From 4f5df60d4cccb9e2b3927a263c39d2c0766d7bcd Mon Sep 17 00:00:00 2001 From: Maxime GRANDCOLAS Date: Thu, 25 Apr 2024 07:42:48 +0200 Subject: [PATCH 1/2] Setup components test (with ms-input tests imported from parsec-cloud) --- package-lock.json | 35 +++++++++++++- package.json | 3 +- .../specs/testMsChoosePasswordInput.spec.ts | 45 ++++++++++++++++++ .../specs/testPasswordInput.spec.ts | 46 +++++++++++++++++++ .../components/specs/testSearchInput.spec.ts | 37 +++++++++++++++ tests/support/mocks.ts | 12 +++++ tests/support/setup.ts | 17 +++++++ vite.config.ts | 18 ++++++-- 8 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 tests/components/specs/testMsChoosePasswordInput.spec.ts create mode 100644 tests/components/specs/testPasswordInput.spec.ts create mode 100644 tests/components/specs/testSearchInput.spec.ts create mode 100644 tests/support/mocks.ts create mode 100644 tests/support/setup.ts diff --git a/package-lock.json b/package-lock.json index e29b80d..fe7306d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@vitejs/plugin-legacy": "^5.0.0", "@vitejs/plugin-vue": "^4.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.3.0", + "@vue/test-utils": "^2.4.5", "@vue/typescript": "^1.8.20", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", @@ -42,6 +42,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.18.1", "eslint-plugin-vue-scoped-css": "^2.5.1", + "happy-dom": "^14.7.1", "prettier": "3.1.0", "prettier-plugin-organize-imports": "^3.2.4", "sass": "^1.69.5", @@ -6987,6 +6988,20 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/happy-dom": { + "version": "14.7.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.7.1.tgz", + "integrity": "sha512-v60Q0evZ4clvMcrAh5/F8EdxDdfHdFrtffz/CNe10jKD+nFweZVxM91tW+UyY2L4AtpgIaXdZ7TQmiO1pfcwbg==", + "dev": true, + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -11747,6 +11762,15 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.91.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", @@ -11836,6 +11860,15 @@ "node": ">=4.0" } }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 346f3a6..b45fa46 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@vitejs/plugin-legacy": "^5.0.0", "@vitejs/plugin-vue": "^4.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.3.0", + "@vue/test-utils": "^2.4.5", "@vue/typescript": "^1.8.20", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", @@ -62,6 +62,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.18.1", "eslint-plugin-vue-scoped-css": "^2.5.1", + "happy-dom": "^14.7.1", "prettier": "3.1.0", "prettier-plugin-organize-imports": "^3.2.4", "sass": "^1.69.5", diff --git a/tests/components/specs/testMsChoosePasswordInput.spec.ts b/tests/components/specs/testMsChoosePasswordInput.spec.ts new file mode 100644 index 0000000..7de4090 --- /dev/null +++ b/tests/components/specs/testMsChoosePasswordInput.spec.ts @@ -0,0 +1,45 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { IonInput } from '@ionic/vue'; +import { MsChoosePasswordInput } from '@lib'; +import { VueWrapper, mount } from '@vue/test-utils'; + +describe('Choose password', () => { + let wrapper: VueWrapper; + + beforeEach(() => { + wrapper = mount(MsChoosePasswordInput, {}); + }); + + it('Validate the fields', async () => { + // Fields are empty, obviously not valid + expect(await wrapper.vm.areFieldsCorrect()).to.be.false; + + const ionInputs = wrapper.findAllComponents(IonInput); + ionInputs[0].vm.$emit('ionInput', { target: { value: 'P@ssw0rd.' } }); + expect(wrapper.vm.password).to.equal('P@ssw0rd.'); + + // Confirmation is not filled, not valid + expect(await wrapper.vm.areFieldsCorrect()).to.be.false; + + ionInputs[1].vm.$emit('ionInput', { target: { value: 'P@ssw0rd.' } }); + expect(wrapper.vm.passwordConfirm).to.equal('P@ssw0rd.'); + + // P@ssw0rd is not strong enough + expect(await wrapper.vm.areFieldsCorrect()).to.be.false; + + ionInputs[0].vm.$emit('ionInput', { + target: { value: 'ABiggerSaferPassword' }, + }); + expect(wrapper.vm.password).to.equal('ABiggerSaferPassword'); + + // Password is strong enough but password and confirmation don't match + expect(await wrapper.vm.areFieldsCorrect()).to.be.false; + + ionInputs[1].vm.$emit('ionInput', { + target: { value: 'ABiggerSaferPassword' }, + }); + expect(wrapper.vm.passwordConfirm).to.equal('ABiggerSaferPassword'); + expect(await wrapper.vm.areFieldsCorrect()).to.be.true; + }); +}); diff --git a/tests/components/specs/testPasswordInput.spec.ts b/tests/components/specs/testPasswordInput.spec.ts new file mode 100644 index 0000000..2dd068f --- /dev/null +++ b/tests/components/specs/testPasswordInput.spec.ts @@ -0,0 +1,46 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { IonInput } from '@ionic/vue'; +import { MsPasswordInput } from '@lib'; +import { VueWrapper, mount } from '@vue/test-utils'; + +describe('Password Input', () => { + let wrapper: VueWrapper; + beforeEach(() => { + wrapper = mount(MsPasswordInput, { + props: { + label: 'A Label', + modelValue: '', + }, + }); + }); + + it('should emit a signal when input changes', async () => { + const ionInput = wrapper.findComponent(IonInput); + ionInput.vm.$emit('ionInput', { target: { value: 'P@ssw0rd.' } }); + expect(wrapper.emitted('change')?.length).to.equal(1); + expect(wrapper.emitted('change')?.at(0)).to.have.same.members(['P@ssw0rd.']); + }); + + it('should emit enter when Enter key is pressed', async () => { + // Setting a value + await wrapper.setProps({ modelValue: 'P@ssw0rd.' }); + const ionInput = wrapper.findComponent(IonInput); + await ionInput.trigger('keyup.enter'); + expect(wrapper.emitted('onEnterKeyup')?.length).to.equal(1); + }); + + it('should not emit enter when input is empty', async () => { + const ionInput = wrapper.findComponent(IonInput); + await ionInput.trigger('keyup.enter'); + expect(wrapper.emitted('onEnterKeyup')).to.be.undefined; + }); + + it('should toggle password visibility button icon and password input type on password visibility button click', async () => { + expect((wrapper.vm as any).passwordVisible).to.be.false; + await wrapper.find('.input-icon').trigger('click'); + expect((wrapper.vm as any).passwordVisible).to.be.true; + await wrapper.find('.input-icon').trigger('click'); + expect((wrapper.vm as any).passwordVisible).to.be.false; + }); +}); diff --git a/tests/components/specs/testSearchInput.spec.ts b/tests/components/specs/testSearchInput.spec.ts new file mode 100644 index 0000000..587c8a8 --- /dev/null +++ b/tests/components/specs/testSearchInput.spec.ts @@ -0,0 +1,37 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { IonInput } from '@ionic/vue'; +import { MsSearchInput } from '@lib'; +import { mount, VueWrapper } from '@vue/test-utils'; + +describe('Search Input', () => { + let wrapper: VueWrapper; + beforeEach(() => { + wrapper = mount(MsSearchInput, { + props: { + placeholder: 'A Label', + }, + }); + }); + + it('should emit a signal when input changes', async () => { + const ionInput = wrapper.findComponent(IonInput); + ionInput.vm.$emit('ionInput', { target: { value: 'Search string' } }); + expect(wrapper.emitted('change')?.length).to.equal(1); + expect(wrapper.emitted('change')?.at(0)).to.have.same.members(['Search string']); + }); + + it('should emit enter when Enter key is pressed', async () => { + // Setting a value + await wrapper.setProps({ modelValue: 'Search string' }); + const ionInput = wrapper.findComponent(IonInput); + await ionInput.trigger('keyup.enter'); + expect(wrapper.emitted('enter')?.length).to.equal(1); + }); + + it('should not emit enter when input is empty', async () => { + const ionInput = wrapper.findComponent(IonInput); + await ionInput.trigger('keyup.enter'); + expect(wrapper.emitted('enter')).to.be.undefined; + }); +}); diff --git a/tests/support/mocks.ts b/tests/support/mocks.ts new file mode 100644 index 0000000..b395a5b --- /dev/null +++ b/tests/support/mocks.ts @@ -0,0 +1,12 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { I18n, Translatable } from '@lib'; +import { config } from '@vue/test-utils'; + +function mockI18n(): void { + config.global.mocks = { + $msTranslate: (tr: Translatable): string => I18n.translate(tr), + }; +} + +export { mockI18n }; diff --git a/tests/support/setup.ts b/tests/support/setup.ts new file mode 100644 index 0000000..bc5f010 --- /dev/null +++ b/tests/support/setup.ts @@ -0,0 +1,17 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +import { I18n } from '@lib'; +import appEnUS from '@lib/locales/en-US.json'; +import appFrFR from '@lib/locales/fr-FR.json'; +import { mockI18n } from '@tests/support/mocks'; + +beforeEach(() => { + I18n.init({ + defaultLocale: 'en-US', + customAssets: { + 'fr-FR': appFrFR, + 'en-US': appEnUS, + }, + }); + mockI18n(); +}); diff --git a/vite.config.ts b/vite.config.ts index 869d19b..8546049 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,11 +1,14 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS +/// + import vue from '@vitejs/plugin-vue'; import path from 'path'; -import { defineConfig } from 'vite'; +import { defineConfig, UserConfig } from 'vite'; import dts from 'vite-plugin-dts'; import { libInjectCss } from 'vite-plugin-lib-inject-css'; // https://vitejs.dev/config/ -export default defineConfig({ +const config: UserConfig = { plugins: [ vue(), libInjectCss(), @@ -22,8 +25,13 @@ export default defineConfig({ }, }, test: { + setupFiles: [path.resolve(__dirname, './tests/support/setup.ts')], globals: true, - environment: 'jsdom', + alias: { + '@lib': path.resolve(__dirname, './lib'), + '@tests': path.resolve(__dirname, './tests'), + }, + environment: 'happy-dom', }, build: { manifest: true, @@ -48,4 +56,6 @@ export default defineConfig({ }, }, }, -}); +}; + +export default defineConfig(config); From b539c01afd597e88fa670d411c2b33d5efed1888 Mon Sep 17 00:00:00 2001 From: Alban Costaz Date: Thu, 25 Apr 2024 10:03:57 +0200 Subject: [PATCH 2/2] Activate unit tests on CI --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcc36a7..9f74b85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,8 +40,6 @@ jobs: timeout-minutes: 2 test: - # Currently disable the tests since they are not configured in the repository. - if: false name: Test runs-on: ubuntu-22.04 steps: