-
- {{ $tr('channelSelectionCount', { count: selectedCount }) }}
-
-
-
-
-
+
+ {{ $tr('channelSelectionCount', { count: selectedCount }) }}
+
+
+
+
+
+
- selectDownloadOption(option)"
- />
-
+ :options="[
+ { label: $tr('downloadPDF'), value: 'pdf' },
+ { label: $tr('downloadCSV'), value: 'csv' },
+ ]"
+ appearance="raised-button"
+ @select="option => selectDownloadOption(option)"
+ />
+
diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js
index 32c9de6843..370d2fad31 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js
+++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js
@@ -1,200 +1,243 @@
-import { mount } from '@vue/test-utils';
-import { factory } from '../../../store';
-import router from '../../../router';
-import { RouteNames } from '../../../constants';
+import { render, screen, waitFor } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+import { createLocalVue } from '@vue/test-utils';
+import Vuex, { Store } from 'vuex';
+import VueRouter from 'vue-router';
import CatalogList from '../CatalogList';
+import { RouteNames } from '../../../constants';
-const store = factory();
-
-router.push({ name: RouteNames.CATALOG_ITEMS });
+const localVue = createLocalVue();
+localVue.use(Vuex);
+localVue.use(VueRouter);
+
+const mockChannels = [
+ {
+ id: 'channel-1',
+ name: 'Channel 1',
+ description: 'Test channel 1',
+ language: 'en',
+ },
+ {
+ id: 'channel-2',
+ name: 'Channel 2',
+ description: 'Test channel 2',
+ language: 'en',
+ },
+];
const results = ['channel-1', 'channel-2'];
-function makeWrapper(computed = {}) {
- const loadCatalog = jest.spyOn(CatalogList.methods, 'loadCatalog');
- loadCatalog.mockImplementation(() => Promise.resolve());
-
- const downloadCSV = jest.spyOn(CatalogList.methods, 'downloadCSV');
- const downloadPDF = jest.spyOn(CatalogList.methods, 'downloadPDF');
+function makeWrapper(overrides = {}) {
+ const mockSearchCatalog = jest.fn(() => Promise.resolve());
- const wrapper = mount(CatalogList, {
- router,
- store,
- computed: {
- page() {
- return {
- count: results.length,
- results,
- };
+ const store = new Store({
+ state: {
+ connection: {
+ online: overrides.offline ? false : true,
},
- ...computed,
},
- stubs: {
- CatalogFilters: true,
+ getters: {
+ loggedIn: () => true,
+ },
+ actions: {
+ showSnackbar: jest.fn(),
+ },
+ modules: {
+ channel: {
+ namespaced: true,
+ state: {
+ channelsMap: {
+ 'channel-1': mockChannels[0],
+ 'channel-2': mockChannels[1],
+ },
+ },
+ getters: {
+ getChannels: state => ids => {
+ return ids.map(id => state.channelsMap[id]).filter(Boolean);
+ },
+ getChannel: state => id => state.channelsMap[id],
+ },
+ actions: {
+ getChannelListDetails: jest.fn(() => Promise.resolve(mockChannels)),
+ },
+ },
+ channelList: {
+ namespaced: true,
+ state: {
+ page: {
+ count: results.length,
+ results,
+ page_number: 1,
+ total_pages: 1,
+ next: null,
+ previous: null,
+ ...overrides.page,
+ },
+ },
+ actions: {
+ searchCatalog: mockSearchCatalog,
+ },
+ },
},
});
- return [wrapper, { loadCatalog, downloadCSV, downloadPDF }];
-}
-
-describe('catalogFilterBar', () => {
- let wrapper, mocks;
- beforeEach(async () => {
- [wrapper, mocks] = makeWrapper();
- await wrapper.setData({ loading: false });
+ const router = new VueRouter({
+ routes: [
+ {
+ name: RouteNames.CATALOG_ITEMS,
+ path: '/catalog',
+ },
+ {
+ name: RouteNames.CATALOG_DETAILS,
+ path: '/catalog/:channelId',
+ },
+ ],
});
- it('should call loadCatalog on mount', () => {
- [wrapper, mocks] = makeWrapper();
- expect(mocks.loadCatalog).toHaveBeenCalled();
+ router.push({ name: RouteNames.CATALOG_ITEMS }).catch(() => {});
+
+ const renderResult = render(CatalogList, {
+ localVue,
+ store,
+ router,
+ stubs: {
+ CatalogFilters: true,
+ },
});
- describe('on query change', () => {
- const searchCatalogMock = jest.fn();
+ return {
+ ...renderResult,
+ store,
+ router,
+ mockSearchCatalog,
+ };
+}
- beforeEach(() => {
- router.replace({ query: {} }).catch(() => {});
- searchCatalogMock.mockReset();
- [wrapper, mocks] = makeWrapper({
- debouncedSearch() {
- return searchCatalogMock;
- },
- });
- });
+describe('CatalogList', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
- it('should call debouncedSearch', async () => {
- const keywords = 'search catalog test';
- router.push({ query: { keywords } }).catch(() => {});
- await wrapper.vm.$nextTick();
- expect(searchCatalogMock).toHaveBeenCalled();
+ describe('initial load', () => {
+ it('should render catalog results on mount', async () => {
+ makeWrapper();
+ await waitFor(() => {
+ // Component renders actual translation - use regex for flexibility
+ expect(screen.getByText(/results found/i)).toBeInTheDocument();
+ });
});
- it('should reset excluded if a filter changed', async () => {
- const keywords = 'search reset test';
- await wrapper.setData({ excluded: ['item 1'] });
- router.push({ query: { keywords } }).catch(() => {});
- await wrapper.vm.$nextTick();
- expect(wrapper.vm.excluded).toEqual([]);
+ it('should call searchCatalog on mount', async () => {
+ const { mockSearchCatalog } = makeWrapper();
+ await waitFor(() => {
+ expect(mockSearchCatalog).toHaveBeenCalled();
+ });
});
- it('should keep excluded if page number changed', async () => {
- await wrapper.setData({ excluded: ['item 1'] });
- router
- .push({
- query: {
- ...wrapper.vm.$route.query,
- page: 2,
- },
- })
- .catch(() => {});
- await wrapper.vm.$nextTick();
- expect(wrapper.vm.excluded).toEqual(['item 1']);
+ it('should display download button when results are available', async () => {
+ makeWrapper();
+ await waitFor(() => {
+ expect(screen.getByTestId('select')).toBeInTheDocument();
+ });
});
});
- describe('download workflow', () => {
- describe('toggling selection mode', () => {
- it('checkboxes and toolbar should be hidden if selecting is false', () => {
- expect(wrapper.findComponent('[data-test="checkbox"]').exists()).toBe(false);
- expect(wrapper.findComponent('[data-test="toolbar"]').exists()).toBe(false);
- });
+ describe('selection mode workflow', () => {
+ it('should hide checkboxes and toolbar initially', async () => {
+ makeWrapper();
+ await waitFor(() => screen.getByTestId('select'));
- it('should activate when select button is clicked', async () => {
- await wrapper.findComponent('[data-test="select"]').trigger('click');
- expect(wrapper.vm.selecting).toBe(true);
- });
+ // Toolbar should not be visible initially (appears only in selection mode)
+ expect(screen.queryByTestId('select-all')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument();
+ });
- it('clicking cancel should exit selection mode', async () => {
- await wrapper.setData({ selecting: true });
- await wrapper.findComponent('[data-test="cancel"]').trigger('click');
- expect(wrapper.vm.selecting).toBe(false);
- });
+ it('should enter selection mode and show toolbar when user clicks select button', async () => {
+ const user = userEvent.setup();
+ makeWrapper();
- it('excluded should reset when selection mode is exited', async () => {
- await wrapper.setData({ selecting: true, excluded: ['item-1', 'item-2'] });
- wrapper.vm.setSelection(false);
- expect(wrapper.vm.excluded).toHaveLength(0);
+ await waitFor(() => screen.getByTestId('select'));
+ await user.click(screen.getByTestId('select'));
+
+ await waitFor(() => {
+ expect(screen.getByTestId('select-all')).toBeInTheDocument();
+ expect(screen.getByTestId('cancel')).toBeInTheDocument();
+ expect(screen.getByText(/channels selected/i)).toBeInTheDocument();
});
});
- describe('selecting channels', () => {
- const excluded = ['item-1'];
+ it('should exit selection mode when user clicks cancel', async () => {
+ const user = userEvent.setup();
+ makeWrapper();
- beforeEach(async () => {
- await wrapper.setData({
- selecting: true,
- excluded,
- });
- });
+ await waitFor(() => screen.getByTestId('select'));
+ await user.click(screen.getByTestId('select'));
+ await waitFor(() => screen.getByTestId('cancel'));
- it('selecting all should select all items on the page', async () => {
- await wrapper.setData({ excluded: excluded.concat(results) });
- wrapper.vm.selectAll = true;
- expect(wrapper.vm.excluded).toEqual(excluded);
- expect(wrapper.vm.selected).toEqual(results);
- });
+ await user.click(screen.getByTestId('cancel'));
- it('deselecting all should select all items on the page', () => {
- wrapper.vm.selectAll = false;
- expect(wrapper.vm.excluded).toEqual(excluded.concat(results));
- expect(wrapper.vm.selected).toEqual([]);
+ await waitFor(() => {
+ expect(screen.queryByTestId('cancel')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('select-all')).not.toBeInTheDocument();
});
+ });
+ });
- it('selecting a channel should remove it from excluded', async () => {
- await wrapper.setData({ excluded: excluded.concat(results) });
- wrapper.vm.selected = [results[0]];
- expect(wrapper.vm.excluded).toEqual(excluded.concat([results[1]]));
- expect(wrapper.vm.selected).toEqual([results[0]]);
- });
+ describe('channel selection', () => {
+ it('should display select-all checkbox in selection mode', async () => {
+ const user = userEvent.setup();
+ makeWrapper();
- it('deselecting a channel should add it to excluded', () => {
- wrapper.vm.selected = [results[0]];
- expect(wrapper.vm.excluded).toEqual(excluded.concat([results[1]]));
- expect(wrapper.vm.selected).toEqual([results[0]]);
+ await waitFor(() => screen.getByTestId('select'));
+ await user.click(screen.getByTestId('select'));
+
+ await waitFor(() => {
+ expect(screen.getByTestId('select-all')).toBeInTheDocument();
+ expect(screen.getByText(/channels selected/i)).toBeInTheDocument();
});
});
+ });
- describe('download csv', () => {
- let downloadChannelsCSV;
- const excluded = ['item-1', 'item-2'];
+ describe('search and filtering', () => {
+ it('should call searchCatalog when query parameters change', async () => {
+ const { router, mockSearchCatalog } = makeWrapper();
- beforeEach(async () => {
- await wrapper.setData({ selecting: true, excluded });
- downloadChannelsCSV = jest.spyOn(wrapper.vm, 'downloadChannelsCSV');
- downloadChannelsCSV.mockImplementation(() => Promise.resolve());
- });
+ await waitFor(() => screen.getByText(/results found/i));
+
+ const initialCalls = mockSearchCatalog.mock.calls.length;
- it('clicking download CSV should call downloadCSV', async () => {
- mocks.downloadCSV.mockImplementationOnce(() => Promise.resolve());
- await wrapper.findComponent('[data-test="download-button"]').trigger('click');
- const menuOptions = wrapper.findAll('.ui-menu-option-content');
- await menuOptions.at(1).trigger('click');
- expect(mocks.downloadCSV).toHaveBeenCalled();
+ await router.push({
+ name: RouteNames.CATALOG_ITEMS,
+ query: { keywords: 'search test' },
});
- it('clicking download PDF should call downloadPDF', async () => {
- mocks.downloadPDF.mockImplementationOnce(() => Promise.resolve());
- await wrapper.findComponent('[data-test="download-button"]').trigger('click');
- const menuOptions = wrapper.findAll('.ui-menu-option-content');
- await menuOptions.at(0).trigger('click');
- expect(mocks.downloadPDF).toHaveBeenCalled();
+ await waitFor(() => {
+ expect(mockSearchCatalog.mock.calls.length).toBeGreaterThan(initialCalls);
});
+ });
+
+ it('should maintain results display after filtering', async () => {
+ const { router } = makeWrapper();
- it('downloadCSV should call downloadChannelsCSV with current parameters', async () => {
- const keywords = 'Download csv keywords test';
- router.replace({ query: { keywords } });
- await wrapper.vm.downloadCSV();
- expect(downloadChannelsCSV.mock.calls[0][0].keywords).toBe(keywords);
+ await waitFor(() => screen.getByText(/results found/i));
+
+ await router.push({
+ name: RouteNames.CATALOG_ITEMS,
+ query: { keywords: 'test search' },
});
- it('downloadCSV should call downloadChannelsCSV with list of excluded items', async () => {
- await wrapper.vm.downloadCSV();
- expect(downloadChannelsCSV.mock.calls[0][0].excluded).toEqual(excluded);
+ await waitFor(() => {
+ expect(screen.getByText(/results found/i)).toBeInTheDocument();
});
+ });
+ });
+
+ describe('download workflow', () => {
+ it('should show select button to enable downloads', async () => {
+ makeWrapper();
- it('downloadCSV should exit selection mode', async () => {
- await wrapper.vm.downloadCSV();
- expect(wrapper.vm.selecting).toBe(false);
+ await waitFor(() => {
+ expect(screen.getByTestId('select')).toBeInTheDocument();
});
});
});