From 777de0ab68534072006e276c6140c4edc8ff9dfe Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Wed, 5 Nov 2025 15:00:08 +0530 Subject: [PATCH 01/13] Refactor catalogList tests to use Testing Library and improve mock implementations --- .../Channel/__tests__/catalogList.spec.js | 373 +++++++++++------- 1 file changed, 220 insertions(+), 153 deletions(-) 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..0d851a44ae 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,267 @@ -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()); +function makeWrapper() { + const mockSearchCatalog = jest.fn(() => Promise.resolve()); + + const store = new Store({ + state: { + connection: { + online: 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); + }, + }, + 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, + }, + }, + actions: { + searchCatalog: mockSearchCatalog, + }, + }, + }, + }); + + const router = new VueRouter({ + routes: [ + { + name: RouteNames.CATALOG_ITEMS, + path: '/catalog', + }, + { + name: RouteNames.CATALOG_DETAILS, + path: '/catalog/:channelId', + }, + ], + }); - const downloadCSV = jest.spyOn(CatalogList.methods, 'downloadCSV'); - const downloadPDF = jest.spyOn(CatalogList.methods, 'downloadPDF'); + router.push({ name: RouteNames.CATALOG_ITEMS }).catch(() => {}); - const wrapper = mount(CatalogList, { - router, + const renderResult = render(CatalogList, { + localVue, store, - computed: { - page() { - return { - count: results.length, - results, - }; - }, - ...computed, - }, + router, stubs: { CatalogFilters: true, + ChannelItem: { + props: ['channelId'], + template: '
Channel Item
', + }, + LoadingText: { template: '
Loading...
' }, + Pagination: { template: '
Pagination
' }, + BottomBar: { template: '
' }, + Checkbox: { + props: ['value', 'label', 'indeterminate'], + template: ` + + `, + }, + ToolBar: { template: '
' }, + OfflineText: { template: '
Offline
' }, + }, + mocks: { + $tr: (key, params) => { + const translations = { + resultsText: params ? `${params.count} result${params.count !== 1 ? 's' : ''} found` : '', + selectChannels: 'Download a summary of selected channels', + selectAll: 'Select all', + cancelButton: 'Cancel', + downloadButton: 'Download', + downloadPDF: 'Download PDF', + downloadCSV: 'Download CSV', + downloadingMessage: 'Download started', + channelSelectionCount: params ? `${params.count} channel${params.count !== 1 ? 's' : ''} selected` : '', + }; + return translations[key] || key; + }, }, }); - return [wrapper, { loadCatalog, downloadCSV, downloadPDF }]; -} -describe('catalogFilterBar', () => { - let wrapper, mocks; + return { ...renderResult, store, router, mockSearchCatalog }; +} - beforeEach(async () => { - [wrapper, mocks] = makeWrapper(); - await wrapper.setData({ loading: false }); +describe('catalogList', () => { + beforeEach(() => { + jest.clearAllMocks(); }); - it('should call loadCatalog on mount', () => { - [wrapper, mocks] = makeWrapper(); - expect(mocks.loadCatalog).toHaveBeenCalled(); + it('should call loadCatalog on mount', async () => { + const { mockSearchCatalog } = makeWrapper(); + await waitFor(() => { + expect(mockSearchCatalog).toHaveBeenCalled(); + }); }); describe('on query change', () => { - const searchCatalogMock = jest.fn(); - - beforeEach(() => { - router.replace({ query: {} }).catch(() => {}); - searchCatalogMock.mockReset(); - [wrapper, mocks] = makeWrapper({ - debouncedSearch() { - return searchCatalogMock; - }, + it('should call searchCatalog when query changes', async () => { + const { router, mockSearchCatalog } = makeWrapper(); + + await waitFor(() => screen.getByText('2 results found')); + + const initialCalls = mockSearchCatalog.mock.calls.length; + + await router.push({ + name: RouteNames.CATALOG_ITEMS, + query: { keywords: 'search catalog test' } }); - }); - it('should call debouncedSearch', async () => { - const keywords = 'search catalog test'; - router.push({ query: { keywords } }).catch(() => {}); - await wrapper.vm.$nextTick(); - expect(searchCatalogMock).toHaveBeenCalled(); - }); - - 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 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']); + await waitFor(() => { + expect(mockSearchCatalog.mock.calls.length).toBeGreaterThan(initialCalls); + }); }); }); 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); + it('checkboxes and toolbar should be hidden if selecting is false', async () => { + makeWrapper(); + await waitFor(() => screen.getByText('2 results found')); + + expect(screen.queryByTestId('checkbox')).not.toBeInTheDocument(); + expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); }); it('should activate when select button is clicked', async () => { - await wrapper.findComponent('[data-test="select"]').trigger('click'); - expect(wrapper.vm.selecting).toBe(true); + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => { + expect(screen.getByText('Select all')).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('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); + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => screen.getByText('Cancel')); + await user.click(screen.getByText('Cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); + }); }); }); describe('selecting channels', () => { - const excluded = ['item-1']; - - beforeEach(async () => { - await wrapper.setData({ - selecting: true, - excluded, + it('should show all channels selected by default when entering selection mode', async () => { + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => { + expect(screen.getByText('2 channels selected')).toBeInTheDocument(); }); }); - 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); - }); - - 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([]); - }); - - 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]]); - }); - - 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]]); + it('should show select all checkbox', async () => { + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => { + const selectAllCheckbox = screen.getByText('Select all'); + expect(selectAllCheckbox).toBeInTheDocument(); + }); }); }); - describe('download csv', () => { - let downloadChannelsCSV; - const excluded = ['item-1', 'item-2']; - - beforeEach(async () => { - await wrapper.setData({ selecting: true, excluded }); - downloadChannelsCSV = jest.spyOn(wrapper.vm, 'downloadChannelsCSV'); - downloadChannelsCSV.mockImplementation(() => Promise.resolve()); - }); - - 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(); - }); - - 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(); - }); - - 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); - }); - - it('downloadCSV should call downloadChannelsCSV with list of excluded items', async () => { - await wrapper.vm.downloadCSV(); - expect(downloadChannelsCSV.mock.calls[0][0].excluded).toEqual(excluded); + describe('download csv and pdf', () => { + it('clicking download button should show Download text', async () => { + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => { + expect(screen.getByText('Download')).toBeInTheDocument(); + }); }); - it('downloadCSV should exit selection mode', async () => { - await wrapper.vm.downloadCSV(); - expect(wrapper.vm.selecting).toBe(false); + it('should show both download options (CSV and PDF) available', async () => { + const user = userEvent.setup(); + makeWrapper(); + + await waitFor(() => screen.getByText('Download a summary of selected channels')); + await user.click(screen.getByText('Download a summary of selected channels')); + + await waitFor(() => { + expect(screen.getByText('Download')).toBeInTheDocument(); + // The dropdown menu contains both PDF and CSV options when clicked + }); }); }); }); From 84d1b9b37d02aed402a4cb304c6f4380c29a7165 Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Wed, 5 Nov 2025 15:48:02 +0530 Subject: [PATCH 02/13] Enhance catalogList tests: clarify checkbox test description and improve formatting --- .../channelList/views/Channel/__tests__/catalogList.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0d851a44ae..8898db5b5f 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -224,7 +224,7 @@ describe('catalogList', () => { }); }); - it('should show select all checkbox', async () => { + it('should show select all checkbox when in selection mode', async () => { const user = userEvent.setup(); makeWrapper(); From 54c181a8191077173470f61efaa3f63a995d1d23 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:31:51 +0000 Subject: [PATCH 03/13] [pre-commit.ci lite] apply automatic fixes --- .../Channel/__tests__/catalogList.spec.js | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) 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 8898db5b5f..07e0801904 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -1,4 +1,4 @@ - import { render, screen, waitFor } from '@testing-library/vue'; +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'; @@ -9,7 +9,7 @@ import { RouteNames } from '../../../constants'; const localVue = createLocalVue(); localVue.use(Vuex); localVue.use(VueRouter); - + const mockChannels = [ { id: 'channel-1', @@ -29,7 +29,7 @@ const results = ['channel-1', 'channel-2']; function makeWrapper() { const mockSearchCatalog = jest.fn(() => Promise.resolve()); - + const store = new Store({ state: { connection: { @@ -108,8 +108,8 @@ function makeWrapper() { props: ['value', 'label', 'indeterminate'], template: ` `, }, - KButton: { - props: ['text', 'dataTest', 'primary'], - template: - '', - }, - KDropdownMenu: { - props: ['options'], - template: ` -
- -
- `, - }, - ToolBar: { template: '
' }, - OfflineText: { template: '
Offline
' }, - VLayout: { template: '
' }, - VFlex: { template: '
' }, - VContainer: { template: '
' }, - VSlideYTransition: { template: '
' }, - VSpacer: { template: '
' }, }, mocks: { $tr: (key, params) => { @@ -222,7 +193,6 @@ describe('CatalogList', () => { makeWrapper(); await waitFor(() => screen.getByText('2 results found')); - // Checkboxes exist but are hidden, toolbar should not be in DOM const checkboxes = screen.queryAllByTestId('checkbox'); if (checkboxes.length > 0) { checkboxes.forEach(checkbox => { @@ -266,10 +236,8 @@ describe('CatalogList', () => { await user.click(screen.getByText('Download a summary of selected channels')); await waitFor(() => screen.getByTestId('toolbar')); - // Click cancel await user.click(screen.getByText('Cancel')); - // Verify toolbar is gone await waitFor(() => { expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); }); @@ -281,11 +249,9 @@ describe('CatalogList', () => { const user = userEvent.setup(); makeWrapper(); - // Enter selection mode await waitFor(() => screen.getByText('Download a summary of selected channels')); await user.click(screen.getByText('Download a summary of selected channels')); - // Wait for toolbar with selection count to appear await waitFor(() => { expect(screen.getByTestId('toolbar')).toBeInTheDocument(); }); @@ -295,11 +261,9 @@ describe('CatalogList', () => { const user = userEvent.setup(); makeWrapper(); - // Enter selection mode await waitFor(() => screen.getByText('Download a summary of selected channels')); await user.click(screen.getByText('Download a summary of selected channels')); - // Verify select-all checkbox appears await waitFor(() => { expect(screen.getByTestId('select-all-checkbox')).toBeInTheDocument(); }); @@ -314,7 +278,6 @@ describe('CatalogList', () => { const initialCalls = mockSearchCatalog.mock.calls.length; - // Change search query await router.push({ name: RouteNames.CATALOG_ITEMS, query: { keywords: 'search test' }, @@ -330,13 +293,11 @@ describe('CatalogList', () => { await waitFor(() => screen.getByText('2 results found')); - // Change search query await router.push({ name: RouteNames.CATALOG_ITEMS, query: { keywords: 'test search' }, }); - // Results should still be visible await waitFor(() => { expect(screen.getByText('2 results found')).toBeInTheDocument(); }); @@ -359,29 +320,9 @@ describe('CatalogList', () => { await waitFor(() => screen.getByText('Download a summary of selected channels')); await user.click(screen.getByText('Download a summary of selected channels')); - // Toolbar should appear when in selection mode await waitFor(() => { expect(screen.getByTestId('toolbar')).toBeInTheDocument(); }); }); }); - - describe('offline state', () => { - it('should display offline message when connection is offline', async () => { - makeWrapper({ offline: true }); - - await waitFor(() => { - expect(screen.getByText('Offline')).toBeInTheDocument(); - }); - }); - }); - - describe('loading state', () => { - it('should show loading message when loading is true', async () => { - makeWrapper(); - - // Verify loading text appears initially - expect(screen.getByText('Loading...')).toBeInTheDocument(); - }); - }); }); From de8e0fc02358d8c81555ce99dbb7f13682c97f2f Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Tue, 25 Nov 2025 09:28:41 +0530 Subject: [PATCH 07/13] Refactor catalogList tests: enhance makeWrapper function --- .../Channel/__tests__/catalogList.spec.js | 58 +++++-------------- 1 file changed, 16 insertions(+), 42 deletions(-) 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 bde7953f00..e996b78dc0 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -38,6 +38,9 @@ function makeWrapper(overrides = {}) { online: overrides.offline ? false : true, }, }, + getters: { + loggedIn: () => true, + }, actions: { showSnackbar: jest.fn(), }, @@ -54,6 +57,7 @@ function makeWrapper(overrides = {}) { 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)), @@ -100,35 +104,6 @@ function makeWrapper(overrides = {}) { router, stubs: { CatalogFilters: true, - ChannelItem: { - props: ['channelId'], - template: '
Channel Item
', - }, - BottomBar: { template: '
' }, - Checkbox: { - props: ['value', 'label', 'indeterminate'], - data() { - return { - isChecked: this.value, - }; - }, - watch: { - value(newVal) { - this.isChecked = newVal; - }, - }, - template: ` - - `, - }, }, mocks: { $tr: (key, params) => { @@ -193,13 +168,9 @@ describe('CatalogList', () => { makeWrapper(); await waitFor(() => screen.getByText('2 results found')); - const checkboxes = screen.queryAllByTestId('checkbox'); - if (checkboxes.length > 0) { - checkboxes.forEach(checkbox => { - expect(checkbox.closest('label')).toHaveStyle('display: none'); - }); - } - expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); + // Toolbar should not be visible initially (appears only in selection mode) + expect(screen.queryByText('Select all')).not.toBeInTheDocument(); + expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); }); it('should enter selection mode when user clicks select button', async () => { @@ -211,7 +182,7 @@ describe('CatalogList', () => { await waitFor(() => { expect(screen.getByText('Select all')).toBeInTheDocument(); - expect(screen.getByTestId('toolbar')).toBeInTheDocument(); + expect(screen.getByText('Cancel')).toBeInTheDocument(); }); }); @@ -234,12 +205,13 @@ describe('CatalogList', () => { // Enter selection mode await waitFor(() => screen.getByText('Download a summary of selected channels')); await user.click(screen.getByText('Download a summary of selected channels')); - await waitFor(() => screen.getByTestId('toolbar')); + await waitFor(() => screen.getByText('Cancel')); await user.click(screen.getByText('Cancel')); await waitFor(() => { - expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); + expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); + expect(screen.queryByText('Select all')).not.toBeInTheDocument(); }); }); }); @@ -253,7 +225,8 @@ describe('CatalogList', () => { await user.click(screen.getByText('Download a summary of selected channels')); await waitFor(() => { - expect(screen.getByTestId('toolbar')).toBeInTheDocument(); + expect(screen.getByText('Select all')).toBeInTheDocument(); + expect(screen.getByText('2 channels selected')).toBeInTheDocument(); }); }); @@ -265,7 +238,7 @@ describe('CatalogList', () => { await user.click(screen.getByText('Download a summary of selected channels')); await waitFor(() => { - expect(screen.getByTestId('select-all-checkbox')).toBeInTheDocument(); + expect(screen.getByText('Select all')).toBeInTheDocument(); }); }); }); @@ -321,7 +294,8 @@ describe('CatalogList', () => { await user.click(screen.getByText('Download a summary of selected channels')); await waitFor(() => { - expect(screen.getByTestId('toolbar')).toBeInTheDocument(); + expect(screen.getByText('Select all')).toBeInTheDocument(); + expect(screen.getByText('Cancel')).toBeInTheDocument(); }); }); }); From 59ac00f52200b5ffafa5e8f0bcf1bbd1a772452e Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Mon, 1 Dec 2025 11:40:54 +0530 Subject: [PATCH 08/13] removed unused test to focus on user interactions --- .../Channel/__tests__/catalogList.spec.js | 44 +------------------ 1 file changed, 2 insertions(+), 42 deletions(-) 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 e996b78dc0..75a8489665 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -29,8 +29,6 @@ const results = ['channel-1', 'channel-2']; function makeWrapper(overrides = {}) { const mockSearchCatalog = jest.fn(() => Promise.resolve()); - const mockDownloadChannelsCSV = jest.fn(() => Promise.resolve()); - const mockDownloadChannelsPDF = jest.fn(() => Promise.resolve()); const store = new Store({ state: { @@ -130,8 +128,6 @@ function makeWrapper(overrides = {}) { store, router, mockSearchCatalog, - mockDownloadChannelsCSV, - mockDownloadChannelsPDF, }; } @@ -173,7 +169,7 @@ describe('CatalogList', () => { expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); }); - it('should enter selection mode when user clicks select button', async () => { + it('should enter selection mode and show toolbar with selection count when user clicks select button', async () => { const user = userEvent.setup(); makeWrapper(); @@ -183,17 +179,6 @@ describe('CatalogList', () => { await waitFor(() => { expect(screen.getByText('Select all')).toBeInTheDocument(); expect(screen.getByText('Cancel')).toBeInTheDocument(); - }); - }); - - it('should show all channels selected by default in selection mode', async () => { - const user = userEvent.setup(); - makeWrapper(); - - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); - - await waitFor(() => { expect(screen.getByText('2 channels selected')).toBeInTheDocument(); }); }); @@ -217,7 +202,7 @@ describe('CatalogList', () => { }); describe('channel selection', () => { - it('should show selection count in toolbar when in selection mode', async () => { + it('should display select-all checkbox and selection count in selection mode', async () => { const user = userEvent.setup(); makeWrapper(); @@ -229,18 +214,6 @@ describe('CatalogList', () => { expect(screen.getByText('2 channels selected')).toBeInTheDocument(); }); }); - - it('should display select-all checkbox in selection mode', async () => { - const user = userEvent.setup(); - makeWrapper(); - - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); - - await waitFor(() => { - expect(screen.getByText('Select all')).toBeInTheDocument(); - }); - }); }); describe('search and filtering', () => { @@ -285,18 +258,5 @@ describe('CatalogList', () => { expect(screen.getByText('Download a summary of selected channels')).toBeInTheDocument(); }); }); - - it('should display toolbar when entering selection mode', async () => { - const user = userEvent.setup(); - makeWrapper(); - - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); - - await waitFor(() => { - expect(screen.getByText('Select all')).toBeInTheDocument(); - expect(screen.getByText('Cancel')).toBeInTheDocument(); - }); - }); }); }); From b3c1b9cfeddef7bfc34bd77df6888db19d52d2fb Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Fri, 19 Dec 2025 13:21:01 +0530 Subject: [PATCH 09/13] Refactor catalogList tests: replace hardcoded strings with regex for improved flexibility --- .../Channel/__tests__/catalogList.spec.js | 74 ++++++++----------- 1 file changed, 29 insertions(+), 45 deletions(-) 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 75a8489665..a5118f3ad4 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -103,24 +103,6 @@ function makeWrapper(overrides = {}) { stubs: { CatalogFilters: true, }, - mocks: { - $tr: (key, params) => { - const translations = { - resultsText: params ? `${params.count} result${params.count !== 1 ? 's' : ''} found` : '', - selectChannels: 'Download a summary of selected channels', - selectAll: 'Select all', - cancelButton: 'Cancel', - downloadButton: 'Download', - downloadPDF: 'Download PDF', - downloadCSV: 'Download CSV', - downloadingMessage: 'Download started', - channelSelectionCount: params - ? `${params.count} channel${params.count !== 1 ? 's' : ''} selected` - : '', - }; - return translations[key] || key; - }, - }, }); return { @@ -140,7 +122,8 @@ describe('CatalogList', () => { it('should render catalog results on mount', async () => { makeWrapper(); await waitFor(() => { - expect(screen.getByText('2 results found')).toBeInTheDocument(); + // Component renders actual translation - use regex for flexibility + expect(screen.getByText(/results found/i)).toBeInTheDocument(); }); }); @@ -154,7 +137,8 @@ describe('CatalogList', () => { it('should display download button when results are available', async () => { makeWrapper(); await waitFor(() => { - expect(screen.getByText('Download a summary of selected channels')).toBeInTheDocument(); + // Use actual button text rendered by component + expect(screen.getByText(/download a summary/i)).toBeInTheDocument(); }); }); }); @@ -162,24 +146,24 @@ describe('CatalogList', () => { describe('selection mode workflow', () => { it('should hide checkboxes and toolbar initially', async () => { makeWrapper(); - await waitFor(() => screen.getByText('2 results found')); + await waitFor(() => screen.getByText(/download a summary/i)); // Toolbar should not be visible initially (appears only in selection mode) - expect(screen.queryByText('Select all')).not.toBeInTheDocument(); - expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); + expect(screen.queryByText(/select all/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/cancel/i)).not.toBeInTheDocument(); }); - it('should enter selection mode and show toolbar with selection count when user clicks select button', async () => { + it('should enter selection mode and show toolbar when user clicks select button', async () => { const user = userEvent.setup(); makeWrapper(); - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); + await waitFor(() => screen.getByText(/download a summary/i)); + await user.click(screen.getByText(/download a summary/i)); await waitFor(() => { - expect(screen.getByText('Select all')).toBeInTheDocument(); - expect(screen.getByText('Cancel')).toBeInTheDocument(); - expect(screen.getByText('2 channels selected')).toBeInTheDocument(); + expect(screen.getByText(/select all/i)).toBeInTheDocument(); + expect(screen.getByText(/cancel/i)).toBeInTheDocument(); + expect(screen.getByText(/channels selected/i)).toBeInTheDocument(); }); }); @@ -188,30 +172,30 @@ describe('CatalogList', () => { makeWrapper(); // Enter selection mode - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); - await waitFor(() => screen.getByText('Cancel')); + await waitFor(() => screen.getByText(/download a summary/i)); + await user.click(screen.getByText(/download a summary/i)); + await waitFor(() => screen.getByText(/cancel/i)); - await user.click(screen.getByText('Cancel')); + await user.click(screen.getByText(/cancel/i)); await waitFor(() => { - expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); - expect(screen.queryByText('Select all')).not.toBeInTheDocument(); + expect(screen.queryByText(/cancel/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/select all/i)).not.toBeInTheDocument(); }); }); }); describe('channel selection', () => { - it('should display select-all checkbox and selection count in selection mode', async () => { + it('should display select-all checkbox in selection mode', async () => { const user = userEvent.setup(); makeWrapper(); - await waitFor(() => screen.getByText('Download a summary of selected channels')); - await user.click(screen.getByText('Download a summary of selected channels')); + await waitFor(() => screen.getByText(/download a summary/i)); + await user.click(screen.getByText(/download a summary/i)); await waitFor(() => { - expect(screen.getByText('Select all')).toBeInTheDocument(); - expect(screen.getByText('2 channels selected')).toBeInTheDocument(); + expect(screen.getByText(/select all/i)).toBeInTheDocument(); + expect(screen.getByText(/channels selected/i)).toBeInTheDocument(); }); }); }); @@ -220,7 +204,7 @@ describe('CatalogList', () => { it('should call searchCatalog when query parameters change', async () => { const { router, mockSearchCatalog } = makeWrapper(); - await waitFor(() => screen.getByText('2 results found')); + await waitFor(() => screen.getByText(/results found/i)); const initialCalls = mockSearchCatalog.mock.calls.length; @@ -234,10 +218,10 @@ describe('CatalogList', () => { }); }); - it('should show results after filtering', async () => { + it('should maintain results display after filtering', async () => { const { router } = makeWrapper(); - await waitFor(() => screen.getByText('2 results found')); + await waitFor(() => screen.getByText(/results found/i)); await router.push({ name: RouteNames.CATALOG_ITEMS, @@ -245,7 +229,7 @@ describe('CatalogList', () => { }); await waitFor(() => { - expect(screen.getByText('2 results found')).toBeInTheDocument(); + expect(screen.getByText(/results found/i)).toBeInTheDocument(); }); }); }); @@ -255,7 +239,7 @@ describe('CatalogList', () => { makeWrapper(); await waitFor(() => { - expect(screen.getByText('Download a summary of selected channels')).toBeInTheDocument(); + expect(screen.getByText(/download a summary/i)).toBeInTheDocument(); }); }); }); From e63c3dabd2a4546941ed4be96c27833f57b5d9c3 Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Thu, 15 Jan 2026 08:38:01 +0530 Subject: [PATCH 10/13] replace button text checks with data-test attributes for improved test reliability --- .../Channel/__tests__/catalogList.spec.js | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) 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 a5118f3ad4..5c8d8e6c2b 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogList.spec.js @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/vue'; +import { render, screen, waitFor, configure } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; import { createLocalVue } from '@vue/test-utils'; import Vuex, { Store } from 'vuex'; @@ -6,6 +6,9 @@ import VueRouter from 'vue-router'; import CatalogList from '../CatalogList'; import { RouteNames } from '../../../constants'; +// Configuring Vue Testing Library to treat 'data-test' as the testId attribute +configure({ testIdAttribute: 'data-test' }); + const localVue = createLocalVue(); localVue.use(Vuex); localVue.use(VueRouter); @@ -137,8 +140,7 @@ describe('CatalogList', () => { it('should display download button when results are available', async () => { makeWrapper(); await waitFor(() => { - // Use actual button text rendered by component - expect(screen.getByText(/download a summary/i)).toBeInTheDocument(); + expect(screen.getByTestId('select')).toBeInTheDocument(); }); }); }); @@ -146,23 +148,23 @@ describe('CatalogList', () => { describe('selection mode workflow', () => { it('should hide checkboxes and toolbar initially', async () => { makeWrapper(); - await waitFor(() => screen.getByText(/download a summary/i)); + await waitFor(() => screen.getByTestId('select')); // Toolbar should not be visible initially (appears only in selection mode) - expect(screen.queryByText(/select all/i)).not.toBeInTheDocument(); - expect(screen.queryByText(/cancel/i)).not.toBeInTheDocument(); + expect(screen.queryByTestId('select-all')).not.toBeInTheDocument(); + expect(screen.queryByTestId('toolbar')).not.toBeInTheDocument(); }); it('should enter selection mode and show toolbar when user clicks select button', async () => { const user = userEvent.setup(); makeWrapper(); - await waitFor(() => screen.getByText(/download a summary/i)); - await user.click(screen.getByText(/download a summary/i)); + await waitFor(() => screen.getByTestId('select')); + await user.click(screen.getByTestId('select')); await waitFor(() => { - expect(screen.getByText(/select all/i)).toBeInTheDocument(); - expect(screen.getByText(/cancel/i)).toBeInTheDocument(); + expect(screen.getByTestId('select-all')).toBeInTheDocument(); + expect(screen.getByTestId('cancel')).toBeInTheDocument(); expect(screen.getByText(/channels selected/i)).toBeInTheDocument(); }); }); @@ -171,16 +173,15 @@ describe('CatalogList', () => { const user = userEvent.setup(); makeWrapper(); - // Enter selection mode - await waitFor(() => screen.getByText(/download a summary/i)); - await user.click(screen.getByText(/download a summary/i)); - await waitFor(() => screen.getByText(/cancel/i)); + await waitFor(() => screen.getByTestId('select')); + await user.click(screen.getByTestId('select')); + await waitFor(() => screen.getByTestId('cancel')); - await user.click(screen.getByText(/cancel/i)); + await user.click(screen.getByTestId('cancel')); await waitFor(() => { - expect(screen.queryByText(/cancel/i)).not.toBeInTheDocument(); - expect(screen.queryByText(/select all/i)).not.toBeInTheDocument(); + expect(screen.queryByTestId('cancel')).not.toBeInTheDocument(); + expect(screen.queryByTestId('select-all')).not.toBeInTheDocument(); }); }); }); @@ -190,11 +191,11 @@ describe('CatalogList', () => { const user = userEvent.setup(); makeWrapper(); - await waitFor(() => screen.getByText(/download a summary/i)); - await user.click(screen.getByText(/download a summary/i)); + await waitFor(() => screen.getByTestId('select')); + await user.click(screen.getByTestId('select')); await waitFor(() => { - expect(screen.getByText(/select all/i)).toBeInTheDocument(); + expect(screen.getByTestId('select-all')).toBeInTheDocument(); expect(screen.getByText(/channels selected/i)).toBeInTheDocument(); }); }); @@ -239,7 +240,7 @@ describe('CatalogList', () => { makeWrapper(); await waitFor(() => { - expect(screen.getByText(/download a summary/i)).toBeInTheDocument(); + expect(screen.getByTestId('select')).toBeInTheDocument(); }); }); }); From 00ea4cb2199cb5ff3c15b2ed07a49af120e5154f Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Fri, 16 Jan 2026 21:12:50 +0530 Subject: [PATCH 11/13] replace data-test attributes with data-testid for consistency in testing --- .../frontend/channelList/views/Channel/CatalogList.vue | 10 +++++----- .../views/Channel/__tests__/catalogList.spec.js | 5 +---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue index 00a741531d..6f58c3375a 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue @@ -35,7 +35,7 @@ @@ -44,7 +44,7 @@ v-model="selectAll" class="mb-4 mx-2" :label="$tr('selectAll')" - data-test="select-all" + data-testid="select-all" :indeterminate="selected.length > 0 && selected.length < channels.length" /> @@ -59,7 +59,7 @@ v-model="selected" class="mx-2" :value="item.id" - data-test="checkbox" + data-testid="checkbox" />
@@ -92,7 +92,7 @@
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 5c8d8e6c2b..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,4 +1,4 @@ -import { render, screen, waitFor, configure } from '@testing-library/vue'; +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'; @@ -6,9 +6,6 @@ import VueRouter from 'vue-router'; import CatalogList from '../CatalogList'; import { RouteNames } from '../../../constants'; -// Configuring Vue Testing Library to treat 'data-test' as the testId attribute -configure({ testIdAttribute: 'data-test' }); - const localVue = createLocalVue(); localVue.use(Vuex); localVue.use(VueRouter); From 2f40c2cc3b465f60025dee2cd8b78daff8d03867 Mon Sep 17 00:00:00 2001 From: Tushar Verma Date: Fri, 16 Jan 2026 21:26:40 +0530 Subject: [PATCH 12/13] replace data-test attribute with data-testid --- .../frontend/channelList/views/Channel/CatalogList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue index 6f58c3375a..2cd8b297af 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue @@ -100,7 +100,7 @@ Date: Fri, 16 Jan 2026 21:47:42 +0530 Subject: [PATCH 13/13] fixed conflit --- .../channelList/views/Channel/CatalogList.vue | 78 ++++--------------- 1 file changed, 16 insertions(+), 62 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue index d646c1b1ab..b21cec44a1 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue @@ -65,35 +65,13 @@ :value="item.id" data-testid="checkbox" /> - - - - - - - - + + -
- {{ $tr('channelSelectionCount', { count: selectedCount }) }} -
- -
- - - - -
{{ $tr('channelSelectionCount', { count: selectedCount }) }}
@@ -150,19 +109,14 @@ > - -
+ :options="[ + { label: $tr('downloadPDF'), value: 'pdf' }, + { label: $tr('downloadCSV'), value: 'csv' }, + ]" + appearance="raised-button" + @select="option => selectDownloadOption(option)" + /> +