diff --git a/api/tests/unit_tests/controllers/console/datasets/test_datasets.py b/api/tests/unit_tests/controllers/console/datasets/test_datasets.py
index e5e4f907947..1952e8f94e5 100644
--- a/api/tests/unit_tests/controllers/console/datasets/test_datasets.py
+++ b/api/tests/unit_tests/controllers/console/datasets/test_datasets.py
@@ -9,6 +9,7 @@ import services
from controllers.console import console_ns
from controllers.console.app.error import ProviderNotInitializeError
from controllers.console.datasets.datasets import (
+ ConsoleDatasetListQuery,
DatasetApi,
DatasetApiBaseUrlApi,
DatasetApiDeleteApi,
@@ -164,6 +165,11 @@ class TestDatasetList:
assert get_datasets_mock.call_args.kwargs["tag_names"] == ["Finance", "Support"]
assert status == 200
+ def test_query_model_trims_empty_tag_names(self):
+ query = ConsoleDatasetListQuery.model_validate({"tag_names": ["", " Finance ", "Support", " "]})
+
+ assert query.tag_names == ["Finance", "Support"]
+
def test_embedding_available_false(self, app: Flask):
api = DatasetListApi()
method = unwrap(api.get)
diff --git a/api/tests/unit_tests/services/test_dataset_service_dataset.py b/api/tests/unit_tests/services/test_dataset_service_dataset.py
index 3d08b6fd096..2d70b4152c9 100644
--- a/api/tests/unit_tests/services/test_dataset_service_dataset.py
+++ b/api/tests/unit_tests/services/test_dataset_service_dataset.py
@@ -15,6 +15,7 @@ from .dataset_service_test_helpers import (
ProviderTokenNotInitError,
RagPipelineDatasetCreateEntity,
SimpleNamespace,
+ TenantAccountRole,
_make_knowledge_configuration,
_make_retrieval_model,
_make_session_context,
@@ -24,6 +25,60 @@ from .dataset_service_test_helpers import (
)
+class TestDatasetServiceList:
+ """Unit tests for dataset list filtering."""
+
+ def test_get_datasets_filters_by_tag_names(self):
+ dataset = DatasetServiceUnitDataFactory.create_dataset_mock(dataset_id="dataset-1")
+ user = SimpleNamespace(id="user-1", current_role=TenantAccountRole.OWNER)
+
+ with (
+ patch("services.dataset_service.db") as mock_db,
+ patch(
+ "services.dataset_service.TagService.get_target_ids_by_tag_names",
+ return_value=["dataset-1"],
+ ) as tag_mock,
+ ):
+ mock_db.session.scalars.return_value.all.return_value = []
+ mock_db.paginate.return_value = SimpleNamespace(items=[dataset], total=1)
+
+ datasets, total = DatasetService.get_datasets(
+ page=1,
+ per_page=20,
+ tenant_id="tenant-1",
+ user=user,
+ include_all=True,
+ tag_names=["Finance", "Support"],
+ )
+
+ assert datasets == [dataset]
+ assert total == 1
+ tag_mock.assert_called_once_with("knowledge", "tenant-1", ["Finance", "Support"])
+ mock_db.paginate.assert_called_once()
+
+ def test_get_datasets_returns_empty_when_tag_names_match_no_targets(self):
+ user = SimpleNamespace(id="user-1", current_role=TenantAccountRole.OWNER)
+
+ with (
+ patch("services.dataset_service.db") as mock_db,
+ patch("services.dataset_service.TagService.get_target_ids_by_tag_names", return_value=[]),
+ ):
+ mock_db.session.scalars.return_value.all.return_value = []
+
+ datasets, total = DatasetService.get_datasets(
+ page=1,
+ per_page=20,
+ tenant_id="tenant-1",
+ user=user,
+ include_all=True,
+ tag_names=["Unknown"],
+ )
+
+ assert datasets == []
+ assert total == 0
+ mock_db.paginate.assert_not_called()
+
+
class TestDatasetServiceValidation:
"""Unit tests for DatasetService validation helpers."""
diff --git a/web/features/tag-management/__tests__/tag-selector.spec.tsx b/web/features/tag-management/__tests__/tag-selector.spec.tsx
index f33470825bc..ec360172c3c 100644
--- a/web/features/tag-management/__tests__/tag-selector.spec.tsx
+++ b/web/features/tag-management/__tests__/tag-selector.spec.tsx
@@ -128,6 +128,20 @@ describe('TagSelector', () => {
expect(screen.getByRole('option', { name: /Backend/i })).toBeInTheDocument()
})
+ it('reports active state when the popup opens and closes', async () => {
+ const user = userEvent.setup()
+ const onActiveChange = vi.fn()
+ render()
+
+ const trigger = screen.getByRole('combobox', { name: /Frontend/i })
+ await user.click(trigger)
+ await screen.findByRole('combobox', { name: i18n.selectorPlaceholder })
+ await user.click(trigger)
+
+ expect(onActiveChange).toHaveBeenNthCalledWith(1, true)
+ expect(onActiveChange).toHaveBeenNthCalledWith(2, false)
+ })
+
it('applies added tags only when the popup closes', async () => {
const user = userEvent.setup()
render()
diff --git a/web/service/knowledge/use-dataset.spec.tsx b/web/service/knowledge/use-dataset.spec.tsx
new file mode 100644
index 00000000000..76f431b87fa
--- /dev/null
+++ b/web/service/knowledge/use-dataset.spec.tsx
@@ -0,0 +1,69 @@
+import type { ReactNode } from 'react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { renderHook, waitFor } from '@testing-library/react'
+import { get } from '../base'
+import { useDatasetList, useInfiniteDatasets } from './use-dataset'
+
+vi.mock('../base', () => ({
+ get: vi.fn(),
+ post: vi.fn(),
+}))
+
+const createWrapper = () => {
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: { retry: false },
+ },
+ })
+
+ return ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+ )
+}
+
+const datasetListResponse = {
+ data: [],
+ has_more: false,
+ limit: 20,
+ page: 1,
+ total: 0,
+}
+
+describe('dataset list hooks', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ vi.mocked(get).mockResolvedValue(datasetListResponse)
+ })
+
+ it('serializes dataset list filters with tag names', async () => {
+ renderHook(
+ () => useDatasetList({
+ initialPage: 1,
+ limit: 20,
+ tag_names: ['Finance', 'Support'],
+ }),
+ { wrapper: createWrapper() },
+ )
+
+ await waitFor(() => {
+ expect(get).toHaveBeenCalledWith('/datasets?tag_names=Finance&tag_names=Support&limit=20&page=1')
+ })
+ })
+
+ it('normalizes infinite dataset list filters with tag names', async () => {
+ renderHook(
+ () => useInfiniteDatasets({
+ page: 2,
+ limit: 10,
+ tag_names: ['Finance'],
+ }),
+ { wrapper: createWrapper() },
+ )
+
+ await waitFor(() => {
+ expect(get).toHaveBeenCalledWith('/datasets?page=2&limit=10&tag_names=Finance')
+ })
+ })
+})