diff --git a/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/ChannelSelectionList.vue b/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/ChannelSelectionList.vue
index 97f36f8e1d..78d274943d 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/ChannelSelectionList.vue
+++ b/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/ChannelSelectionList.vue
@@ -37,13 +37,13 @@
diff --git a/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/__tests__/channelSelectionList.spec.js b/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/__tests__/channelSelectionList.spec.js
index a7befd7c43..f7105f1934 100644
--- a/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/__tests__/channelSelectionList.spec.js
+++ b/contentcuration/contentcuration/frontend/channelList/views/ChannelSet/__tests__/channelSelectionList.spec.js
@@ -1,9 +1,12 @@
-import { mount } from '@vue/test-utils';
+import { render, screen, within } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+import VueRouter from 'vue-router';
import { Store } from 'vuex';
import ChannelSelectionList from '../ChannelSelectionList';
import { ChannelListTypes } from 'shared/constants';
const searchWord = 'search test';
+
const editChannel = {
id: 'editchannel',
name: searchWord,
@@ -14,7 +17,7 @@ const editChannel = {
const editChannel2 = {
id: 'editchannel2',
- name: '',
+ name: 'Another Channel',
description: '',
edit: true,
published: true,
@@ -22,100 +25,154 @@ const editChannel2 = {
const publicChannel = {
id: 'publicchannel',
+ name: 'Public Channel',
public: true,
published: true,
};
-const getters = {
- channels: jest.fn(() => [editChannel, editChannel2, publicChannel]),
- getChannel: jest.fn(() => () => editChannel),
-};
-
-const actions = {
+const mockActions = {
loadChannelList: jest.fn(() => Promise.resolve()),
};
-const store = new Store({
- modules: {
- channel: {
- namespaced: true,
- getters,
- actions,
+const makeStore = () =>
+ new Store({
+ modules: {
+ channel: {
+ namespaced: true,
+ state: {},
+ getters: {
+ channels: () => [editChannel, editChannel2, publicChannel],
+ getChannel: () => id => [editChannel, editChannel2, publicChannel].find(c => c.id === id),
+ },
+ actions: mockActions,
+ mutations: {
+ ADD_CHANNELS() {},
+ },
+ },
},
- },
-});
+ });
-function makeWrapper() {
- const loadChannelList = jest.spyOn(ChannelSelectionList.methods, 'loadChannelList');
- loadChannelList.mockImplementation(() => Promise.resolve());
+const renderComponent = (props = {}) => {
+ const router = new VueRouter({
+ routes: [{ path: '/', name: 'Home' }],
+ });
- const wrapper = mount(ChannelSelectionList, {
- propsData: {
+ return render(ChannelSelectionList, {
+ router,
+ store: makeStore(),
+ props: {
listType: ChannelListTypes.EDITABLE,
+ value: [], // Default to empty
+ ...props, // Allow overriding props for specific tests
},
- computed: {
- channels() {
- return [editChannel, editChannel2, publicChannel];
- },
- },
- store,
});
+};
- return [wrapper, { loadChannelList }];
-}
-
-describe('channelSelectionList', () => {
- let wrapper, mocks;
-
+describe('ChannelSelectionList', () => {
beforeEach(() => {
- [wrapper, mocks] = makeWrapper();
+ jest.clearAllMocks();
});
- afterEach(() => {
- mocks.loadChannelList.mockRestore();
- });
+ it('renders a list of editable channels and hides non-editable ones', async () => {
+ await renderComponent();
+
+ // Specific wait avoids wrapping the whole block in waitFor
+ expect(await screen.findByLabelText(/search for a channel/i)).toBeInTheDocument();
- it('should show the correct channels based on listType', async () => {
- await wrapper.setData({ loading: false });
- expect(wrapper.vm.listChannels.find(c => c.id === editChannel.id)).toBeTruthy();
- expect(wrapper.vm.listChannels.find(c => c.id === editChannel2.id)).toBeTruthy();
- expect(wrapper.vm.listChannels.find(c => c.id === publicChannel.id)).toBeFalsy();
+ expect(screen.getByText(editChannel.name)).toBeInTheDocument();
+ expect(screen.getByText(editChannel2.name)).toBeInTheDocument();
+ expect(screen.queryByText(publicChannel.name)).not.toBeInTheDocument();
});
- it('should select channels when the channel has been checked', async () => {
- await wrapper.setData({ loading: false });
- await wrapper.findComponent(`[data-test="checkbox-${editChannel.id}"]`).trigger('click');
+ it('filters the channel list when the user types in the search box', async () => {
+ const user = userEvent.setup();
+ await renderComponent();
+
+ // Wait for data load
+ expect(await screen.findByText(editChannel.name)).toBeInTheDocument();
+ expect(screen.getByText(editChannel2.name)).toBeInTheDocument();
+
+ const searchInput = screen.getByLabelText(/search for a channel/i);
+ await user.clear(searchInput);
+ await user.type(searchInput, editChannel.name);
- expect(wrapper.emitted('input')[0][0]).toEqual([editChannel.id]);
+ // Verify filter happened
+ expect(await screen.findByText(editChannel.name)).toBeInTheDocument();
+ expect(screen.queryByText(editChannel2.name)).not.toBeInTheDocument();
});
- it('should deselect channels when the channel has been unchecked', async () => {
- await wrapper.setData({ loading: false });
- await wrapper.findComponent(`[data-test="checkbox-${editChannel.id}"]`).trigger('click'); // Check the channel
- await wrapper.findComponent(`[data-test="checkbox-${editChannel.id}"]`).trigger('click'); // Uncheck the channel
+ it('selects a channel when the user clicks the checkbox', async () => {
+ const user = userEvent.setup();
+ const { emitted } = await renderComponent();
- expect(wrapper.emitted('input')[0].length).toEqual(1); // Only one event should be emitted (corresponding to the initial check)
- expect(wrapper.emitted('input')[0][0]).toEqual([editChannel.id]); // The initial check event should be emitted
+ await screen.findByText(editChannel.name);
+
+ // Using getByTestId because the component doesn't expose unique
+ // accessible roles for individual channel checkboxes
+ const checkboxRow = screen.getByTestId(`checkbox-${editChannel.id}`);
+
+ // Find the checkbox strictly within this row
+ const checkbox = within(checkboxRow).getByRole('checkbox');
+
+ await user.click(checkbox);
+
+ expect(emitted()).toHaveProperty('input');
+ expect(emitted().input).toHaveLength(1);
+ expect(emitted().input[0][0]).toEqual([editChannel.id]);
});
- it('should filter channels based on the search text', async () => {
- await wrapper.setData({ loading: false, search: searchWord });
- expect(wrapper.vm.listChannels.find(c => c.id === editChannel.id)).toBeTruthy();
- expect(wrapper.vm.listChannels.find(c => c.id === editChannel2.id)).toBeFalsy();
+ it('deselects a channel when the user clicks the checkbox of an already selected channel', async () => {
+ const user = userEvent.setup();
+
+ // Initialize with the channel already selected
+ const { emitted } = await renderComponent({ value: [editChannel.id] });
+
+ await screen.findByText(editChannel.name);
+
+ // Using getByTestId because the component doesn't expose unique
+ // accessible roles for individual channel checkboxes
+ const checkboxRow = screen.getByTestId(`checkbox-${editChannel.id}`);
+ const checkbox = within(checkboxRow).getByRole('checkbox');
+
+ // Click the checkbox to deselect
+ await user.click(checkbox);
+
+ expect(emitted()).toHaveProperty('input');
+ expect(emitted().input).toHaveLength(1);
+ expect(emitted().input[0][0]).toEqual([]);
});
- it('should select channels when the channel card has been clicked', async () => {
- await wrapper.setData({ loading: false });
- await wrapper.findComponent(`[data-test="channel-item-${editChannel.id}"]`).trigger('click');
- expect(wrapper.emitted('input')[0][0]).toEqual([editChannel.id]);
+ it('selects a channel when the user clicks the channel card', async () => {
+ const user = userEvent.setup();
+ const { emitted } = await renderComponent();
+
+ await screen.findByText(editChannel.name);
+
+ // Using getByTestId because the component doesn't expose accessible
+ // roles for channel cards
+ const card = screen.getByTestId(`channel-item-${editChannel.id}`);
+ await user.click(card);
+
+ expect(emitted()).toHaveProperty('input');
+ expect(emitted().input).toHaveLength(1);
+ expect(emitted().input[0][0]).toEqual([editChannel.id]);
});
- it('should deselect channels when the channel card has been clicked', async () => {
- await wrapper.setData({ loading: false });
- await wrapper.findComponent(`[data-test="channel-item-${editChannel.id}"]`).trigger('click'); // Check the channel
- await wrapper.findComponent(`[data-test="channel-item-${editChannel.id}"]`).trigger('click'); // Uncheck the channel
+ it('deselects a channel when the user clicks a selected channel card', async () => {
+ const user = userEvent.setup();
+
+ // Initialize with the channel already selected
+ const { emitted } = await renderComponent({ value: [editChannel.id] });
+
+ await screen.findByText(editChannel.name);
+
+ // Using getByTestId because the component doesn't expose accessible
+ // roles for channel cards
+ const card = screen.getByTestId(`channel-item-${editChannel.id}`);
+ await user.click(card);
- expect(wrapper.emitted('input')[0].length).toEqual(1); // Only one event should be emitted (corresponding to the initial check)
- expect(wrapper.emitted('input')[0][0]).toEqual([editChannel.id]); // The initial check event should be emitted
+ expect(emitted()).toHaveProperty('input');
+ expect(emitted().input).toHaveLength(1);
+ expect(emitted().input[0][0]).toEqual([]);
});
});