Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,171 +1,259 @@
import { mount } from '@vue/test-utils';
import TrashModal from '../TrashModal';
import { render, screen, waitFor, configure } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import VueRouter from 'vue-router';

import { factory } from '../../../store';
import router from '../../../router';
import { RouteNames } from '../../../constants';
import TrashModal from '../TrashModal';

const store = factory();

const TRASH_ID = 'trash-root-id';

const testChildren = [
{
id: 'test1',
title: 'Item',
kind: 'video',
modified: new Date(2020, 1, 20),
},
{
id: 'test2',
title: 'Item',
kind: 'audio',
modified: new Date(2020, 2, 1),
},
{
id: 'test3',
title: 'Topic',
kind: 'topic',
modified: new Date(2020, 1, 1),
},
{ id: 'test1', title: 'Item', kind: 'video', modified: new Date(2020, 1, 20) },
{ id: 'test2', title: 'Item', kind: 'audio', modified: new Date(2020, 2, 1) },
{ id: 'test3', title: 'Topic', kind: 'topic', modified: new Date(2020, 1, 1) },
];

function makeWrapper(items) {
const loadContentNodes = jest.spyOn(TrashModal.methods, 'loadContentNodes');
loadContentNodes.mockImplementation(() => Promise.resolve());
const loadAncestors = jest.spyOn(TrashModal.methods, 'loadAncestors');
loadAncestors.mockImplementation(() => Promise.resolve());
const loadChildren = jest.spyOn(TrashModal.methods, 'loadChildren');
loadChildren.mockImplementation(() => Promise.resolve({ more: null, results: [] }));

const wrapper = mount(TrashModal, {
store,
router,
computed: {
currentChannel() {
return {
id: 'current channel',
};
},
trashId() {
return 'trash';
},
items() {
return items || testChildren;
async function makeWrapper(items = testChildren, isLoading = false) {
const loadContentNodesSpy = jest
.spyOn(TrashModal.methods, 'loadContentNodes')
.mockResolvedValue({});
jest.spyOn(TrashModal.methods, 'loadAncestors').mockResolvedValue();
jest.spyOn(TrashModal.methods, 'removeContentNodes').mockResolvedValue();
const loadNodesSpy = jest.spyOn(TrashModal.methods, 'loadNodes');

if (isLoading) {
jest.spyOn(TrashModal.methods, 'loadChildren').mockReturnValue(new Promise(() => {}));
} else {
jest.spyOn(TrashModal.methods, 'loadChildren').mockResolvedValue({
more: items === testChildren ? null : { parent: TRASH_ID, page: 2 },
results: [],
});
}

const router = new VueRouter({
routes: [
{ name: RouteNames.TRASH, path: '/:nodeId/trash', component: TrashModal },
{
name: RouteNames.TREE_VIEW,
path: '/:nodeId/:detailNodeId?',
component: { template: '<div>Tree</div>' },
},
offline() {
return false;
],
});

router.replace({ name: RouteNames.TRASH, params: { nodeId: 'test' } }).catch(() => {});

const routerPush = jest.spyOn(router, 'push').mockResolvedValue();

const utils = render(
TrashModal,
{
store,
router,
stubs: {
ResourceDrawer: true,
OfflineText: true,
},
backLink() {
return {
name: 'TEST_PARENT',
};
computed: {
currentChannel: () => ({ id: 'test-channel-id' }),
trashId: () => TRASH_ID,
items: () => items,
offline: () => false,
backLink: () => ({ name: RouteNames.TREE_VIEW, params: { nodeId: 'test' } }),
getSelectedTopicAndResourceCountText: () => ids => `${ids.length} items selected`,
counts: () => ({ topicCount: 0, resourceCount: 0 }),
},
},
stubs: {
ResourceDrawer: true,
OfflineText: true,
localVue => {
localVue.use(VueRouter);
},
});
);

return [wrapper, { loadContentNodes, loadAncestors, loadChildren }];
if (!isLoading) {
await waitFor(() => {
expect(screen.queryByTestId('loading')).not.toBeInTheDocument();
});
}

const user = userEvent.setup();
return { ...utils, routerPush, user, loadNodesSpy, loadContentNodesSpy };
}

describe('trashModal', () => {
let wrapper;
describe('TrashModal', () => {
beforeAll(() => configure({ testIdAttribute: 'data-test' }));
afterAll(() => configure({ testIdAttribute: 'data-testid' }));

beforeEach(async () => {
[wrapper] = makeWrapper();
router.replace({ name: RouteNames.TRASH, params: { nodeId: 'test' } }).catch(() => {});
await wrapper.setData({ loading: false });
beforeEach(() => {
jest.restoreAllMocks();
});

describe('on load', () => {
it('should show loading indicator if content is loading', async () => {
await wrapper.setData({ loading: true });
expect(wrapper.findComponent('[data-test="loading"]').exists()).toBe(true);
it('shows a loading indicator while content is loading', async () => {
await makeWrapper(testChildren, true);
expect(screen.getByTestId('loading')).toBeInTheDocument();
});

it('should show empty text if there are no items', async () => {
const [emptyWrapper] = makeWrapper([]);
await emptyWrapper.setData({ loading: false });
expect(emptyWrapper.findComponent('[data-test="empty"]').exists()).toBe(true);
it('shows empty text when trash has no items', async () => {
await makeWrapper([]);
expect(screen.getByTestId('empty')).toBeInTheDocument();
});

it('should show items in list', () => {
expect(wrapper.findComponent('[data-test="list"]').exists()).toBe(true);
it('shows the item list when trash has items', async () => {
await makeWrapper();
expect(screen.getByTestId('list')).toBeInTheDocument();
});
});

describe('on topic tree selection', () => {
it('clicking item should set previewNodeId', async () => {
await wrapper.findComponent('[data-test="item"]').trigger('click');
expect(wrapper.vm.previewNodeId).toBe(testChildren[0].id);
});
describe('on item selection', () => {
it('checking an item enables the Delete and Restore buttons', async () => {
const { user } = await makeWrapper();

it('checking item in list should add the item ID to the selected array', () => {
wrapper
.findComponent('[data-test="checkbox"]')
.find('input[type="checkbox"]')
.element.click();
expect(wrapper.vm.selected).toEqual(['test1']);
expect(screen.getByTestId('delete')).toBeDisabled();
expect(screen.getByTestId('restore')).toBeDisabled();

await user.click(screen.getAllByRole('checkbox')[1]);

await waitFor(() => {
expect(screen.getByTestId('delete')).toBeEnabled();
expect(screen.getByTestId('restore')).toBeEnabled();
});
});

it('checking select all checkbox should check all items', () => {
wrapper.findComponent('[data-test="selectall"]').vm.$emit('input', true);
expect(wrapper.vm.selected).toEqual(testChildren.map(c => c.id));
it('checking the select-all checkbox checks all items', async () => {
const { user } = await makeWrapper();
await user.click(screen.getByTestId('selectall'));

await waitFor(() => {
screen
.getAllByRole('checkbox')
.slice(1)
.forEach(cb => {
expect(cb).toBeChecked();
});
});
});
});

describe('on close', () => {
it('clicking close button should go back to parent route', async () => {
await wrapper.findComponent('[data-test="close"]').trigger('click');
expect(wrapper.vm.$route.name).toBe('TEST_PARENT');
it('clicking the close button navigates back to the tree view', async () => {
const { routerPush, user } = await makeWrapper();

const closeButton = await screen.findByTestId('close');
await user.click(closeButton);

await waitFor(() => {
expect(routerPush).toHaveBeenCalledWith(
expect.objectContaining({ name: RouteNames.TREE_VIEW }),
);
});
});
});

describe('on delete', () => {
it('DELETE button should be disabled if no items are selected', () => {
expect(wrapper.findComponent('[data-test="delete"]').vm.disabled).toBe(true);
it('Delete button is disabled when no items are selected', async () => {
await makeWrapper();
expect(screen.getByTestId('delete')).toBeDisabled();
});

it('clicking DELETE button should open delete confirmation dialog', async () => {
await wrapper.setData({ selected: testChildren.map(c => c.id) });
await wrapper.findComponent('[data-test="delete"]').trigger('click');
expect(wrapper.vm.showConfirmationDialog).toBe(true);
it('clicking Delete opens a confirmation dialog', async () => {
const { user } = await makeWrapper();
await user.click(screen.getByTestId('selectall'));
await user.click(screen.getByTestId('delete'));

expect(await screen.findByText(/You cannot undo this action/i)).toBeInTheDocument();
});

it('clicking CLOSE on delete confirmation dialog should close the dialog', async () => {
await wrapper.setData({ showConfirmationDialog: true });
await wrapper.findComponent('[data-test="deleteconfirm"]').vm.$emit('cancel');
expect(wrapper.vm.showConfirmationDialog).toBe(false);
it('clicking Cancel in the confirmation dialog closes it', async () => {
const { user } = await makeWrapper();
await user.click(screen.getByTestId('selectall'));
await user.click(screen.getByTestId('delete'));
await screen.findByText(/You cannot undo this action/i);

await user.click(screen.getByRole('button', { name: /Cancel/i }));

await waitFor(() => {
expect(screen.queryByText(/You cannot undo this action/i)).not.toBeInTheDocument();
});
});

it('clicking DELETE PERMANENTLY on delete confirmation dialog should trigger deletion', async () => {
const selected = testChildren.map(c => c.id);
const deleteContentNodes = jest.spyOn(wrapper.vm, 'deleteContentNodes');
deleteContentNodes.mockImplementation(() => Promise.resolve());
await wrapper.setData({ selected, showConfirmationDialog: true });
await wrapper.findComponent('[data-test="deleteconfirm"]').vm.$emit('submit');
expect(deleteContentNodes).toHaveBeenCalledWith(selected);
it('clicking Delete permanently calls deleteContentNodes with the selected IDs', async () => {
const deleteContentNodes = jest
.spyOn(TrashModal.methods, 'deleteContentNodes')
.mockResolvedValue();

const { user } = await makeWrapper();

await user.click(screen.getByTestId('selectall'));
await user.click(screen.getByTestId('delete'));
await user.click(await screen.findByRole('button', { name: /Delete permanently/i }));

await waitFor(() => {
expect(deleteContentNodes).toHaveBeenCalledWith(testChildren.map(c => c.id));
});
});

it('successful deletion triggers snackbar and reloads nodes', async () => {
jest.spyOn(TrashModal.methods, 'deleteContentNodes').mockResolvedValue();
const dispatchSpy = jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());

const { user, loadNodesSpy } = await makeWrapper();
await user.click(screen.getByTestId('selectall'));
await user.click(screen.getByTestId('delete'));
await user.click(await screen.findByRole('button', { name: /Delete permanently/i }));

await waitFor(() => {
expect(dispatchSpy).toHaveBeenCalledWith('showSnackbar', {
text: 'Permanently deleted',
});
expect(loadNodesSpy).toHaveBeenCalled();
});
});
});

describe('on restore', () => {
it('RESTORE button should be disabled if no items are selected', () => {
expect(wrapper.findComponent('[data-test="restore"]').vm.disabled).toBe(true);
it('Restore button is disabled when no items are selected', async () => {
await makeWrapper();
expect(screen.getByTestId('restore')).toBeDisabled();
});

it('RESTORE should set moveModalOpen to true', async () => {
const selected = testChildren.map(c => c.id);
await wrapper.setData({ selected });
await wrapper.findComponent('[data-test="restore"]').trigger('click');
expect(wrapper.vm.moveModalOpen).toBe(true);
it('clicking Restore opens the MoveModal', async () => {
const { user } = await makeWrapper();
await user.click(screen.getByTestId('selectall'));
await user.click(screen.getByTestId('restore'));
expect(await screen.findByRole('button', { name: /Move here/i })).toBeInTheDocument();
});
});

describe('selection count', () => {
it('shows selected item count in the bottom bar', async () => {
const { user } = await makeWrapper();

expect(screen.queryByText(/items selected/i)).not.toBeInTheDocument();

await user.click(screen.getByTestId('selectall'));

expect(await screen.findByText(`${testChildren.length} items selected`)).toBeInTheDocument();
});
});

describe('pagination', () => {
it('shows a Show more button when there is more paginated content', async () => {
await makeWrapper([testChildren[0]]);
expect(await screen.findByRole('button', { name: /Show more/i })).toBeInTheDocument();
});

it('clicking Show more calls loadContentNodes with pagination params', async () => {
const { user, loadContentNodesSpy } = await makeWrapper([testChildren[0]]);
const showMoreBtn = await screen.findByRole('button', { name: /Show more/i });

await user.click(showMoreBtn);

it('moveNoves should clear selected and previewNodeId', async () => {
const moveContentNodes = jest.spyOn(wrapper.vm, 'moveContentNodes');
moveContentNodes.mockImplementation(() => Promise.resolve());
wrapper.vm.moveNodes();
expect(wrapper.vm.selected).toEqual([]);
expect(wrapper.vm.previewNodeId).toBe(null);
await waitFor(() => {
expect(loadContentNodesSpy).toHaveBeenCalledWith({ parent: TRASH_ID, page: 2 });
});
});
});
});
Loading