Skip to content
This repository has been archived by the owner on Dec 19, 2023. It is now read-only.

Commit

Permalink
fix!: remove nuxt as default test environment (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
danielroe and antfu authored Jan 25, 2023
1 parent 476137a commit 3b8e16e
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 111 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,10 @@ jobs:
- name: 🛠 Build project
run: pnpm build

- name: 🧪 Playground Unit Test
- name: 🧪 Playground Test
run: pnpm -C playground run test:unit --coverage

- name: 🧪 Playground Nuxt Test
run: pnpm -C playground run test:nuxt --coverage

- name: 🧪 Playground Nuxt Test on Dev
- name: 🧪 Playground Test on Dev
run: pnpm -C playground run test:dev

- name: 🟩 Coverage
Expand Down
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,39 @@ export default defineConfigWithNuxt({
})
```

4. Setting environment for your tests

That's it. Now when you run `vitest` your Nuxt environment will be available throughout your tests.
By default, `nuxt-vitest` will not change your default Vitest environment, so you can do fine-grain opt-in and run Nuxt tests together with other unit tests.

We provided a filename convention that test files contains `.nuxt.`, like `*.nuxt.test.{js,ts}` and `*.nuxt.spec.{js,ts}`, will be run in Nuxt environment automatically.

Or you can add `@vitest-environment nuxt` in your test file as a comment to opt-in per test file.

```js
// @vitest-environment nuxt
import { test } from 'nuxt-vitest'

test('my test', () => {
// ... test with Nuxt environment!
})
```

Finally, you can set `envoironment: 'nuxt'`, to enable Nuxt environment for **all tests**.

```js
// vitest.config.ts
import { defineConfigWithNuxt } from 'nuxt-vitest'

export default defineConfigWithNuxt({
test: {
envoironment: 'nuxt'
}
})
```

## 👉 Important notes

When you run your tests within `nuxt-vitest`, they will be running in a [`happy-dom`](https://github.com/capricorn86/happy-dom) environment. Before your tests run, a global Nuxt app will be initialised (including, for example, running any plugins or code you've defined in your `app.vue`).
When you run your tests within the Nuxt environment, they will be running in a [`happy-dom`](https://github.com/capricorn86/happy-dom) environment. Before your tests run, a global Nuxt app will be initialised (including, for example, running any plugins or code you've defined in your `app.vue`).

This means you should be take particular care not to mutate the global state in your tests (or, if you have, to reset it afterwards).

Expand Down
10 changes: 9 additions & 1 deletion packages/nuxt-vitest/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,18 @@ export async function getVitestConfig(

return {
...options.viteConfig,
server: {
...options.viteConfig.server,
middlewareMode: false,
},
test: {
...options.viteConfig.test,
dir: options.nuxt.options.rootDir,
environment: 'nuxt',
environmentMatchGlobs: [
['**/*.nuxt.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', 'nuxt'],
['{test,tests}/nuxt/**.*', 'nuxt'],
...(options.viteConfig.test?.environmentMatchGlobs || []),
],
deps: {
...options.viteConfig.test?.deps,
inline:
Expand Down
8 changes: 4 additions & 4 deletions packages/nuxt-vitest/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { getPort } from 'get-port-please'
export interface NuxtVitestOptions {
startOnBoot?: boolean
logToConsole?: boolean
configFile?: string
vitestConfig?: VitestConfig
}

Expand Down Expand Up @@ -68,11 +67,10 @@ export default defineNuxtModule<NuxtVitestOptions>({
)) as typeof import('vitest/node')

// For testing dev mode in CI, maybe expose an option to user later
const vitestConfig = process.env.NUXT_VITEST_DEV_TEST
const vitestConfig: VitestConfig = process.env.NUXT_VITEST_DEV_TEST
? {
...options.vitestConfig,
watch: false,
config: options.configFile,
}
: {
...options.vitestConfig,
Expand All @@ -82,9 +80,11 @@ export default defineNuxtModule<NuxtVitestOptions>({
api: {
port: PORT,
},
config: options.configFile,
}

// TODO: Investigate segfault when loading config file in Nuxt
viteConfig.configFile = false

// Start Vitest
const promise = startVitest('test', [], vitestConfig, viteConfig)

Expand Down
160 changes: 81 additions & 79 deletions packages/vitest-environment-nuxt/src/modules/auto-import-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,95 +32,97 @@ export default defineNuxtModule({

addVitePlugin({
name: 'nuxt:vitest:auto-import-mock',
transform(code, id) {
if (!code.includes(HELPER_NAME)) return
if (id.includes('/node_modules/')) return
enforce: 'post',
transform: {
order: 'post',
handler(code, id) {
if (!code.includes(HELPER_NAME)) return
if (id.includes('/node_modules/')) return

let ast: AcornNode
try {
ast = this.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
ranges: true,
})
} catch (e) {
return
}
let ast: AcornNode
try {
ast = this.parse(code, {
sourceType: 'module',
ecmaVersion: 'latest',
ranges: true,
})
} catch (e) {
return
}

const mocks: MockInfo[] = []
const mocks: MockInfo[] = []

walk(ast, {
enter: node => {
if (node.type !== 'CallExpression') return
const call = node as CallExpression
if (
call.callee.type !== 'Identifier' ||
call.callee.name !== HELPER_NAME
) {
return
}
if (call.arguments.length !== 2) {
return
}
if (call.arguments[0].type !== 'Literal') {
return // TODO: warn
}
const name = call.arguments[0].value as string
const importItem = imports.find(_ => name === (_.as || _.name))
if (!importItem) {
return this.error(`Cannot find import "${name}" to mock`)
}
mocks.push({
name,
import: importItem,
factory: code.slice(
call.arguments[1].range![0],
call.arguments[1].range![1]
),
start: call.range![0],
end: call.range![1],
})
},
})
walk(ast as any, {
enter: node => {
if (node.type !== 'CallExpression') return
const call = node as CallExpression
if (
call.callee.type !== 'Identifier' ||
call.callee.name !== HELPER_NAME
) {
return
}
if (call.arguments.length !== 2) {
return
}
if (call.arguments[0].type !== 'Literal') {
return // TODO: warn
}
const name = call.arguments[0].value as string
const importItem = imports.find(_ => name === (_.as || _.name))
if (!importItem) {
return this.error(`Cannot find import "${name}" to mock`)
}
mocks.push({
name,
import: importItem,
factory: code.slice(
call.arguments[1].range![0],
call.arguments[1].range![1]
),
start: call.range![0],
end: call.range![1],
})
},
})

if (!mocks.length) return
if (!mocks.length) return

const s = new MagicString(code)
const s = new MagicString(code)

const mockMap = new Map<string, MockInfo[]>()
for (const mock of mocks) {
s.overwrite(mock.start, mock.end, '')
if (!mockMap.has(mock.import.from)) {
mockMap.set(mock.import.from, [])
const mockMap = new Map<string, MockInfo[]>()
for (const mock of mocks) {
s.overwrite(mock.start, mock.end, '')
if (!mockMap.has(mock.import.from)) {
mockMap.set(mock.import.from, [])
}
mockMap.get(mock.import.from)!.push(mock)
}
mockMap.get(mock.import.from)!.push(mock)
}

const mockCode = [...mockMap.entries()]
.map(([from, mocks]) => {
const lines = [
`vi.mock(${JSON.stringify(from)}, async () => {`,
` const mod = { ...await vi.importActual(${JSON.stringify(
from
)}) }`,
]
for (const mock of mocks) {
lines.push(
` mod[${JSON.stringify(mock.name)}] = (${mock.factory})()`
)
}
lines.push(` return mod`)
lines.push(`})`)
return lines.join('\n')
})
.join('\n')
const mockCode = [...mockMap.entries()]
.map(([from, mocks]) => {
const lines = [
`vi.mock(${JSON.stringify(from)}, async (importOriginal) => {`,
` const mod = { ...await importOriginal() }`,
]
for (const mock of mocks) {
lines.push(
` mod[${JSON.stringify(mock.name)}] = (${mock.factory})()`
)
}
lines.push(` return mod`)
lines.push(`})`)
return lines.join('\n')
})
.join('\n')

s.append('\nimport {vi} from "vitest";\n' + mockCode)
s.prepend('\nimport {vi} from "vitest";\n' + mockCode + '\n\n')

return {
code: s.toString(),
map: s.generateMap(),
}
return {
code: s.toString(),
map: s.generateMap(),
}
},
},
})
},
Expand Down
4 changes: 1 addition & 3 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ export default defineNuxtConfig({
vitest: {
startOnBoot: true,
logToConsole: true,
vitestConfig: {
dir: 'tests/nuxt',
}
},
vite: {
// TODO: fix bug with stubbing root package
resolve: {
alias: {
'nuxt-vitest/config': '../packages/vitest-environment-nuxt/src/config',
'nuxt-vitest/utils': '../packages/vitest-environment-nuxt/src/utils',
'vitest-environment-nuxt/utils':
'../packages/vitest-environment-nuxt/src/utils',
'vitest-environment-nuxt':
Expand Down
5 changes: 2 additions & 3 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"test:nuxt": "vitest -c vitest.config.nuxt.ts",
"test:unit": "vitest -c vitest.config.unit.ts",
"test:unit": "vitest",
"test:dev": "NUXT_VITEST_DEV_TEST=true nuxt dev",
"test": "pnpm test:unit --run && pnpm test:nuxt --run && pnpm test:dev"
"test": "pnpm test:unit --run && pnpm test:dev"
},
"devDependencies": {
"@nuxt/devtools-edge": "0.0.0-27909847.46d1bc3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { defineConfigWithNuxt } from 'nuxt-vitest/config'

export default defineConfigWithNuxt({
test: {
name: 'nuxt',
dir: 'tests/nuxt',
dir: 'tests',
coverage: {
reportsDirectory: 'coverage/nuxt',
reportsDirectory: 'coverage',
},
},
})
11 changes: 0 additions & 11 deletions playground/vitest.config.unit.ts

This file was deleted.

Loading

0 comments on commit 3b8e16e

Please sign in to comment.