diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogFilters.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogFilters.vue index f2e92dc894..7b6cb7d0e7 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogFilters.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogFilters.vue @@ -1,133 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ $tr('includesLabel') }} - - - - - - - - - - - - - - - - - + + + + + @@ -135,134 +30,67 @@ diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue index 00a741531d..5b245e8239 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue @@ -1,120 +1,130 @@ - - - - - - - - + + + + + + + + + - - + - - {{ $tr('resultsText', { count: page.count }) }} - - - - - - + - + {{ $tr('resultsText', { count: page.count }) }} + + - - - - + + + + + + + + + + + + + - - + {{ $tr('channelSelectionCount', { count: selectedCount }) }} + + + + - - - - - - {{ $tr('channelSelectionCount', { count: selectedCount }) }} - - - + - - - selectDownloadOption(option)" - /> - - - + data-test="download-button" + iconAfter="dropup" + > + selectDownloadOption(option)" + /> + + + + @@ -128,8 +138,10 @@ import isEqual from 'lodash/isEqual'; import sortBy from 'lodash/sortBy'; import union from 'lodash/union'; + import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow'; import { RouteNames } from '../../constants'; import CatalogFilters from './CatalogFilters'; + import CatalogFilterBar from './CatalogFilterBar'; import ChannelItem from './ChannelItem'; import LoadingText from 'shared/views/LoadingText'; import Pagination from 'shared/views/Pagination'; @@ -146,6 +158,7 @@ ChannelItem, LoadingText, CatalogFilters, + CatalogFilterBar, Pagination, BottomBar, Checkbox, @@ -153,6 +166,13 @@ OfflineText, }, mixins: [channelExportMixin, constantsTranslationMixin], + setup() { + const { windowIsSmall } = useKResponsiveWindow(); + + return { + windowIsSmall, + }; + }, data() { return { loading: true, @@ -301,9 +321,39 @@ diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/CatalogFilterPanelContent.spec.js b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/CatalogFilterPanelContent.spec.js new file mode 100644 index 0000000000..0dba2e4fbe --- /dev/null +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/CatalogFilterPanelContent.spec.js @@ -0,0 +1,86 @@ +import { render, screen } from '@testing-library/vue'; +import { Store } from 'vuex'; +import VueRouter from 'vue-router'; +import CatalogFilterPanelContent from '../components/CatalogFilterPanelContent.vue'; + +const mockRouter = new VueRouter({ + routes: [{ name: 'CATALOG_FAQ', path: '/catalog/faq' }], +}); + +const createStore = () => { + return new Store({ + getters: { + loggedIn: () => true, + }, + }); +}; + +const renderComponent = () => { + const store = createStore(); + + return render(CatalogFilterPanelContent, { + store, + routes: mockRouter, + }); +}; + +beforeEach(() => { + window.libraryMode = false; + window.publicKinds = ['video', 'audio', 'document']; + window.publicLicenses = [1, 2, 3]; +}); + +describe('CatalogFilterPanelContent', () => { + it('renders all filter components', () => { + renderComponent(); + + expect(screen.getByLabelText('Keywords')).toBeInTheDocument(); + expect(screen.getByLabelText('Licenses')).toBeInTheDocument(); + expect(screen.getByLabelText('Formats')).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: 'Starred' })).toBeInTheDocument(); + expect(screen.getByText('Display only channels with')).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: 'Resources for coaches' })).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: 'Captions or subtitles' })).toBeInTheDocument(); + expect(screen.getByText('Frequently asked questions')).toBeInTheDocument(); + expect(screen.getByRole('img')).toBeInTheDocument(); + }); + + it('renders keyword search input', () => { + renderComponent(); + expect(screen.getByLabelText('Keywords')).toBeInTheDocument(); + }); + + it('hides license filter in library mode', () => { + window.libraryMode = true; + renderComponent(); + expect(screen.queryByLabelText('Licenses')).not.toBeInTheDocument(); + }); + + it('shows license filter in non-library mode', () => { + window.libraryMode = false; + renderComponent(); + expect(screen.getByLabelText('Licenses')).toBeInTheDocument(); + }); + + it('renders help tooltip for coach resources', () => { + renderComponent(); + expect(screen.getByRole('checkbox', { name: 'Resources for coaches' })).toBeInTheDocument(); + }); + + it('renders footer with copyright', () => { + renderComponent(); + const currentYear = new Date().getFullYear(); + expect(screen.getByText(`© ${currentYear} Learning Equality`)).toBeInTheDocument(); + }); + + it('renders FAQ link with external icon', () => { + renderComponent(); + const faqLink = screen.getByText('Frequently asked questions'); + expect(faqLink).toBeInTheDocument(); + }); + + it('renders language filter', () => { + renderComponent(); + expect(screen.getByText('Languages')).toBeInTheDocument(); + }); +}); diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogFilters.spec.js b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogFilters.spec.js index 22acb35886..1703435b6e 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogFilters.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/catalogFilters.spec.js @@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'; import { factory } from '../../../store'; import router from '../../../router'; import CatalogFilters from '../CatalogFilters'; +import CatalogFilterPanelContent from '../components/CatalogFilterPanelContent.vue'; const store = factory(); @@ -24,25 +25,44 @@ describe('catalogFilters', () => { }); describe('keywords', () => { - it('should call setKeywords when keywords change', () => { - const setKeywordsMock = jest.fn(); - const setKeywords = () => { - return () => { - setKeywordsMock(); - }; - }; - wrapper = makeWrapper({ setKeywords }); - const keywords = wrapper.find('[data-test="keywords"]'); - keywords.element.value = 'test'; - keywords.trigger('input'); - expect(setKeywordsMock).toHaveBeenCalled(); + it('should update keywords data when updateKeywords is called', async () => { + wrapper = mount(CatalogFilterPanelContent, { + sync: false, + router, + store, + }); + + await wrapper.setData({ keywordInput: 'initial value' }); + + wrapper.vm.updateKeywords(); + + expect(wrapper.vm.keywords).toBe('initial value'); + }); + + it('keywordInput should sync with route query parameters on mount', async () => { + const keywords = 'route keywords'; + await router.push({ query: { keywords } }); + + wrapper = mount(CatalogFilterPanelContent, { + sync: false, + router, + store, + }); + + expect(wrapper.vm.keywordInput).toBe(keywords); }); - it('keywordInput should stay in sync with query param', () => { - const keywords = 'testing new keyword'; - router.push({ query: { keywords } }); - wrapper.vm.$nextTick(() => { - expect(wrapper.vm.keywordInput).toBe(keywords); + + it('keywordInput should update when route query parameters change', async () => { + wrapper = mount(CatalogFilterPanelContent, { + sync: false, + router, + store, }); + + const keywords = 'new route keywords'; + await router.push({ query: { keywords } }); + + expect(wrapper.vm.keywordInput).toBe(keywords); }); }); }); diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/components/CatalogFilterPanelContent.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/components/CatalogFilterPanelContent.vue new file mode 100644 index 0000000000..f636d3e275 --- /dev/null +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/components/CatalogFilterPanelContent.vue @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + {{ $tr('includesLabel') }} + + + + + + + + + + + + + + + + + + + + + + diff --git a/contentcuration/contentcuration/frontend/channelList/views/ChannelListIndex.vue b/contentcuration/contentcuration/frontend/channelList/views/ChannelListIndex.vue index e7d563d342..c45f88b571 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/ChannelListIndex.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/ChannelListIndex.vue @@ -74,6 +74,7 @@ >