0
0
Fork 0

Added vitest

This commit is contained in:
Joan Sanchez 2022-12-22 13:46:43 +01:00
parent 618c85315d
commit df203ddb9b
33 changed files with 12553 additions and 10887 deletions

22926
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,48 @@
{
"name": "salix-front",
"version": "0.0.1",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "cypress run --browser chromium"
},
"dependencies": {
"@quasar/extras": "^1.15.8",
"axios": "^1.2.1",
"pinia": "^2.0.28",
"quasar": "^2.11.1",
"validator": "^13.7.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@pinia/testing": "^0.0.14",
"@quasar/app-vite": "^1.1.3",
"autoprefixer": "^10.4.13",
"cypress": "^12.2.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-vue": "^9.8.0",
"postcss": "^8.4.20",
"prettier": "^2.8.1"
},
"engines": {
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
"name": "salix-front",
"version": "0.0.1",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open",
"test:e2e:ci": "cypress run --browser chromium",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest",
"test:unit:ci": "vitest run"
},
"dependencies": {
"@quasar/extras": "^1.15.8",
"axios": "^1.2.1",
"pinia": "^2.0.28",
"quasar": "^2.11.1",
"validator": "^13.7.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vue-router-mock": "^0.1.9"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@pinia/testing": "^0.0.14",
"@quasar/app-vite": "^1.1.3",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.1.2",
"@vue/test-utils": "^2.0.0",
"autoprefixer": "^10.4.13",
"cypress": "^12.2.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-vue": "^9.8.0",
"postcss": "^8.4.20",
"prettier": "^2.8.1",
"vitest": "^0.15.0"
},
"engines": {
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}

7
quasar.extensions.json Normal file
View File

@ -0,0 +1,7 @@
{
"@quasar/testing-unit-vitest": {
"options": [
"scripts"
]
}
}

5
quasar.testing.json Normal file
View File

@ -0,0 +1,5 @@
{
"unit-vitest": {
"runnerCommand": "vitest run"
}
}

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import { createWrapper } from 'app/test/jest/jestHelpers';
import App from '../App.vue';
import { useSession } from 'src/composables/useSession';

View File

