test(apps/app): set up Vitest harness — closes TECH-APP-VITEST (WS-6)
Mirrors apps/portal's Vitest setup so the SPA can take frontend unit + component tests. Required prerequisite for WS-6 sessie 3b's admin UI work — apps/portal had 113+ tests, apps/app had zero, and launching WS-6's organizer UI uncovered while the portal SPA is well-tested would be asymmetric quality. Setup: - vitest, happy-dom, @vue/test-utils, @testing-library/vue installed - vitest.config.ts mirrors portal config: trimmed auto-imports (no pinia/vue-router/vue-i18n/@vueuse/math) so tests run fast in happy-dom without loading the full Vuexy bundle - AutoImport's dts:false prevents the trimmed test-only set from clobbering the dev-server's full auto-imports.d.ts (apps/app's auto-import surface is bigger than the portal's) - tests/setup.ts mocks vue-router by default; tests that exercise the real router can override per-suite - Sample sanity test confirms the harness works end-to-end Adds `pnpm test` and `pnpm test:watch` scripts to package.json. Refs: BACKLOG TECH-APP-VITEST, WS-6 sessie 3b prerequisite Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,9 @@
|
|||||||
"lint": "eslint . -c .eslintrc.cjs --fix --ext .ts,.js,.cjs,.vue,.tsx,.jsx",
|
"lint": "eslint . -c .eslintrc.cjs --fix --ext .ts,.js,.cjs,.vue,.tsx,.jsx",
|
||||||
"build:icons": "tsx src/plugins/iconify/build-icons.ts",
|
"build:icons": "tsx src/plugins/iconify/build-icons.ts",
|
||||||
"msw:init": "msw init public/ --save",
|
"msw:init": "msw init public/ --save",
|
||||||
"postinstall": "npm run build:icons && npm run msw:init"
|
"postinstall": "npm run build:icons && npm run msw:init",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@casl/ability": "6.7.3",
|
"@casl/ability": "6.7.3",
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
"@intlify/unplugin-vue-i18n": "11.0.1",
|
"@intlify/unplugin-vue-i18n": "11.0.1",
|
||||||
"@stylistic/stylelint-config": "1.0.1",
|
"@stylistic/stylelint-config": "1.0.1",
|
||||||
"@stylistic/stylelint-plugin": "2.1.3",
|
"@stylistic/stylelint-plugin": "2.1.3",
|
||||||
|
"@testing-library/vue": "^8.1.0",
|
||||||
"@tiptap/extension-character-count": "^2.27.1",
|
"@tiptap/extension-character-count": "^2.27.1",
|
||||||
"@tiptap/extension-placeholder": "^2.27.1",
|
"@tiptap/extension-placeholder": "^2.27.1",
|
||||||
"@tiptap/extension-subscript": "^2.27.1",
|
"@tiptap/extension-subscript": "^2.27.1",
|
||||||
@@ -92,6 +95,7 @@
|
|||||||
"@typescript-eslint/parser": "7.18.0",
|
"@typescript-eslint/parser": "7.18.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@vitejs/plugin-vue": "6.0.1",
|
||||||
"@vitejs/plugin-vue-jsx": "5.1.1",
|
"@vitejs/plugin-vue-jsx": "5.1.1",
|
||||||
|
"@vue/test-utils": "^2.4.9",
|
||||||
"baseline-browser-mapping": "^2.10.16",
|
"baseline-browser-mapping": "^2.10.16",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
@@ -103,6 +107,7 @@
|
|||||||
"eslint-plugin-sonarjs": "0.24.0",
|
"eslint-plugin-sonarjs": "0.24.0",
|
||||||
"eslint-plugin-unicorn": "51.0.1",
|
"eslint-plugin-unicorn": "51.0.1",
|
||||||
"eslint-plugin-vue": "9.33.0",
|
"eslint-plugin-vue": "9.33.0",
|
||||||
|
"happy-dom": "^20.9.0",
|
||||||
"msw": "2.6.8",
|
"msw": "2.6.8",
|
||||||
"postcss-html": "1.8.0",
|
"postcss-html": "1.8.0",
|
||||||
"postcss-scss": "4.0.9",
|
"postcss-scss": "4.0.9",
|
||||||
@@ -123,6 +128,7 @@
|
|||||||
"vite-plugin-vue-meta-layouts": "0.6.1",
|
"vite-plugin-vue-meta-layouts": "0.6.1",
|
||||||
"vite-plugin-vuetify": "2.1.2",
|
"vite-plugin-vuetify": "2.1.2",
|
||||||
"vite-svg-loader": "5.1.0",
|
"vite-svg-loader": "5.1.0",
|
||||||
|
"vitest": "^4.1.5",
|
||||||
"vue-shepherd": "3.0.0",
|
"vue-shepherd": "3.0.0",
|
||||||
"vue-tsc": "3.1.2"
|
"vue-tsc": "3.1.2"
|
||||||
},
|
},
|
||||||
|
|||||||
646
apps/app/pnpm-lock.yaml
generated
646
apps/app/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
19
apps/app/tests/sanity.spec.ts
Normal file
19
apps/app/tests/sanity.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Harness sanity check — confirms Vitest + happy-dom + auto-imports
|
||||||
|
* are wired correctly. If this passes, real tests under
|
||||||
|
* src/**\/__tests__/ can run without per-suite plumbing.
|
||||||
|
*
|
||||||
|
* Tracked: BACKLOG TECH-APP-VITEST closed by WS-6 sessie 3b.
|
||||||
|
*/
|
||||||
|
describe('vitest harness', () => {
|
||||||
|
it('runs in happy-dom with globals', () => {
|
||||||
|
expect(typeof window).toBe('object')
|
||||||
|
expect(typeof document).toBe('object')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('truth holds', () => {
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
8
apps/app/tests/setup.ts
Normal file
8
apps/app/tests/setup.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { vi } from 'vitest'
|
||||||
|
|
||||||
|
// Default vue-router mock — individual tests can override with their own mock.
|
||||||
|
// Page-level tests that exercise the actual router should not import this.
|
||||||
|
vi.mock('vue-router', () => ({
|
||||||
|
useRoute: () => ({ params: {}, query: {} }),
|
||||||
|
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
|
||||||
|
}))
|
||||||
37
apps/app/vitest.config.ts
Normal file
37
apps/app/vitest.config.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
// Dedicated Vitest config — intentionally trimmed down from vite.config.ts.
|
||||||
|
// Skip Vuetify / MetaLayouts / VueRouter plugins so unit tests run fast in
|
||||||
|
// happy-dom without loading the full Vuexy bundle. Mirrors apps/portal/vitest.config.ts.
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
AutoImport({
|
||||||
|
imports: ['vue', '@vueuse/core'],
|
||||||
|
dirs: ['./src/@core/utils', './src/@core/composable/', './src/composables/', './src/utils/'],
|
||||||
|
vueTemplate: true,
|
||||||
|
// Don't write to auto-imports.d.ts — vite.config.ts owns that file
|
||||||
|
// with the full app's auto-import set. Trimmed test-only set must
|
||||||
|
// not clobber the IDE typings for the running dev server.
|
||||||
|
dts: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
'@core': fileURLToPath(new URL('./src/@core', import.meta.url)),
|
||||||
|
'@layouts': fileURLToPath(new URL('./src/@layouts', import.meta.url)),
|
||||||
|
'@images': fileURLToPath(new URL('./src/assets/images/', import.meta.url)),
|
||||||
|
'@styles': fileURLToPath(new URL('./src/assets/styles/', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
globals: true,
|
||||||
|
include: ['tests/**/*.{test,spec}.ts', 'src/**/__tests__/**/*.{test,spec}.ts'],
|
||||||
|
setupFiles: ['./tests/setup.ts'],
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user