From 1f5c62eeb7907abb09efc0a710023c7ee19d5d19 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 15 Nov 2024 18:14:23 +0100 Subject: [PATCH 1/9] feat: `vitest` use client and server side testing for `kit` --- .changeset/thirty-ducks-buy.md | 5 + packages/addons/vitest-addon/index.ts | 188 ++++++++++++++++++-------- 2 files changed, 139 insertions(+), 54 deletions(-) create mode 100644 .changeset/thirty-ducks-buy.md diff --git a/.changeset/thirty-ducks-buy.md b/.changeset/thirty-ducks-buy.md new file mode 100644 index 000000000..db7f3b21f --- /dev/null +++ b/.changeset/thirty-ducks-buy.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat: `vitest` use client and server side testing for `kit` diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index 88059301a..895224a71 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -1,12 +1,12 @@ import { dedent, defineAddon, log } from '@sveltejs/cli-core'; -import { common, exports, imports, object } from '@sveltejs/cli-core/js'; +import { array, common, exports, functions, imports, object } from '@sveltejs/cli-core/js'; import { parseJson, parseScript } from '@sveltejs/cli-core/parsers'; export default defineAddon({ id: 'vitest', homepage: 'https://vitest.dev', options: {}, - run: ({ sv, typescript }) => { + run: ({ sv, typescript, kit }) => { const ext = typescript ? 'ts' : 'js'; sv.devDependency('vitest', '^2.0.4'); @@ -38,63 +38,143 @@ export default defineAddon({ `; }); - sv.file(`vite.config.${ext}`, (content) => { - const { ast, generateCode } = parseScript(content); - - // find `defineConfig` import declaration for "vite" - const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration'); - const defineConfigImportDecl = importDecls.find( - (importDecl) => - (importDecl.source.value === 'vite' || importDecl.source.value === 'vitest/config') && - importDecl.importKind === 'value' && - importDecl.specifiers?.some( - (specifier) => - specifier.type === 'ImportSpecifier' && specifier.imported.name === 'defineConfig' - ) - ); - - // we'll need to replace the "vite" import for a "vitest/config" import. - // if `defineConfig` is the only specifier in that "vite" import, remove the entire import declaration - if (defineConfigImportDecl?.specifiers?.length === 1) { - const idxToRemove = ast.body.indexOf(defineConfigImportDecl); - ast.body.splice(idxToRemove, 1); - } else { - // otherwise, just remove the `defineConfig` specifier - const idxToRemove = defineConfigImportDecl?.specifiers?.findIndex( - (s) => s.type === 'ImportSpecifier' && s.imported.name === 'defineConfig' - ); - if (idxToRemove) defineConfigImportDecl?.specifiers?.splice(idxToRemove, 1); - } + if (kit) { + sv.devDependency('@testing-library/svelte', '^5.2.4'); + sv.devDependency('@testing-library/jest-dom', '^6.6.3'); + sv.devDependency('jsdom', '^25.0.1'); + + sv.file(`${kit.routesDirectory}/page.svelte.test.${ext}`, (content) => { + if (content) return content; - const config = common.expressionFromString('defineConfig({})'); - const defaultExport = exports.defaultExport(ast, config); + return dedent` + import { describe,test, expect } from "vitest"; + import { render, screen } from '@testing-library/svelte'; + import Page from './+page.svelte'; - const test = object.create({ - include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']") + describe('/+page.svelte',()=>{ + test('should render h1',()=>{ + render(Page); + expect(screen.getByRole('heading',{level:1})).toBeInTheDocument(); + }) + }) + `; }); - // uses the `defineConfig` helper - if ( - defaultExport.value.type === 'CallExpression' && - defaultExport.value.arguments[0]?.type === 'ObjectExpression' - ) { - // if the previous `defineConfig` was aliased, reuse the alias for the "vitest/config" import - const importSpecifier = defineConfigImportDecl?.specifiers?.find( - (sp) => sp.type === 'ImportSpecifier' && sp.imported.name === 'defineConfig' + sv.file('vitest-setup-client.ts', (content) => { + if (content) return content; + + return dedent` + import '@testing-library/jest-dom/vitest' + + // add global mocks here, i.e. for sveltekit '$app/stores' + `; + }); + + sv.file('vitest.workspace.ts', (content) => { + const { ast, generateCode } = parseScript(content); + + imports.addNamed(ast, 'vitest/config', { defineWorkspace: 'defineWorkspace' }); + imports.addNamed(ast, '@testing-library/svelte/vite', { svelteTesting: 'svelteTesting' }); + + const clientObjectExpression = object.create({ + extends: common.createLiteral(`./vite.config.${ext}`), + plugins: common.expressionFromString( + '[svelteTesting({resolveBrowser: true,autoCleanup: true})]' + ), + test: object.create({ + name: common.createLiteral('client'), + environment: common.createLiteral('jsdom'), + clearMocks: common.expressionFromString('true'), + include: common.expressionFromString('["src/**/*.svelte.{test,spec}.{js,ts}"]'), + exclude: common.expressionFromString('["src/lib/server/**"]'), + setupFiles: common.expressionFromString('["./vitest-setup-client.ts"]') + }) + }); + const serverObjectExpression = object.create({ + extends: common.createLiteral(`./vite.config.${ext}`), + test: object.create({ + name: common.createLiteral('server'), + environment: common.createLiteral('node'), + include: common.expressionFromString('["src/**/*.{test,spec}.{js,ts}"]'), + exclude: common.expressionFromString('["src/**/*.svelte.{test,spec}.{js,ts}"]') + }) + }); + + const defineWorkspaceFallback = functions.call('defineWorkspace', []); + const { value: defineWorkspaceCall } = exports.defaultExport(ast, defineWorkspaceFallback); + if (defineWorkspaceCall.type !== 'CallExpression') { + log.warn('Unexpected vite config for vitest add-on. Could not update.'); + } + + const workspaceArray = functions.argumentByIndex( + defineWorkspaceCall, + 0, + array.createEmpty() ); - const defineConfigAlias = (importSpecifier?.local?.name ?? 'defineConfig') as string; - imports.addNamed(ast, 'vitest/config', { defineConfig: defineConfigAlias }); - - object.properties(defaultExport.value.arguments[0], { test }); - } else if (defaultExport.value.type === 'ObjectExpression') { - // if the config is just an object expression, just add the property - object.properties(defaultExport.value, { test }); - } else { - // unexpected config shape - log.warn('Unexpected vite config for vitest add-on. Could not update.'); - } + array.push(workspaceArray, clientObjectExpression); + array.push(workspaceArray, serverObjectExpression); - return generateCode(); - }); + return generateCode(); + }); + } else { + sv.file(`vite.config.${ext}`, (content) => { + const { ast, generateCode } = parseScript(content); + + // find `defineConfig` import declaration for "vite" + const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration'); + const defineConfigImportDecl = importDecls.find( + (importDecl) => + (importDecl.source.value === 'vite' || importDecl.source.value === 'vitest/config') && + importDecl.importKind === 'value' && + importDecl.specifiers?.some( + (specifier) => + specifier.type === 'ImportSpecifier' && specifier.imported.name === 'defineConfig' + ) + ); + + // we'll need to replace the "vite" import for a "vitest/config" import. + // if `defineConfig` is the only specifier in that "vite" import, remove the entire import declaration + if (defineConfigImportDecl?.specifiers?.length === 1) { + const idxToRemove = ast.body.indexOf(defineConfigImportDecl); + ast.body.splice(idxToRemove, 1); + } else { + // otherwise, just remove the `defineConfig` specifier + const idxToRemove = defineConfigImportDecl?.specifiers?.findIndex( + (s) => s.type === 'ImportSpecifier' && s.imported.name === 'defineConfig' + ); + if (idxToRemove) defineConfigImportDecl?.specifiers?.splice(idxToRemove, 1); + } + + const config = common.expressionFromString('defineConfig({})'); + const defaultExport = exports.defaultExport(ast, config); + + const test = object.create({ + include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']") + }); + + // uses the `defineConfig` helper + if ( + defaultExport.value.type === 'CallExpression' && + defaultExport.value.arguments[0]?.type === 'ObjectExpression' + ) { + // if the previous `defineConfig` was aliased, reuse the alias for the "vitest/config" import + const importSpecifier = defineConfigImportDecl?.specifiers?.find( + (sp) => sp.type === 'ImportSpecifier' && sp.imported.name === 'defineConfig' + ); + const defineConfigAlias = (importSpecifier?.local?.name ?? 'defineConfig') as string; + imports.addNamed(ast, 'vitest/config', { defineConfig: defineConfigAlias }); + + object.properties(defaultExport.value.arguments[0], { test }); + } else if (defaultExport.value.type === 'ObjectExpression') { + // if the config is just an object expression, just add the property + object.properties(defaultExport.value, { test }); + } else { + // unexpected config shape + log.warn('Unexpected vite config for vitest add-on. Could not update.'); + } + + return generateCode(); + }); + } } }); From 44298fbe612ffe8336ddc69517e8b38c8413b02a Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 15 Nov 2024 18:35:05 +0100 Subject: [PATCH 2/9] improvements --- packages/addons/vitest-addon/index.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index 895224a71..dc52fe8a7 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -47,7 +47,7 @@ export default defineAddon({ if (content) return content; return dedent` - import { describe,test, expect } from "vitest"; + import { describe,test, expect } from 'vitest'; import { render, screen } from '@testing-library/svelte'; import Page from './+page.svelte'; @@ -79,15 +79,15 @@ export default defineAddon({ const clientObjectExpression = object.create({ extends: common.createLiteral(`./vite.config.${ext}`), plugins: common.expressionFromString( - '[svelteTesting({resolveBrowser: true,autoCleanup: true})]' + '[svelteTesting({ resolveBrowser: true, autoCleanup: true })]' ), test: object.create({ name: common.createLiteral('client'), environment: common.createLiteral('jsdom'), clearMocks: common.expressionFromString('true'), - include: common.expressionFromString('["src/**/*.svelte.{test,spec}.{js,ts}"]'), - exclude: common.expressionFromString('["src/lib/server/**"]'), - setupFiles: common.expressionFromString('["./vitest-setup-client.ts"]') + include: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']"), + exclude: common.expressionFromString("['src/lib/server/**']"), + setupFiles: common.expressionFromString("['./vitest-setup-client.ts']") }) }); const serverObjectExpression = object.create({ @@ -95,8 +95,8 @@ export default defineAddon({ test: object.create({ name: common.createLiteral('server'), environment: common.createLiteral('node'), - include: common.expressionFromString('["src/**/*.{test,spec}.{js,ts}"]'), - exclude: common.expressionFromString('["src/**/*.svelte.{test,spec}.{js,ts}"]') + include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']"), + exclude: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']") }) }); @@ -120,7 +120,7 @@ export default defineAddon({ sv.file(`vite.config.${ext}`, (content) => { const { ast, generateCode } = parseScript(content); - // find `defineConfig` import declaration for "vite" + // find `defineConfig` import declaration for 'vite' const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration'); const defineConfigImportDecl = importDecls.find( (importDecl) => @@ -132,8 +132,8 @@ export default defineAddon({ ) ); - // we'll need to replace the "vite" import for a "vitest/config" import. - // if `defineConfig` is the only specifier in that "vite" import, remove the entire import declaration + // we'll need to replace the 'vite' import for a 'vitest/config' import. + // if `defineConfig` is the only specifier in that 'vite' import, remove the entire import declaration if (defineConfigImportDecl?.specifiers?.length === 1) { const idxToRemove = ast.body.indexOf(defineConfigImportDecl); ast.body.splice(idxToRemove, 1); @@ -157,7 +157,7 @@ export default defineAddon({ defaultExport.value.type === 'CallExpression' && defaultExport.value.arguments[0]?.type === 'ObjectExpression' ) { - // if the previous `defineConfig` was aliased, reuse the alias for the "vitest/config" import + // if the previous `defineConfig` was aliased, reuse the alias for the 'vitest/config' import const importSpecifier = defineConfigImportDecl?.specifiers?.find( (sp) => sp.type === 'ImportSpecifier' && sp.imported.name === 'defineConfig' ); From 96e9576248375995a06337e95ea6e6ea4d8fdda3 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:44:34 +0100 Subject: [PATCH 3/9] Update packages/addons/vitest-addon/index.ts Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- packages/addons/vitest-addon/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index dc52fe8a7..394f85597 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -54,7 +54,7 @@ export default defineAddon({ describe('/+page.svelte',()=>{ test('should render h1',()=>{ render(Page); - expect(screen.getByRole('heading',{level:1})).toBeInTheDocument(); + expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); }) }) `; From abe06ea8243c69e99ee6fe34c4f5fc5e3528fdb4 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 15 Nov 2024 19:09:58 +0100 Subject: [PATCH 4/9] enhance --- packages/addons/vitest-addon/index.ts | 193 ++++++++++---------------- 1 file changed, 73 insertions(+), 120 deletions(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index 394f85597..976ce7cb3 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -10,6 +10,9 @@ export default defineAddon({ const ext = typescript ? 'ts' : 'js'; sv.devDependency('vitest', '^2.0.4'); + sv.devDependency('@testing-library/svelte', '^5.2.4'); + sv.devDependency('@testing-library/jest-dom', '^6.6.3'); + sv.devDependency('jsdom', '^25.0.1'); sv.file('package.json', (content) => { const { data, generateCode } = parseJson(content); @@ -39,142 +42,92 @@ export default defineAddon({ }); if (kit) { - sv.devDependency('@testing-library/svelte', '^5.2.4'); - sv.devDependency('@testing-library/jest-dom', '^6.6.3'); - sv.devDependency('jsdom', '^25.0.1'); - sv.file(`${kit.routesDirectory}/page.svelte.test.${ext}`, (content) => { if (content) return content; return dedent` - import { describe,test, expect } from 'vitest'; - import { render, screen } from '@testing-library/svelte'; - import Page from './+page.svelte'; - - describe('/+page.svelte',()=>{ - test('should render h1',()=>{ - render(Page); - expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); + import { describe,test, expect } from 'vitest'; + import '@testing-library/jest-dom'; + import { render, screen } from '@testing-library/svelte'; + import Page from './+page.svelte'; + + describe('/+page.svelte', () => { + test('should render h1', () => { + render(Page); + expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); + }) }) - }) - `; + `; }); - - sv.file('vitest-setup-client.ts', (content) => { + } else { + sv.file(`src/App.svelte.test.${ext}`, (content) => { if (content) return content; return dedent` + import { describe,test, expect } from 'vitest'; + import '@testing-library/jest-dom'; + import { render, screen } from '@testing-library/svelte'; + import App from './App.svelte'; + + describe('App.svelte', () => { + test('should render h1', () => { + render(App); + expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); + }) + }) + `; + }); + } + + sv.file(`vitest-setup-client.${ext}`, (content) => { + if (content) return content; + + return dedent` import '@testing-library/jest-dom/vitest' // add global mocks here, i.e. for sveltekit '$app/stores' `; + }); + + sv.file(`vitest.workspace.${ext}`, (content) => { + const { ast, generateCode } = parseScript(content); + + imports.addNamed(ast, 'vitest/config', { defineWorkspace: 'defineWorkspace' }); + imports.addNamed(ast, '@testing-library/svelte/vite', { svelteTesting: 'svelteTesting' }); + + const clientObjectExpression = object.create({ + extends: common.createLiteral(`./vite.config.${ext}`), + plugins: common.expressionFromString('[svelteTesting()]'), + test: object.create({ + name: common.createLiteral('client'), + environment: common.createLiteral('jsdom'), + clearMocks: common.expressionFromString('true'), + include: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']"), + exclude: common.expressionFromString("['src/lib/server/**']"), + setupFiles: common.expressionFromString("['./vitest-setup-client.ts']") + }) + }); + const serverObjectExpression = object.create({ + extends: common.createLiteral(`./vite.config.${ext}`), + test: object.create({ + name: common.createLiteral('server'), + environment: common.createLiteral('node'), + include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']"), + exclude: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']") + }) }); - sv.file('vitest.workspace.ts', (content) => { - const { ast, generateCode } = parseScript(content); - - imports.addNamed(ast, 'vitest/config', { defineWorkspace: 'defineWorkspace' }); - imports.addNamed(ast, '@testing-library/svelte/vite', { svelteTesting: 'svelteTesting' }); - - const clientObjectExpression = object.create({ - extends: common.createLiteral(`./vite.config.${ext}`), - plugins: common.expressionFromString( - '[svelteTesting({ resolveBrowser: true, autoCleanup: true })]' - ), - test: object.create({ - name: common.createLiteral('client'), - environment: common.createLiteral('jsdom'), - clearMocks: common.expressionFromString('true'), - include: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']"), - exclude: common.expressionFromString("['src/lib/server/**']"), - setupFiles: common.expressionFromString("['./vitest-setup-client.ts']") - }) - }); - const serverObjectExpression = object.create({ - extends: common.createLiteral(`./vite.config.${ext}`), - test: object.create({ - name: common.createLiteral('server'), - environment: common.createLiteral('node'), - include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']"), - exclude: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']") - }) - }); + const defineWorkspaceFallback = functions.call('defineWorkspace', []); + const { value: defineWorkspaceCall } = exports.defaultExport(ast, defineWorkspaceFallback); + if (defineWorkspaceCall.type !== 'CallExpression') { + log.warn('Unexpected vite config for vitest add-on. Could not update.'); + } - const defineWorkspaceFallback = functions.call('defineWorkspace', []); - const { value: defineWorkspaceCall } = exports.defaultExport(ast, defineWorkspaceFallback); - if (defineWorkspaceCall.type !== 'CallExpression') { - log.warn('Unexpected vite config for vitest add-on. Could not update.'); - } - - const workspaceArray = functions.argumentByIndex( - defineWorkspaceCall, - 0, - array.createEmpty() - ); - array.push(workspaceArray, clientObjectExpression); - array.push(workspaceArray, serverObjectExpression); - - return generateCode(); - }); - } else { - sv.file(`vite.config.${ext}`, (content) => { - const { ast, generateCode } = parseScript(content); - - // find `defineConfig` import declaration for 'vite' - const importDecls = ast.body.filter((n) => n.type === 'ImportDeclaration'); - const defineConfigImportDecl = importDecls.find( - (importDecl) => - (importDecl.source.value === 'vite' || importDecl.source.value === 'vitest/config') && - importDecl.importKind === 'value' && - importDecl.specifiers?.some( - (specifier) => - specifier.type === 'ImportSpecifier' && specifier.imported.name === 'defineConfig' - ) - ); - - // we'll need to replace the 'vite' import for a 'vitest/config' import. - // if `defineConfig` is the only specifier in that 'vite' import, remove the entire import declaration - if (defineConfigImportDecl?.specifiers?.length === 1) { - const idxToRemove = ast.body.indexOf(defineConfigImportDecl); - ast.body.splice(idxToRemove, 1); - } else { - // otherwise, just remove the `defineConfig` specifier - const idxToRemove = defineConfigImportDecl?.specifiers?.findIndex( - (s) => s.type === 'ImportSpecifier' && s.imported.name === 'defineConfig' - ); - if (idxToRemove) defineConfigImportDecl?.specifiers?.splice(idxToRemove, 1); - } - - const config = common.expressionFromString('defineConfig({})'); - const defaultExport = exports.defaultExport(ast, config); - - const test = object.create({ - include: common.expressionFromString("['src/**/*.{test,spec}.{js,ts}']") - }); + const workspaceArray = functions.argumentByIndex(defineWorkspaceCall, 0, array.createEmpty()); + array.push(workspaceArray, clientObjectExpression); + array.push(workspaceArray, serverObjectExpression); - // uses the `defineConfig` helper - if ( - defaultExport.value.type === 'CallExpression' && - defaultExport.value.arguments[0]?.type === 'ObjectExpression' - ) { - // if the previous `defineConfig` was aliased, reuse the alias for the 'vitest/config' import - const importSpecifier = defineConfigImportDecl?.specifiers?.find( - (sp) => sp.type === 'ImportSpecifier' && sp.imported.name === 'defineConfig' - ); - const defineConfigAlias = (importSpecifier?.local?.name ?? 'defineConfig') as string; - imports.addNamed(ast, 'vitest/config', { defineConfig: defineConfigAlias }); - - object.properties(defaultExport.value.arguments[0], { test }); - } else if (defaultExport.value.type === 'ObjectExpression') { - // if the config is just an object expression, just add the property - object.properties(defaultExport.value, { test }); - } else { - // unexpected config shape - log.warn('Unexpected vite config for vitest add-on. Could not update.'); - } - - return generateCode(); - }); - } + return generateCode(); + }); } }); From 88f2bdf285ae609d5261f17c961cba8b131fe8c3 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 15 Nov 2024 19:11:00 +0100 Subject: [PATCH 5/9] fix file extension --- packages/addons/vitest-addon/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index 976ce7cb3..d98a73770 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -104,7 +104,7 @@ export default defineAddon({ clearMocks: common.expressionFromString('true'), include: common.expressionFromString("['src/**/*.svelte.{test,spec}.{js,ts}']"), exclude: common.expressionFromString("['src/lib/server/**']"), - setupFiles: common.expressionFromString("['./vitest-setup-client.ts']") + setupFiles: common.expressionFromString(`['./vitest-setup-client.${ext}']`) }) }); const serverObjectExpression = object.create({ From 5622141d643190c5ac9947051603f58e2f00f861 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 23 Dec 2024 09:41:41 +0100 Subject: [PATCH 6/9] add new code --- packages/addons/vitest-addon/index.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index c6dd095ae..4ab557822 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -84,9 +84,26 @@ export default defineAddon({ if (content) return content; return dedent` - import '@testing-library/jest-dom/vitest' + import '@testing-library/jest-dom/vitest'; + import {vi} from 'vitest'; // add global mocks here, i.e. for sveltekit '$app/stores' + + // needed for svelte/motion that exports new MediaQuery which calls window.matchMedia eagerly + Object.defineProperty(window, "matchMedia", { + writable: true, + enumerable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); `; }); From cbb95100dd60563740587329ddb30b8d033aced9 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 2 Feb 2025 17:20:06 +0100 Subject: [PATCH 7/9] small fixes --- packages/addons/_tests/vitest/test.ts | 6 +++++- packages/addons/vitest-addon/index.ts | 28 +++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/addons/_tests/vitest/test.ts b/packages/addons/_tests/vitest/test.ts index ed4519969..f6a0166d4 100644 --- a/packages/addons/_tests/vitest/test.ts +++ b/packages/addons/_tests/vitest/test.ts @@ -1,3 +1,4 @@ +import { execSync } from 'node:child_process'; import { expect } from '@playwright/test'; import { setupTest } from '../_setup/suite.ts'; import vitest from '../../vitest-addon/index.ts'; @@ -7,7 +8,10 @@ const { test, variants, prepareServer } = setupTest({ vitest }); test.concurrent.for(variants)('core - %s', async (variant, { page, ...ctx }) => { const cwd = await ctx.run(variant, { vitest: {} }); - const { close } = await prepareServer({ cwd, page }); + const { close } = await prepareServer({ cwd, page }, () => { + execSync('npm run test', { cwd, stdio: 'pipe' }); + }); + // kill server process when we're done ctx.onTestFinished(async () => await close()); diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index 769b02a24..d031c61c5 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -47,17 +47,17 @@ export default defineAddon({ if (content) return content; return dedent` - import { describe,test, expect } from 'vitest'; - import '@testing-library/jest-dom'; + import { describe, test, expect } from 'vitest'; + import '@testing-library/jest-dom/vitest'; import { render, screen } from '@testing-library/svelte'; - import Page from './+page.svelte'; + import Page from './+page.svelte'; describe('/+page.svelte', () => { test('should render h1', () => { render(Page); - expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); - }) - }) + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + }); `; }); } else { @@ -65,17 +65,17 @@ export default defineAddon({ if (content) return content; return dedent` - import { describe,test, expect } from 'vitest'; - import '@testing-library/jest-dom'; + import { describe, test, expect } from 'vitest'; + import '@testing-library/jest-dom/vitest'; import { render, screen } from '@testing-library/svelte'; - import App from './App.svelte'; + import App from './App.svelte'; describe('App.svelte', () => { test('should render h1', () => { render(App); - expect(screen.getByRole('heading', {level:1})).toBeInTheDocument(); - }) - }) + expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); + }); + }); `; }); } @@ -85,9 +85,9 @@ export default defineAddon({ return dedent` import '@testing-library/jest-dom/vitest'; - import {vi} from 'vitest'; + import { vi } from 'vitest'; - // add global mocks here, i.e. for sveltekit '$app/stores' + // add global mocks here, i.e. for sveltekit '$app/state' // needed for svelte/motion that exports new MediaQuery which calls window.matchMedia eagerly Object.defineProperty(window, "matchMedia", { From c0d8a617bf31276e72d0a1ebf35b0a995848c603 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 7 Feb 2025 13:56:41 +0100 Subject: [PATCH 8/9] ditch `vitest.workspace.js` and use `vite.config.js` instead for workspaces --- packages/addons/vitest-addon/index.ts | 48 ++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index d031c61c5..b5eef9fb5 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -87,30 +87,29 @@ export default defineAddon({ import '@testing-library/jest-dom/vitest'; import { vi } from 'vitest'; - // add global mocks here, i.e. for sveltekit '$app/state' - - // needed for svelte/motion that exports new MediaQuery which calls window.matchMedia eagerly - Object.defineProperty(window, "matchMedia", { - writable: true, - enumerable: true, - value: vi.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), // deprecated - removeListener: vi.fn(), // deprecated - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), - }); + // required for svelte5 + jsdom as jsdom does not support matchMedia + if(typeof window != undefined && !window.matchMedia) { + Object.defineProperty(window, 'matchMedia', { + writable: true, + enumerable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }) + } + + // add more mocks here if you need them `; }); - sv.file(`vitest.workspace.${ext}`, (content) => { + sv.file(`vite.config.${ext}`, (content) => { const { ast, generateCode } = parseScript(content); - imports.addNamed(ast, 'vitest/config', { defineWorkspace: 'defineWorkspace' }); imports.addNamed(ast, '@testing-library/svelte/vite', { svelteTesting: 'svelteTesting' }); const clientObjectExpression = object.create({ @@ -135,13 +134,16 @@ export default defineAddon({ }) }); - const defineWorkspaceFallback = functions.call('defineWorkspace', []); - const { value: defineWorkspaceCall } = exports.defaultExport(ast, defineWorkspaceFallback); + const defineConfigFallback = functions.call('defineConfig', []); + const { value: defineWorkspaceCall } = exports.defaultExport(ast, defineConfigFallback); if (defineWorkspaceCall.type !== 'CallExpression') { - log.warn('Unexpected vite config for vitest add-on. Could not update.'); + log.warn('Unexpected vite config. Could not update.'); } - const workspaceArray = functions.argumentByIndex(defineWorkspaceCall, 0, array.createEmpty()); + const vitestConfig = functions.argumentByIndex(defineWorkspaceCall, 0, object.createEmpty()); + const testObject = object.property(vitestConfig, 'test', object.createEmpty()); + + const workspaceArray = object.property(testObject, 'workspace', array.createEmpty()); array.push(workspaceArray, clientObjectExpression); array.push(workspaceArray, serverObjectExpression); From 2cb6f97044d9cfcdf38bc177de42d0178ca4a596 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 7 Feb 2025 14:30:47 +0100 Subject: [PATCH 9/9] remove if --- packages/addons/vitest-addon/index.ts | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/addons/vitest-addon/index.ts b/packages/addons/vitest-addon/index.ts index b5eef9fb5..c0cb0c630 100644 --- a/packages/addons/vitest-addon/index.ts +++ b/packages/addons/vitest-addon/index.ts @@ -88,20 +88,18 @@ export default defineAddon({ import { vi } from 'vitest'; // required for svelte5 + jsdom as jsdom does not support matchMedia - if(typeof window != undefined && !window.matchMedia) { - Object.defineProperty(window, 'matchMedia', { - writable: true, - enumerable: true, - value: vi.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), - }) - } + Object.defineProperty(window, 'matchMedia', { + writable: true, + enumerable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }) // add more mocks here if you need them `;