@ -8,6 +8,7 @@ const i18n = createI18n({
globalInjection: true,
messages,
missingWarn: false,
legacy: false,
});
export default boot(({ app }) => {

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper } from 'app/tests/jest/jestHelpers';
import { createWrapper } from 'app/test/jest/jestHelpers';
import Leftmenu from '../LeftMenu.vue';
import { createTestingPinia } from '@pinia/testing';

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import { createWrapper, axios } from 'app/test/jest/jestHelpers';
import Paginate from '../PaginateData.vue';
const mockPush = jest.fn();
@ -7,7 +7,7 @@ const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
currentRoute: { value: 'myCurrentRoute' },
}),
}));
@ -19,8 +19,8 @@ describe('Paginate', () => {
attrs: {
url: expectedUrl,
sortBy: 'id DESC',
rowsPerPage: 3
}
rowsPerPage: 3,
},
};
vm = createWrapper(Paginate, options).vm;
@ -29,7 +29,7 @@ describe('Paginate', () => {
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' },
{ id: 3, name: 'Bruce Wayne' },
]
],
});
});
@ -37,7 +37,7 @@ describe('Paginate', () => {
vm.rows = [];
vm.pagination.page = 1;
vm.hasMoreData = true;
})
});
describe('paginate()', () => {
it('should call to the paginate() method and set the data on the rows property', async () => {
@ -46,9 +46,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 0
}
}
skip: 0,
},
},
};
await vm.paginate();
@ -63,9 +63,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 0
}
}
skip: 0,
},
},
};
await vm.paginate();
@ -78,9 +78,9 @@ describe('Paginate', () => {
filter: {
order: 'id DESC',
limit: 3,
skip: 3
}
}
skip: 3,
},
},
};
vm.pagination.page = 2;
@ -125,8 +125,8 @@ describe('Paginate', () => {
jest.spyOn(axios, 'get').mockResolvedValue({
data: [
{ id: 1, name: 'Tony Stark' },
{ id: 2, name: 'Jessica Jones' }
]
{ id: 2, name: 'Jessica Jones' },
],
});
vm.rows = [

View File

@ -1,23 +1,22 @@
import { describe, expect, it, jest } from '@jest/globals';
import { axios, flushPromises } from 'app/tests/jest/jestHelpers';
import { axios, flushPromises } from 'app/test/jest/jestHelpers';
import { useRole } from '../useRole';
const role = useRole();
describe('useRole', () => {
describe('fetch', () => {
it('should call setUser and setRoles of the state with the expected data', async () => {
const rolesData = [
{
role: {
name: 'salesPerson'
}
name: 'salesPerson',
},
},
{
role: {
name: 'admin'
}
}
name: 'admin',
},
},
];
const fetchedUser = {
id: 999,
@ -26,18 +25,18 @@ describe('useRole', () => {
lang: 'en',
userConfig: {
darkMode: false,
}
}
},
};
const expectedUser = {
id: 999,
name: `T'Challa`,
nickname: 'Black Panther',
lang: 'en',
darkMode: false,
}
const expectedRoles = ['salesPerson', 'admin']
};
const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: fetchedUser }
data: { roles: rolesData, user: fetchedUser },
});
jest.spyOn(role.state, 'setUser');
@ -50,19 +49,19 @@ describe('useRole', () => {
expect(role.state.setUser).toHaveBeenCalledWith(expectedUser);
expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles);
role.state.setRoles([])
role.state.setRoles([]);
});
});
describe('hasAny', () => {
it('should return true if a role matched', async () => {
role.state.setRoles(['admin'])
role.state.setRoles(['admin']);
const hasRole = role.hasAny(['admin']);
await flushPromises();
expect(hasRole).toBe(true);
role.state.setRoles([])
role.state.setRoles([]);
});
it('should return false if no roles matched', async () => {

View File

@ -1,7 +1,7 @@
import { describe, expect, it, jest } from '@jest/globals';
import { useSession } from '../useSession';
import { useState } from '../useState';
import { axios } from 'app/tests/jest/jestHelpers';
import { axios } from 'app/test/jest/jestHelpers';
const session = useSession();
const state = useState();
@ -9,7 +9,7 @@ const state = useState();
describe('session', () => {
describe('getToken / setToken', () => {
it('should return an empty string if no token is found in local or session storage', async () => {
const expectedToken = ''
const expectedToken = '';
const token = session.getToken();
@ -17,11 +17,11 @@ describe('session', () => {
});
it('should return the token stored in local or session storage', async () => {
const expectedToken = 'myToken'
const expectedToken = 'myToken';
const data = {
token: expectedToken,
keepLogin: false
}
keepLogin: false,
};
session.setToken(data);
const token = session.getToken();
@ -38,23 +38,22 @@ describe('session', () => {
nickname: 'Black Panther',
lang: 'en',
darkMode: false,
}
};
const expectedUser = {
id: 0,
name: '',
nickname: '',
lang: '',
darkMode: null,
}
};
let user = state.getUser();
localStorage.setItem('token', 'tokenToBeGone');
state.setUser(previousUser)
state.setUser(previousUser);
expect(localStorage.getItem('token')).toEqual('tokenToBeGone');
expect(user.value).toEqual(previousUser);
session.destroy();
user = state.getUser();
@ -71,29 +70,29 @@ describe('session', () => {
lang: 'en',
userConfig: {
darkMode: false,
}
}
},
};
const rolesData = [
{
role: {
name: 'salesPerson'
}
name: 'salesPerson',
},
},
{
role: {
name: 'admin'
}
}
name: 'admin',
},
},
];
it('should fetch the user roles and then set token in the sessionStorage', async () => {
const expectedRoles = ['salesPerson', 'admin']
const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser }
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'mySessionToken'
const keepLogin = false
const expectedToken = 'mySessionToken';
const keepLogin = false;
await session.login(expectedToken, keepLogin);
@ -105,17 +104,17 @@ describe('session', () => {
expect(localToken).toBeNull();
expect(sessionToken).toEqual(expectedToken);
session.destroy() // this clears token and user for any other test
session.destroy(); // this clears token and user for any other test
});
it('should fetch the user roles and then set token in the localStorage', async () => {
const expectedRoles = ['salesPerson', 'admin']
const expectedRoles = ['salesPerson', 'admin'];
jest.spyOn(axios, 'get').mockResolvedValue({
data: { roles: rolesData, user: expectedUser }
data: { roles: rolesData, user: expectedUser },
});
const expectedToken = 'myLocalToken'
const keepLogin = true
const expectedToken = 'myLocalToken';
const keepLogin = true;
await session.login(expectedToken, keepLogin);
@ -127,7 +126,7 @@ describe('session', () => {
expect(localToken).toEqual(expectedToken);
expect(sessionToken).toBeNull();
session.destroy() // this clears token and user for any other test
session.destroy(); // this clears token and user for any other test
});
});
});

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import { createWrapper, axios } from 'app/test/jest/jestHelpers';
import ClaimDescriptorMenu from '../Card/ClaimDescriptorMenu.vue';
const mockPush = jest.fn();
@ -23,9 +23,9 @@ describe('ClaimDescriptorMenu', () => {
vm = createWrapper(ClaimDescriptorMenu, {
propsData: {
claim: {
id: 1
}
}
id: 1,
},
},
}).vm;
});
@ -40,9 +40,7 @@ describe('ClaimDescriptorMenu', () => {
await vm.deleteClaim();
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'positive' }
));
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' }));
});
});
});

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import { createWrapper, axios } from 'app/test/jest/jestHelpers';
import Login from '../LoginMain.vue';
const mockPush = jest.fn();
@ -7,7 +7,7 @@ const mockPush = jest.fn();
jest.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' }
currentRoute: { value: 'myCurrentRoute' },
}),
}));
@ -29,8 +29,8 @@ describe('Login', () => {
lang: 'en',
userConfig: {
darkMode: false,
}
}
},
};
jest.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } });
jest.spyOn(axios, 'get').mockResolvedValue({ data: { roles: [], user: expectedUser } });
jest.spyOn(vm.quasar, 'notify');
@ -40,9 +40,7 @@ describe('Login', () => {
await vm.onSubmit();
expect(vm.session.getToken()).toEqual('token');
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'positive' }
));
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'positive' }));
vm.session.destroy();
});

