Fix most jest tests

This commit is contained in:
Ramon Wenger 2022-10-26 14:20:57 +02:00
parent 243422cf9a
commit a0fd6fddd1
10 changed files with 1322 additions and 5661 deletions

View File

@ -1,10 +1,9 @@
import module from '../../../fixtures/module.minimal'; import module from '../../../fixtures/module.minimal';
import { getMinimalMe } from '../../../support/helpers'; import {getMinimalMe} from '../../../support/helpers';
import { hasOperationName } from '../../../support/graphql'; import {hasOperationName} from '../../../support/graphql';
let snapshotTitle; let snapshotTitle;
let deleteSuccess; let deleteSuccess;
let page;
const moduleWithSnapshots = { const moduleWithSnapshots = {
...module, ...module,
@ -28,68 +27,71 @@ const moduleWithSnapshots = {
], ],
}; };
const mockDeleteSnapshot = (success) => { // const mockDeleteSnapshot = (success) => {
cy.intercept('POST', '/api/graphql', (req) => { // cy.intercept('POST', '/api/graphql', (req) => {
if (hasOperationName(req, 'DeleteSnapshot')) { // if (hasOperationName(req, 'DeleteSnapshot')) {
let result; // let result;
if (success) { // if (success) {
result = { // result = {
message: 'yay!', // message: 'yay!',
__typename: 'Success', // __typename: 'Success',
}; // };
} else { // } else {
result = { // result = {
reason: 'Not the owner', // reason: 'Not the owner',
__typename: 'NotOwner', // __typename: 'NotOwner',
}; // };
} // }
req.reply({ // req.reply({
data: { // data: {
deleteSnapshot: { // deleteSnapshot: {
result, // result,
}, // },
}, // },
}); // });
} //
}); // }
}; // });
// };
const mockUpdateSnapshot = (title) => { // const mockUpdateSnapshot = (title) => {
cy.intercept('POST', '/api/graphql', (req) => { // cy.intercept('POST', '/api/graphql', (req) => {
if (hasOperationName(req, 'UpdateSnapshot')) { // if (hasOperationName(req, 'UpdateSnapshot')) {
let snapshot; // let snapshot;
if (title) { // if (title) {
snapshot = { // snapshot = {
__typename: 'SnapshotNode', // __typename: 'SnapshotNode',
id: 'U25hcHNob3ROb2RlOjQ=', // id: 'U25hcHNob3ROb2RlOjQ=',
title, // title,
}; // };
} else { // } else {
snapshot = { // snapshot = {
__typename: 'NotOwner', // __typename: 'NotOwner',
reason: 'Not the owner', // reason: 'Not the owner',
}; // };
} // }
req.reply({ // req.reply({
data: { // data: {
updateSnapshot: { // updateSnapshot: {
snapshot, // snapshot,
}, // },
}, // },
}); // });
} // }
}); // });
}; //
// };
// wait for the specified amount of requests in the test, so they don't spill over to the next test // wait for the specified amount of requests in the test, so they don't spill over to the next test
const waitForNRequests = (n) => { const waitNTimes = (n) => {
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
cy.wait('@graphqlRequest'); cy.wait('@graphqlRequest');
} }
}; };
describe('Snapshot', () => { describe('Snapshot', () => {
const operations = (isTeacher) => ({ const operations = isTeacher => ({
operations: { operations: {
UpdateSnapshot: { UpdateSnapshot: {
updateSnapshot: { updateSnapshot: {
@ -127,22 +129,21 @@ describe('Snapshot', () => {
}; };
} }
return result; return result;
}, },
}, },
}, },
MeQuery: getMinimalMe({ isTeacher }), MeQuery: getMinimalMe({isTeacher}),
ModuleDetailsQuery: { ModuleDetailsQuery: {
module, module,
}, },
CreateSnapshot: { CreateSnapshot: {
createSnapshot: { createSnapshot: {
snapshot: { snapshot: {
id: 'snapshot-id', id: '',
title: 'Mi Snapshot', title: '',
created: '2022-01-01', created: '',
creator: 'me', creator: '',
shared: false,
mine: true,
}, },
success: true, success: true,
}, },
@ -178,7 +179,6 @@ describe('Snapshot', () => {
}, },
SnapshotDetail: { SnapshotDetail: {
snapshot: { snapshot: {
title: 'Shared snapshot',
chapters: [], chapters: [],
module: {}, module: {},
}, },
@ -194,7 +194,6 @@ describe('Snapshot', () => {
beforeEach(() => { beforeEach(() => {
snapshotTitle = false; snapshotTitle = false;
deleteSuccess = false; deleteSuccess = false;
page = moduleWithSnapshots;
cy.setup(); cy.setup();
}); });
@ -202,7 +201,7 @@ describe('Snapshot', () => {
cy.mockGraphqlOps(operations(true)); cy.mockGraphqlOps(operations(true));
cy.visit('module/miteinander-reden/'); cy.visit('module/miteinander-reden/');
cy.getByDataCy('snapshot-menu').should('be.visible'); cy.getByDataCy('snapshot-menu').should('be.visible');
waitForNRequests(4); waitNTimes(4);
}); });
it('Menu is not visible for student', () => { it('Menu is not visible for student', () => {
@ -211,27 +210,19 @@ describe('Snapshot', () => {
cy.getByDataCy('module-title').should('be.visible'); cy.getByDataCy('module-title').should('be.visible');
cy.getByDataCy('snapshot-menu').should('not.exist'); cy.getByDataCy('snapshot-menu').should('not.exist');
waitForNRequests(3); waitNTimes(3);
}); });
it('Creates Snapshot', () => { it('Creates Snapshot', () => {
cy.mockGraphqlOps(operations(true)); cy.mockGraphqlOps(operations(true));
cy.visit('module/miteinander-reden/snapshots'); cy.visit('module/miteinander-reden/');
cy.getByDataCy('snapshot-list')
.should('exist')
.within(() => {
cy.get('.snapshots__snapshot').should('have.length', 1);
});
cy.getByDataCy('back-link').click();
cy.getByDataCy('module-snapshots-button').click(); cy.getByDataCy('module-snapshots-button').click();
cy.getByDataCy('create-snapshot-button').click(); cy.getByDataCy('create-snapshot-button').click();
cy.getByDataCy('show-all-snapshots-button').click(); cy.getByDataCy('show-all-snapshots-button').click();
cy.getByDataCy('snapshot-list') cy.getByDataCy('snapshot-list').should('exist').within(() => {
.should('exist') cy.get('.snapshots__snapshot').should('have.length', 1);
.within(() => { });
cy.get('.snapshots__snapshot').should('have.length', 2); waitNTimes(7);
});
waitForNRequests(7);
}); });
it('Applies Snapshot', () => { it('Applies Snapshot', () => {
@ -243,7 +234,7 @@ describe('Snapshot', () => {
cy.getByDataCy('module-title').should('exist'); cy.getByDataCy('module-title').should('exist');
cy.getByDataCy('snapshot-header').should('not.exist'); cy.getByDataCy('snapshot-header').should('not.exist');
waitForNRequests(8); waitNTimes(9);
}); });
it('Renames Snapshot', () => { it('Renames Snapshot', () => {
@ -252,12 +243,12 @@ describe('Snapshot', () => {
snapshotTitle = newTitle; snapshotTitle = newTitle;
// mockUpdateSnapshot(newTitle); // mockUpdateSnapshot(newTitle);
cy.visit('module/miteinander-reden/snapshots'); cy.visit('module/miteinander-reden/snapshots');
cy.getByDataCy('snapshot-link').should('contain.text', 'Old Title'); cy.getByDataCy('snapshot-link').should('have.text', 'Old Title');
cy.getByDataCy('rename-snapshot-button').click(); cy.getByDataCy('rename-snapshot-button').click();
cy.getByDataCy('edit-name-input').clear().type(newTitle); cy.getByDataCy('edit-name-input').clear().type(newTitle);
cy.getByDataCy('modal-save-button').click(); cy.getByDataCy('modal-save-button').click();
cy.getByDataCy('snapshot-link').should('contain.text', 'New Title'); cy.getByDataCy('snapshot-link').should('have.text', 'New Title');
waitForNRequests(5); waitNTimes(5);
}); });
it('Deletes Snapshot', () => { it('Deletes Snapshot', () => {
@ -269,7 +260,7 @@ describe('Snapshot', () => {
cy.getByDataCy('delete-snapshot-button').click(); cy.getByDataCy('delete-snapshot-button').click();
cy.getByDataCy('modal-save-button').click(); cy.getByDataCy('modal-save-button').click();
cy.getByDataCy('snapshot-entry').should('have.length', 0); cy.getByDataCy('snapshot-entry').should('have.length', 0);
waitForNRequests(6); waitNTimes(6);
}); });
it('Displays the Snapshot list correcly', () => { it('Displays the Snapshot list correcly', () => {
@ -278,16 +269,13 @@ describe('Snapshot', () => {
cy.getByDataCy('snapshot-entry').should('have.length', 1); cy.getByDataCy('snapshot-entry').should('have.length', 1);
cy.getByDataCy('delete-snapshot-button').should('exist'); cy.getByDataCy('delete-snapshot-button').should('exist');
cy.getByDataCy('rename-snapshot-button').should('exist'); cy.getByDataCy('rename-snapshot-button').should('exist');
cy.getByDataCy('snapshot-link').should('contain.text', 'Old Title'); cy.getByDataCy('snapshot-link').should('have.text', 'Old Title');
cy.getByDataCy('team-snapshots-link').click(); cy.getByDataCy('team-snapshots-link').click();
cy.getByDataCy('snapshot-entry').should('have.length', 1); cy.getByDataCy('snapshot-entry').should('have.length', 1);
cy.getByDataCy('snapshot-link').should('contain.text', 'Shared snapshot'); cy.getByDataCy('snapshot-link').should('have.text', 'Shared snapshot');
cy.getByDataCy('delete-snapshot-button').should('not.exist'); cy.getByDataCy('delete-snapshot-button').should('not.exist');
cy.getByDataCy('rename-snapshot-button').should('not.exist'); cy.getByDataCy('rename-snapshot-button').should('not.exist');
cy.getByDataCy('snapshot-link').click(); waitNTimes(4);
cy.getByDataCy('module-title').should('contain.text', 'Shared snapshot');
waitForNRequests(5);
}); });
afterEach(() => {});
}); });

View File

@ -1,21 +1,42 @@
module.exports = { module.exports = {
moduleFileExtensions: ['js', 'jsx', 'ts', 'json', 'vue'], moduleFileExtensions: [
'js',
'jsx',
'ts',
'json',
'vue',
],
transform: { transform: {
'\\.(gql|graphql)$': 'jest-transform-graphql', '\\.(gql|graphql)$': '@graphql-tools/jest-transform',
'^.+\\.js$': 'babel-jest', '^.+\\.js$': 'babel-jest',
'^.+\\.ts$': 'babel-jest', '^.+\\.ts$': 'babel-jest',
'^.+\\.vue$': '@vue/vue2-jest', '^.+\\.vue$': '@vue/vue3-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
}, },
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'], modulePaths: [
transformIgnorePatterns: ['/node_modules/'], '<rootDir>/src',
'<rootDir>/node_modules',
],
transformIgnorePatterns: [
'/node_modules/',
],
moduleNameMapper: { moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1', '^@/(.*)$': '<rootDir>/src/$1',
'^gql/(.*)$': '<rootDir>/src/graphql/gql/$1', '^gql/(.*)$': '<rootDir>/src/graphql/gql/$1',
}, },
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], snapshotSerializers: [
'<rootDir>/node_modules/jest-serializer-vue',
],
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
testMatch: ['**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'], testEnvironmentOptions: {
testURL: 'http://localhost/', url: 'http://localhost/',
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], customExportConditions: ['node', 'node-addons'] // needed according to https://github.com/vuejs/vue-jest/issues/479
},
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)',
],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
}; };

6700
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -48,10 +48,11 @@
"@vue/apollo-option": "^4.0.0-alpha.16", "@vue/apollo-option": "^4.0.0-alpha.16",
"@vue/compat": "3.2.30", "@vue/compat": "3.2.30",
"@vue/compiler-sfc": "3.2.30", "@vue/compiler-sfc": "3.2.30",
"@vue/vue3-jest": "^27.0.0", "@vue/test-utils": "^2.2.0",
"@vue/vue3-jest": "^29.1.1",
"autoprefixer": "^10.4.12", "autoprefixer": "^10.4.12",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^27.5.1", "babel-jest": "^29.2.2",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"chalk": "^2.0.1", "chalk": "^2.0.1",
"copy-webpack-plugin": "^10.1.0", "copy-webpack-plugin": "^10.1.0",
@ -72,7 +73,8 @@
"graphql-tag": "^2.10.1", "graphql-tag": "^2.10.1",
"graphql-tools": "^8.2.5", "graphql-tools": "^8.2.5",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"jest": "^27.5.1", "jest": "^29.2.2",
"jest-environment-jsdom": "^29.2.2",
"jest-serializer-vue": "^2.0.2", "jest-serializer-vue": "^2.0.2",
"jest-transform-graphql": "^2.1.0", "jest-transform-graphql": "^2.1.0",
"jest-transform-stub": "^2.0.0", "jest-transform-stub": "^2.0.0",

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="filter-group"> <div class="filter-group">
<filter-entry <filter-entry
:text="title"
v-bind="$attrs" v-bind="$attrs"
:text="title"
:type="category" :type="category"
:category="category" :category="category"
:is-category="true" :is-category="true"

View File

@ -64,7 +64,6 @@
</template> </template>
<script> <script>
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql'; import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql'; import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
import LoadingButton from '@/components/LoadingButton'; import LoadingButton from '@/components/LoadingButton';

View File

@ -1,9 +1,9 @@
import { createLocalVue, mount } from '@vue/test-utils'; import {mount} from '@vue/test-utils';
import Checkbox from '@/components/ui/BaseInput'; import Checkbox from '@/components/ui/BaseInput';
const localVue = createLocalVue();
describe('Checkbox.vue', () => { describe('Checkbox.vue', () => {
it('should display the provided props', async () => { it('should display the provided props', async () => {
const props = { const props = {
label: 'Lonely label', label: 'Lonely label',
@ -11,8 +11,7 @@ describe('Checkbox.vue', () => {
item: null, item: null,
}; };
const wrapper = mount(Checkbox, { const wrapper = mount(Checkbox, {
propsData: props, props,
localVue,
}); });
const input = wrapper.find('[data-cy="base-input-input"]'); const input = wrapper.find('[data-cy="base-input-input"]');
@ -25,19 +24,19 @@ describe('Checkbox.vue', () => {
it.skip('should emit updated value', async () => { it.skip('should emit updated value', async () => {
// todo: failed after update from jest@25 to jest@27, probably already at jest@26 // todo: failed after update from jest@25 to jest@27, probably already at jest@26
const labelText = 'Lonely label'; const labelText = 'Lonely label';
const item = { name: 'bla' }; const item = {name: 'bla'};
const props = { const props = {
label: labelText, label: labelText,
checked: false, checked: false,
item, item,
}; };
const wrapper = mount(Checkbox, { const wrapper = mount(Checkbox, {
propsData: props, props,
localVue,
}); });
wrapper.element.click(); wrapper.element.click();
expect(wrapper.emitted()['input'][0]).toEqual([!props.checked, item]); expect(wrapper.emitted()['input'][0]).toEqual([!props.checked, item]);
}); });
}); });

View File

@ -1,5 +1,5 @@
import { createLocalVue, mount } from '@vue/test-utils'; import {mount} from '@vue/test-utils';
import { createMockClient } from 'mock-apollo-client'; import {createMockClient} from 'mock-apollo-client';
import SIDEBAR from '@/graphql/gql/local/sidebar.gql'; import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql'; import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
@ -11,9 +11,9 @@ import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQue
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget'; import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
// https://dev.to/n_tepluhina/testing-vue-apollo-2020-edition-2l2p // https://dev.to/n_tepluhina/testing-vue-apollo-2020-edition-2l2p
const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
const updateSettingsResponse = { const updateSettingsResponse = {
@ -85,7 +85,9 @@ const meQueryResponse = {
}, },
team: null, team: null,
isTeacher: true, isTeacher: true,
permissions: ['users.can_manage_school_class_content'], permissions: [
'users.can_manage_school_class_content',
],
onboardingVisited: true, onboardingVisited: true,
}, },
}, },
@ -104,14 +106,14 @@ const schoolClasses = [
{ {
id: 'abcd123', id: 'abcd123',
name: 'Hello', name: 'Hello',
}, }, {
{
id: 'xyz098', id: 'xyz098',
name: 'Kitty', name: 'Kitty',
}, },
]; ];
describe('ClassSelectionWidget.vue', () => { describe('ClassSelectionWidget.vue', () => {
let mockClient; let mockClient;
let apolloProvider; let apolloProvider;
let wrapper; let wrapper;
@ -125,6 +127,7 @@ describe('ClassSelectionWidget.vue', () => {
moduleDetailHandler: jest.fn().mockResolvedValueOnce(moduleDetailResponse), moduleDetailHandler: jest.fn().mockResolvedValueOnce(moduleDetailResponse),
}; };
const createComponent = () => { const createComponent = () => {
mockClient = createMockClient(); mockClient = createMockClient();
mockClient.cache.writeQuery = jest.fn(); mockClient.cache.writeQuery = jest.fn();
@ -141,7 +144,6 @@ describe('ClassSelectionWidget.vue', () => {
}); });
wrapper = mount(ClassSelectionWidget, { wrapper = mount(ClassSelectionWidget, {
localVue,
apolloProvider, apolloProvider,
mocks: { mocks: {
$route: { $route: {
@ -153,12 +155,15 @@ describe('ClassSelectionWidget.vue', () => {
}); });
wrapper.vm.me.schoolClass = schoolClasses; wrapper.vm.me.schoolClass = schoolClasses;
}; };
it('should delete the modules cache and query the current module on a class change', async () => { it('should delete the modules cache and query the current module on a class change', async () => {
createComponent(); createComponent();
wrapper.vm.me.selectedClass = { id: 'abcd123' }; wrapper.vm.me.selectedClass = {id: 'abcd123'};
wrapper.vm.updateSelectedClassAndHidePopover(schoolClasses[1]); wrapper.vm.updateSelectedClassAndHidePopover(schoolClasses[1]);
expect(requestHandlers.updateSettingsHandler).toHaveBeenCalledWith({ expect(requestHandlers.updateSettingsHandler).toHaveBeenCalledWith({
@ -175,6 +180,7 @@ describe('ClassSelectionWidget.vue', () => {
// expect(requestHandlers.mySchoolClassHandler).toHaveBeenCalled(); // expect(requestHandlers.mySchoolClassHandler).toHaveBeenCalled();
// current module is being refetched // current module is being refetched
// expect(requestHandlers.moduleDetailHandler).toHaveBeenCalledWith({slug}); // expect(requestHandlers.moduleDetailHandler).toHaveBeenCalledWith({slug});
}); });
afterEach(() => { afterEach(() => {
@ -182,4 +188,6 @@ describe('ClassSelectionWidget.vue', () => {
mockClient = null; mockClient = null;
apolloProvider = null; apolloProvider = null;
}); });
}); });

View File

@ -1,16 +1,16 @@
import { createLocalVue, shallowMount } from '@vue/test-utils'; import {shallowMount} from '@vue/test-utils';
import CommentInput from '@/components/rooms/CommentInput'; import CommentInput from '@/components/rooms/CommentInput';
const localVue = createLocalVue();
localVue.directive('auto-grow', {});
describe('CommentInput.vue', () => { describe('CommentInput.vue', () => {
it('should clear input after submit', async () => { it('should clear input after submit', async () => {
const inputText = 'some value'; const inputText = 'some value';
const wrapper = shallowMount(CommentInput, { const wrapper = shallowMount(CommentInput,{
localVue, directives: {
}); 'auto-grow': {}
}
} );
const textInput = wrapper.find('[data-cy="comment-textarea"]'); const textInput = wrapper.find('[data-cy="comment-textarea"]');
await textInput.setValue(inputText); await textInput.setValue(inputText);
@ -19,4 +19,5 @@ describe('CommentInput.vue', () => {
expect(wrapper.emitted()['submit'][0]).toEqual([inputText]); expect(wrapper.emitted()['submit'][0]).toEqual([inputText]);
}); });
}); });

View File

@ -1,18 +1,16 @@
import { createLocalVue, shallowMount } from '@vue/test-utils'; import {shallowMount} from '@vue/test-utils';
import TextForm from '@/components/content-forms/TextForm'; import TextForm from '@/components/content-forms/TextForm';
const localVue = createLocalVue();
describe('TextForm.vue', () => { describe('TextForm.vue', () => {
it('should emit user input and its index', async () => { it('should emit user input and its index', async () => {
const inputText = 'some value'; const inputText = 'some value';
const props = { const props = {
value: { text: '' }, value: { text: '' },
index: 1, index: 1
}; };
const wrapper = shallowMount(TextForm, { const wrapper = shallowMount(TextForm, {
propsData: props, props,
localVue,
}); });
const textInput = wrapper.find('[data-cy="text-form-input"]'); const textInput = wrapper.find('[data-cy="text-form-input"]');
@ -20,4 +18,5 @@ describe('TextForm.vue', () => {
expect(wrapper.emitted()['change-text'][0]).toEqual([inputText, props.index]); expect(wrapper.emitted()['change-text'][0]).toEqual([inputText, props.index]);
}); });
}); });