Select the types of activity you want to include in your feed.
[READ-ONLY] Mirror of https://github.com/danielroe/nuxt-vitest. An vitest environment with support for testing code that needs a Nuxt runtime environment
···42424343This means you should be take particular care not to mutate the global state in your tests (or, if you have, to reset it afterwards).
44444545+## 🛠️ Helpers
4646+4747+`vitest-environment-nuxt` provides a number of helpers to make testing Nuxt apps easier.
4848+4949+### `mountSuspended`
5050+5151+// TODO:
5252+5353+### `mockNuxtImport`
5454+5555+`mockNuxtImport` allows you to mock Nuxt's auto import functionality. For example, to mock `useStorage`, you can do so like this:
5656+5757+```ts
5858+import { mockNuxtImport } from 'vitest-environment-nuxt/utils'
5959+6060+mockNuxtImport('useStorage', () => {
6161+ return () => {
6262+ return { value: 'mocked storage' }
6363+ }
6464+})
6565+6666+// your tests here
6767+```
6868+4569## 💻 Development
46704771- Clone this repository
···11-export { registerEndpoint } from './runtime/mock'
11+export { registerEndpoint, mockNuxtImport } from './runtime/mock'
22export { mountSuspended } from './runtime/mount'
+7
playground/composables/auto-import-mock.ts
···11+export function useAutoImportedTarget() {
22+ return 'the original'
33+}
44+55+export function useAutoImportedNonTarget() {
66+ return 'the original'
77+}
···11+import { expect, it } from 'vitest'
22+33+it('should not mock', () => {
44+ expect(useAutoImportedTarget()).toMatchInlineSnapshot('"the original"')
55+ expect(useAutoImportedNonTarget()).toMatchInlineSnapshot('"the original"')
66+})
+127
src/modules/auto-import-mock.ts
···11+import type { Import } from 'unimport'
22+import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
33+import { walk } from 'estree-walker'
44+import type { CallExpression } from 'estree'
55+import { AcornNode } from 'rollup'
66+import MagicString from 'magic-string'
77+88+const HELPER_NAME = 'mockNuxtImport'
99+1010+export interface MockInfo {
1111+ name: string
1212+ import: Import
1313+ factory: string
1414+ start: number
1515+ end: number
1616+}
1717+1818+/**
1919+ * This module is a macro that transforms `mockNuxtImport()` to `vi.mock()`,
2020+ * which make it possible to mock Nuxt imports.
2121+ */
2222+export default defineNuxtModule({
2323+ meta: {
2424+ name: 'vitest:auto-import-mock',
2525+ },
2626+ setup(_, nuxt) {
2727+ let imports: Import[] = []
2828+2929+ nuxt.hook('imports:extend', _ => {
3030+ imports = _
3131+ })
3232+3333+ addVitePlugin({
3434+ name: 'nuxt:auto-import-mock',
3535+ transform(code, id) {
3636+ if (!code.includes(HELPER_NAME)) return
3737+ if (id.includes('/node_modules/')) return
3838+3939+ let ast: AcornNode
4040+ try {
4141+ ast = this.parse(code, {
4242+ sourceType: 'module',
4343+ ecmaVersion: 'latest',
4444+ ranges: true,
4545+ })
4646+ } catch (e) {
4747+ return
4848+ }
4949+5050+ const mocks: MockInfo[] = []
5151+5252+ walk(ast, {
5353+ enter: node => {
5454+ if (node.type !== 'CallExpression') return
5555+ const call = node as CallExpression
5656+ if (
5757+ call.callee.type !== 'Identifier' ||
5858+ call.callee.name !== HELPER_NAME
5959+ ) {
6060+ return
6161+ }
6262+ if (call.arguments.length !== 2) {
6363+ return
6464+ }
6565+ if (call.arguments[0].type !== 'Literal') {
6666+ return // TODO: warn
6767+ }
6868+ const name = call.arguments[0].value as string
6969+ const importItem = imports.find(_ => name === (_.as || _.name))
7070+ if (!importItem) {
7171+ return this.error(`Cannot find import "${name}" to mock`)
7272+ }
7373+ mocks.push({
7474+ name,
7575+ import: importItem,
7676+ factory: code.slice(
7777+ call.arguments[1].range![0],
7878+ call.arguments[1].range![1]
7979+ ),
8080+ start: call.range![0],
8181+ end: call.range![1],
8282+ })
8383+ },
8484+ })
8585+8686+ if (!mocks.length) return
8787+8888+ const s = new MagicString(code)
8989+9090+ const mockMap = new Map<string, MockInfo[]>()
9191+ for (const mock of mocks) {
9292+ s.overwrite(mock.start, mock.end, '')
9393+ if (!mockMap.has(mock.import.from)) {
9494+ mockMap.set(mock.import.from, [])
9595+ }
9696+ mockMap.get(mock.import.from)!.push(mock)
9797+ }
9898+9999+ const mockCode = [...mockMap.entries()]
100100+ .map(([from, mocks]) => {
101101+ const lines = [
102102+ `vi.mock(${JSON.stringify(from)}, async () => {`,
103103+ ` const mod = { ...await vi.importActual(${JSON.stringify(
104104+ from
105105+ )}) }`,
106106+ ]
107107+ for (const mock of mocks) {
108108+ lines.push(
109109+ ` mod[${JSON.stringify(mock.name)}] = (${mock.factory})()`
110110+ )
111111+ }
112112+ lines.push(` return mod`)
113113+ lines.push(`})`)
114114+ return lines.join('\n')
115115+ })
116116+ .join('\n')
117117+118118+ s.append('\nimport {vi} from "vitest";\n' + mockCode)
119119+120120+ return {
121121+ code: s.toString(),
122122+ map: s.generateMap(),
123123+ }
124124+ },
125125+ })
126126+ },
127127+})
+7
src/runtime/mock.ts
···99 // @ts-expect-error private property
1010 window.__registry.add(url)
1111}
1212+1313+export function mockNuxtImport<T = any>(
1414+ name: string,
1515+ factory: () => T | Promise<T>
1616+) {
1717+ throw new Error('mockNuxtImport() is a macro and it did not get transpiled')
1818+}