View File

@ -1,5 +1,5 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import { createWrapper, axios } from 'app/test/jest/jestHelpers';
import TicketBoxing from '../TicketBoxing.vue';
const mockPush = jest.fn();
@ -10,10 +10,10 @@ jest.mock('vue-router', () => ({
currentRoute: {
value: {
params: {
id: 1
}
}
}
id: 1,
},
},
},
}),
}));
@ -34,13 +34,9 @@ xdescribe('TicketBoxing', () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
const videoList = [
"2022-01-01T01-01-00.mp4",
"2022-02-02T02-02-00.mp4",
"2022-03-03T03-03-00.mp4",
]
max: 2,
};
const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4'];
jest.spyOn(axios, 'get').mockResolvedValue({ data: videoList });
jest.spyOn(vm.quasar, 'notify');
@ -55,17 +51,15 @@ xdescribe('TicketBoxing', () => {
const expeditionId = 1;
const timed = {
min: 1,
max: 2
}
max: 2,
};
jest.spyOn(axios, 'get').mockResolvedValue({ data: [] });
jest.spyOn(vm.quasar, 'notify')
jest.spyOn(vm.quasar, 'notify');
await vm.getVideoList(expeditionId, timed);
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{ 'type': 'negative' }
));
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' }));
});
});
});

View File

@ -1,5 +1,5 @@
import { mount, flushPromises } from '@vue/test-utils';
import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-jest';
import { installQuasarPlugin, installRouter } from '@quasar/quasar-app-extension-testing-unit-jest';
import { i18n } from 'src/boot/i18n';
import { Notify, Dialog } from 'quasar';
import axios from 'axios';
@ -10,6 +10,7 @@ installQuasarPlugin({
Dialog,
},
});
installRouter();
export function createWrapper(component, options) {
const mountOptions = {};

View File

@ -0,0 +1,34 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest';
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import ExampleComponent from './demo/ExampleComponent.vue';
installQuasar();
describe('example Component', () => {
it('should mount component with todos', () => {
const wrapper = mount(ExampleComponent, {
props: {
title: 'Hello',
totalCount: 4,
todos: [
{ id: 1, content: 'Hallo' },
{ id: 2, content: 'Hoi' },
],
},
});
expect(wrapper.vm.clickCount).toBe(0);
wrapper.find('.q-item').trigger('click');
expect(wrapper.vm.clickCount).toBe(1);
});
it('should mount component without todos', () => {
const wrapper = mount(ExampleComponent, {
props: {
title: 'Hello',
totalCount: 4,
},
});
expect(wrapper.findAll('.q-item')).toHaveLength(0);
});
});

View File

@ -0,0 +1,19 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest';
import { mount } from '@vue/test-utils';
import { Notify } from 'quasar';
import { describe, expect, it, vi } from 'vitest';
import NotifyComponent from './demo/NotifyComponent.vue';
installQuasar({ plugins: { Notify } });
describe('notify example', () => {
it('should call notify on click', async () => {
expect(NotifyComponent).toBeTruthy();
const wrapper = mount(NotifyComponent, {});
const spy = vi.spyOn(Notify, 'create');
expect(spy).not.toHaveBeenCalled();
wrapper.trigger('click');
expect(spy).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,44 @@
<template>
<div>
<p>{{ title }}</p>
<q-list>
<q-item v-for="todo in todos" :key="todo.id" @click="increment" clickable>
{{ todo.id }} - {{ todo.content }}
</q-item>
</q-list>
<p>Count: {{ todoCount }} / {{ totalCount }}</p>
<p>Active: {{ active ? 'yes' : 'no' }}</p>
<p>Clicks on todos: {{ clickCount }}</p>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
const props = defineProps({
title: {
type: String,
required: true,
},
todos: {
type: Array,
default() {
return [];
},
},
totalCount: {
type: Number,
required: true,
},
active: Boolean,
});
const clickCount = ref(0);
function increment() {
clickCount.value += 1;
return clickCount.value;
}
const todoCount = computed(() => props.todos.length);
</script>

View File

@ -0,0 +1,11 @@
<template>
<q-btn @click="onClick"> Click me! </q-btn>
</template>
<script setup>
import { Notify } from 'quasar';
function onClick() {
Notify.create('Hello there!');
}
</script>

View File

@ -0,0 +1,90 @@
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest';
import { vi, describe, expect, it, beforeAll } from 'vitest';
// import { createWrapper } from 'app/test/jest/jestHelpers';
import { mount } from '@vue/test-utils';
import { i18n } from 'src/boot/i18n';
import { Notify, Dialog } from 'quasar';
import App from 'src/App.vue';
import { useSession } from 'src/composables/useSession';
installQuasar({
plugins: {
Notify,
Dialog,
},
});
const mockPush = vi.fn();
const mockLoggedIn = vi.fn();
const mockDestroy = vi.fn();
const session = useSession();
vi.mock('vue-router', () => ({
useRouter: () => ({
push: mockPush,
currentRoute: { value: 'myCurrentRoute' },
}),
}));
vi.mock('src/composables/useSession', () => ({
useSession: () => ({
isLoggedIn: mockLoggedIn,
destroy: mockDestroy,
}),
}));
describe('App', () => {
let vm;
beforeAll(() => {
const options = {
global: {
stubs: ['router-view'],
plugins: [i18n],
},
};
vm = mount(App, options).vm;
});
it('should return a login error message', async () => {
vi.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(false);
const response = {
response: {
status: 401,
},
};
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Invalid username or password',
type: 'negative',
})
);
});
it('should return an unauthorized error message', async () => {
vi.spyOn(vm.quasar, 'notify');
session.isLoggedIn.mockReturnValue(true);
const response = {
response: {
status: 401,
},
};
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Access denied',
type: 'negative',
})
);
expect(session.destroy).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,13 @@
import { beforeEach } from 'vitest';
import { createRouterMock, injectRouterMock, VueRouterMock } from 'vue-router-mock';
import { config } from '@vue/test-utils';
// https://github.com/posva/vue-router-mock
export function installRouter(options) {
beforeEach(() => {
const router = createRouterMock(options);
injectRouterMock(router);
});
config.plugins.VueWrapper.install(VueRouterMock);
}

View File

@ -0,0 +1 @@
// This file will be run before each test file

27
vitest.config.js Normal file
View File

@ -0,0 +1,27 @@
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
import { quasar, transformAssetUrls } from '@quasar/vite-plugin';
import jsconfigPaths from 'vite-jsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
test: {
environment: 'happy-dom',
setupFiles: 'test/vitest/setup-file.js',
include: [
// Matches vitest tests in any subfolder of 'src' or into 'test/vitest/__tests__'
// Matches all files with extension 'js', 'jsx', 'ts' and 'tsx'
'src/**/*.vitest.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
'test/vitest/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
],
},
plugins: [
vue({
template: { transformAssetUrls },
}),
quasar({
sassVariables: 'src/quasar-variables.scss',
}),
jsconfigPaths(),
],
});