@jasonshimmy/vite-plugin-cer-app 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/IMPLEMENTATION_PLAN.md +1 -1
- package/README.md +7 -7
- package/VITE_PLUGIN_FRAMEWORK_PLAN.md +12 -12
- package/commits.txt +1 -3
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +21 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/create/index.js +2 -2
- package/dist/cli/create/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +10 -21
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/build-ssr.d.ts.map +1 -1
- package/dist/plugin/build-ssr.js +151 -28
- package/dist/plugin/build-ssr.js.map +1 -1
- package/dist/plugin/dts-generator.js +4 -4
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.js +3 -3
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/plugin/virtual/components.js +3 -3
- package/dist/plugin/virtual/components.js.map +1 -1
- package/dist/plugin/virtual/composables.js +3 -3
- package/dist/plugin/virtual/composables.js.map +1 -1
- package/dist/plugin/virtual/error.js +2 -2
- package/dist/plugin/virtual/error.js.map +1 -1
- package/dist/plugin/virtual/layouts.js +3 -3
- package/dist/plugin/virtual/layouts.js.map +1 -1
- package/dist/plugin/virtual/loading.js +2 -2
- package/dist/plugin/virtual/loading.js.map +1 -1
- package/dist/plugin/virtual/middleware.js +3 -3
- package/dist/plugin/virtual/middleware.js.map +1 -1
- package/dist/plugin/virtual/plugins.js +3 -3
- package/dist/plugin/virtual/plugins.js.map +1 -1
- package/dist/plugin/virtual/routes.d.ts.map +1 -1
- package/dist/plugin/virtual/routes.js +14 -4
- package/dist/plugin/virtual/routes.js.map +1 -1
- package/dist/plugin/virtual/server-api.js +3 -3
- package/dist/plugin/virtual/server-api.js.map +1 -1
- package/dist/plugin/virtual/server-middleware.js +3 -3
- package/dist/plugin/virtual/server-middleware.js.map +1 -1
- package/dist/runtime/app-template.d.ts +1 -1
- package/dist/runtime/app-template.d.ts.map +1 -1
- package/dist/runtime/app-template.js +6 -0
- package/dist/runtime/app-template.js.map +1 -1
- package/dist/runtime/composables/use-page-data.d.ts +15 -6
- package/dist/runtime/composables/use-page-data.d.ts.map +1 -1
- package/dist/runtime/composables/use-page-data.js +30 -9
- package/dist/runtime/composables/use-page-data.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +1 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +138 -17
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/docs/cli.md +2 -2
- package/docs/configuration.md +4 -4
- package/docs/data-loading.md +8 -7
- package/docs/getting-started.md +5 -5
- package/docs/head-management.md +3 -3
- package/docs/middleware.md +2 -2
- package/docs/plugins.md +1 -1
- package/docs/rendering-modes.md +1 -1
- package/docs/routing.md +1 -1
- package/docs/server-api.md +10 -1
- package/docs/testing.md +4 -4
- package/package.json +1 -1
- package/src/__tests__/index.test.ts +21 -0
- package/src/__tests__/plugin/build-ssg.test.ts +265 -0
- package/src/__tests__/plugin/build-ssr.test.ts +180 -0
- package/src/__tests__/plugin/cer-app-plugin.test.ts +409 -0
- package/src/__tests__/plugin/dts-generator.test.ts +246 -0
- package/src/__tests__/plugin/resolve-config.test.ts +158 -0
- package/src/__tests__/plugin/transforms/auto-import.test.ts +1 -1
- package/src/__tests__/plugin/virtual/components.test.ts +1 -1
- package/src/__tests__/plugin/virtual/composables.test.ts +1 -1
- package/src/__tests__/plugin/virtual/error.test.ts +71 -0
- package/src/__tests__/plugin/virtual/layouts.test.ts +1 -1
- package/src/__tests__/plugin/virtual/loading.test.ts +72 -0
- package/src/__tests__/plugin/virtual/middleware.test.ts +1 -1
- package/src/__tests__/plugin/virtual/plugins.test.ts +1 -1
- package/src/__tests__/plugin/virtual/routes.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-api.test.ts +1 -1
- package/src/__tests__/plugin/virtual/server-middleware.test.ts +102 -0
- package/src/__tests__/runtime/use-page-data.test.ts +81 -5
- package/src/__tests__/types/config.test.ts +23 -0
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/commands/preview.ts +21 -2
- package/src/cli/create/index.ts +2 -2
- package/src/cli/create/templates/spa/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/spa/package.json.tpl +1 -1
- package/src/cli/create/templates/ssg/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssg/package.json.tpl +1 -1
- package/src/cli/create/templates/ssr/cer.config.ts.tpl +1 -1
- package/src/cli/create/templates/ssr/package.json.tpl +1 -1
- package/src/cli/index.ts +1 -1
- package/src/plugin/build-ssg.ts +9 -22
- package/src/plugin/build-ssr.ts +150 -28
- package/src/plugin/dts-generator.ts +4 -4
- package/src/plugin/index.ts +2 -2
- package/src/plugin/transforms/auto-import.ts +3 -3
- package/src/plugin/virtual/components.ts +3 -3
- package/src/plugin/virtual/composables.ts +3 -3
- package/src/plugin/virtual/error.ts +2 -2
- package/src/plugin/virtual/layouts.ts +3 -3
- package/src/plugin/virtual/loading.ts +2 -2
- package/src/plugin/virtual/middleware.ts +3 -3
- package/src/plugin/virtual/plugins.ts +3 -3
- package/src/plugin/virtual/routes.ts +15 -4
- package/src/plugin/virtual/server-api.ts +3 -3
- package/src/plugin/virtual/server-middleware.ts +3 -3
- package/src/runtime/app-template.ts +6 -0
- package/src/runtime/composables/use-page-data.ts +31 -9
- package/src/runtime/entry-server-template.ts +138 -17
- package/tsconfig.build.json +1 -1
- package/dist/__tests__/plugin/path-utils.test.d.ts +0 -2
- package/dist/__tests__/plugin/path-utils.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/path-utils.test.js +0 -305
- package/dist/__tests__/plugin/path-utils.test.js.map +0 -1
- package/dist/__tests__/plugin/scanner.test.d.ts +0 -2
- package/dist/__tests__/plugin/scanner.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/scanner.test.js +0 -143
- package/dist/__tests__/plugin/scanner.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/auto-import.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/auto-import.test.js +0 -151
- package/dist/__tests__/plugin/transforms/auto-import.test.js.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts +0 -2
- package/dist/__tests__/plugin/transforms/head-inject.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/transforms/head-inject.test.js +0 -151
- package/dist/__tests__/plugin/transforms/head-inject.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/components.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/components.test.js +0 -47
- package/dist/__tests__/plugin/virtual/components.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/composables.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/composables.test.js +0 -48
- package/dist/__tests__/plugin/virtual/composables.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/layouts.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/layouts.test.js +0 -59
- package/dist/__tests__/plugin/virtual/layouts.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/middleware.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/middleware.test.js +0 -58
- package/dist/__tests__/plugin/virtual/middleware.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/plugins.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/plugins.test.js +0 -73
- package/dist/__tests__/plugin/virtual/plugins.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/routes.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/routes.test.js +0 -167
- package/dist/__tests__/plugin/virtual/routes.test.js.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts +0 -2
- package/dist/__tests__/plugin/virtual/server-api.test.d.ts.map +0 -1
- package/dist/__tests__/plugin/virtual/server-api.test.js +0 -72
- package/dist/__tests__/plugin/virtual/server-api.test.js.map +0 -1
- package/dist/__tests__/runtime/use-head.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-head.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-head.test.js +0 -202
- package/dist/__tests__/runtime/use-head.test.js.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.d.ts +0 -2
- package/dist/__tests__/runtime/use-page-data.test.d.ts.map +0 -1
- package/dist/__tests__/runtime/use-page-data.test.js +0 -41
- package/dist/__tests__/runtime/use-page-data.test.js.map +0 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'pathe'
|
|
4
|
+
|
|
5
|
+
// We test the server entry code generation by importing just that function.
|
|
6
|
+
// The `buildSSR` function itself invokes Vite's `build` API which we don't
|
|
7
|
+
// need to exercise in unit tests (it's an integration concern).
|
|
8
|
+
vi.mock('vite', () => ({ build: vi.fn().mockResolvedValue(undefined) }))
|
|
9
|
+
// Partial mock: keep the real readFileSync/existsSync but allow overrides in
|
|
10
|
+
// individual describe blocks if needed.
|
|
11
|
+
vi.mock('node:fs', async (importOriginal) => {
|
|
12
|
+
const actual = await importOriginal<typeof import('node:fs')>()
|
|
13
|
+
return { ...actual, existsSync: vi.fn().mockReturnValue(true) }
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
import type { ResolvedCerConfig } from '../../plugin/dev-server.js'
|
|
17
|
+
|
|
18
|
+
// Build a minimal ResolvedCerConfig so we can call generateServerEntryCode
|
|
19
|
+
// without spinning up a real Vite build.
|
|
20
|
+
function makeConfig(overrides: Partial<ResolvedCerConfig> = {}): ResolvedCerConfig {
|
|
21
|
+
return {
|
|
22
|
+
root: '/project',
|
|
23
|
+
srcDir: '/project/app',
|
|
24
|
+
mode: 'ssr',
|
|
25
|
+
ssr: { dsd: true },
|
|
26
|
+
ssg: { paths: [], concurrency: 4 },
|
|
27
|
+
...overrides,
|
|
28
|
+
} as unknown as ResolvedCerConfig
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('build-ssr generateServerEntryCode (template content)', () => {
|
|
32
|
+
// Read the source of build-ssr.ts to assert it contains the expected
|
|
33
|
+
// generated code strings. This is intentionally coarse-grained:
|
|
34
|
+
// we check that the template emits the right imports, exports, and
|
|
35
|
+
// structural elements rather than testing every character.
|
|
36
|
+
const src = readFileSync(
|
|
37
|
+
resolve(import.meta.dirname, '../../plugin/build-ssr.ts'),
|
|
38
|
+
'utf-8',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
it('template imports registerBuiltinComponents from custom-elements-runtime', () => {
|
|
42
|
+
expect(src).toContain('registerBuiltinComponents')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('template imports renderToStringWithJITCSS from ssr subpath', () => {
|
|
46
|
+
expect(src).toContain('renderToStringWithJITCSS')
|
|
47
|
+
expect(src).toContain('custom-elements-runtime/ssr')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('template imports initRouter from router subpath', () => {
|
|
51
|
+
expect(src).toContain('initRouter')
|
|
52
|
+
expect(src).toContain('custom-elements-runtime/router')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('template loads client index.html for merging', () => {
|
|
56
|
+
expect(src).toContain('_clientTemplate')
|
|
57
|
+
expect(src).toContain('../client/index.html')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('template defines _mergeWithClientTemplate helper', () => {
|
|
61
|
+
expect(src).toContain('_mergeWithClientTemplate')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('template defines _prepareRequest async function', () => {
|
|
65
|
+
expect(src).toContain('_prepareRequest')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('template exports handler as both named and default export', () => {
|
|
69
|
+
expect(src).toContain('export const handler')
|
|
70
|
+
expect(src).toContain('export default handler')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('template exports apiRoutes, plugins, and layouts', () => {
|
|
74
|
+
expect(src).toContain('export { apiRoutes, plugins, layouts }')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('template sets globalThis.__CER_DATA__ synchronously before render', () => {
|
|
78
|
+
expect(src).toContain('globalThis).__CER_DATA__ = loaderData')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('template deletes __CER_DATA__ after render', () => {
|
|
82
|
+
expect(src).toContain('delete (globalThis).__CER_DATA__')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('template uses renderToStringWithJITCSSDSD (dsd always on)', () => {
|
|
86
|
+
expect(src).toContain('renderToStringWithJITCSSDSD')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('template passes dsdPolyfill: false to suppress inline polyfill', () => {
|
|
90
|
+
expect(src).toContain('dsdPolyfill: false')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('template calls registerEntityMap with entities.json', () => {
|
|
94
|
+
expect(src).toContain('registerEntityMap(entitiesJson)')
|
|
95
|
+
expect(src).toContain('entities.json')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('template imports DSD_POLYFILL_SCRIPT and injects before </body>', () => {
|
|
99
|
+
expect(src).toContain('DSD_POLYFILL_SCRIPT')
|
|
100
|
+
expect(src).toContain("finalHtml.replace('</body>'")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('template merges SSR html with client template when available', () => {
|
|
104
|
+
expect(src).toContain('_clientTemplate')
|
|
105
|
+
expect(src).toContain('_mergeWithClientTemplate(ssrHtml, _clientTemplate)')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('template reads virtual:cer-routes', () => {
|
|
109
|
+
expect(src).toContain('virtual:cer-routes')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('template reads virtual:cer-layouts', () => {
|
|
113
|
+
expect(src).toContain('virtual:cer-layouts')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('template reads virtual:cer-plugins', () => {
|
|
117
|
+
expect(src).toContain('virtual:cer-plugins')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('template reads virtual:cer-server-api', () => {
|
|
121
|
+
expect(src).toContain('virtual:cer-server-api')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('template reads virtual:cer-components', () => {
|
|
125
|
+
expect(src).toContain('virtual:cer-components')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('sets Content-Type header on response', () => {
|
|
129
|
+
expect(src).toContain('text/html; charset=utf-8')
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('buildSSR', () => {
|
|
134
|
+
let buildMock: ReturnType<typeof vi.fn>
|
|
135
|
+
let buildSSR: (config: ResolvedCerConfig, userConfig?: Record<string, unknown>) => Promise<void>
|
|
136
|
+
|
|
137
|
+
beforeEach(async () => {
|
|
138
|
+
const { build } = await import('vite')
|
|
139
|
+
buildMock = vi.mocked(build)
|
|
140
|
+
buildMock.mockClear()
|
|
141
|
+
buildMock.mockResolvedValue(undefined as never)
|
|
142
|
+
;({ buildSSR } = await import('../../plugin/build-ssr.js'))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('calls vite build twice (client then server)', async () => {
|
|
146
|
+
await buildSSR(makeConfig())
|
|
147
|
+
expect(buildMock).toHaveBeenCalledTimes(2)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('first build targets dist/client output dir', async () => {
|
|
151
|
+
await buildSSR(makeConfig())
|
|
152
|
+
const firstCall = buildMock.mock.calls[0][0] as Record<string, unknown>
|
|
153
|
+
expect((firstCall.build as Record<string, unknown>).outDir).toContain('dist/client')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('second build targets dist/server output dir', async () => {
|
|
157
|
+
await buildSSR(makeConfig())
|
|
158
|
+
const secondCall = buildMock.mock.calls[1][0] as Record<string, unknown>
|
|
159
|
+
expect((secondCall.build as Record<string, unknown>).outDir).toContain('dist/server')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('second build has ssr:true', async () => {
|
|
163
|
+
await buildSSR(makeConfig())
|
|
164
|
+
const secondCall = buildMock.mock.calls[1][0] as Record<string, unknown>
|
|
165
|
+
expect((secondCall.build as Record<string, unknown>).ssr).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('server bundle entry file is named server.js', async () => {
|
|
169
|
+
await buildSSR(makeConfig())
|
|
170
|
+
const secondCall = buildMock.mock.calls[1][0] as Record<string, unknown>
|
|
171
|
+
const rollup = (secondCall.build as Record<string, unknown>).rollupOptions as Record<string, unknown>
|
|
172
|
+
expect((rollup.output as Record<string, unknown>).entryFileNames).toBe('server.js')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('merges user viteUserConfig into client build', async () => {
|
|
176
|
+
await buildSSR(makeConfig(), { define: { MY_FLAG: 'true' } })
|
|
177
|
+
const firstCall = buildMock.mock.calls[0][0] as Record<string, unknown>
|
|
178
|
+
expect(firstCall.define).toEqual({ MY_FLAG: 'true' })
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('@jasonshimmy/custom-elements-runtime/vite-plugin', () => ({
|
|
4
|
+
cerPlugin: vi.fn().mockReturnValue([{ name: 'cer-runtime-plugin' }]),
|
|
5
|
+
}))
|
|
6
|
+
vi.mock('../../plugin/dev-server.js', () => ({
|
|
7
|
+
configureCerDevServer: vi.fn().mockResolvedValue(undefined),
|
|
8
|
+
}))
|
|
9
|
+
vi.mock('../../plugin/scanner.js', () => ({
|
|
10
|
+
createWatcher: vi.fn().mockReturnValue({ on: vi.fn(), close: vi.fn() }),
|
|
11
|
+
scanDirectory: vi.fn().mockResolvedValue([]),
|
|
12
|
+
}))
|
|
13
|
+
vi.mock('../../plugin/dts-generator.js', () => ({
|
|
14
|
+
scanComposableExports: vi.fn().mockResolvedValue(new Map()),
|
|
15
|
+
writeAutoImportDts: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
writeTsconfigPaths: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
vi.mock('../../plugin/virtual/routes.js', () => ({ generateRoutesCode: vi.fn().mockResolvedValue('// routes') }))
|
|
19
|
+
vi.mock('../../plugin/virtual/layouts.js', () => ({ generateLayoutsCode: vi.fn().mockResolvedValue('// layouts') }))
|
|
20
|
+
vi.mock('../../plugin/virtual/components.js', () => ({ generateComponentsCode: vi.fn().mockResolvedValue('// components') }))
|
|
21
|
+
vi.mock('../../plugin/virtual/composables.js', () => ({ generateComposablesCode: vi.fn().mockResolvedValue('// composables') }))
|
|
22
|
+
vi.mock('../../plugin/virtual/plugins.js', () => ({ generatePluginsCode: vi.fn().mockResolvedValue('// plugins') }))
|
|
23
|
+
vi.mock('../../plugin/virtual/middleware.js', () => ({ generateMiddlewareCode: vi.fn().mockResolvedValue('// middleware') }))
|
|
24
|
+
vi.mock('../../plugin/virtual/server-api.js', () => ({ generateServerApiCode: vi.fn().mockResolvedValue('// server-api') }))
|
|
25
|
+
vi.mock('../../plugin/virtual/server-middleware.js', () => ({ generateServerMiddlewareCode: vi.fn().mockResolvedValue('// server-middleware') }))
|
|
26
|
+
vi.mock('../../plugin/virtual/loading.js', () => ({ generateLoadingCode: vi.fn().mockResolvedValue('// loading') }))
|
|
27
|
+
vi.mock('../../plugin/virtual/error.js', () => ({ generateErrorCode: vi.fn().mockResolvedValue('// error') }))
|
|
28
|
+
vi.mock('../../plugin/transforms/auto-import.js', () => ({ autoImportTransform: vi.fn().mockReturnValue(null) }))
|
|
29
|
+
|
|
30
|
+
import { cerApp } from '../../plugin/index.js'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type TestPlugin = {
|
|
34
|
+
name: string
|
|
35
|
+
config: (viteConfig: Record<string, unknown>, env: Record<string, unknown>) => unknown
|
|
36
|
+
configResolved: (resolved: Record<string, unknown>) => void
|
|
37
|
+
resolveId: (id: string) => string | undefined
|
|
38
|
+
load: (id: string) => Promise<string | null>
|
|
39
|
+
transform: (code: string, id: string) => unknown
|
|
40
|
+
buildStart: () => Promise<void>
|
|
41
|
+
configureServer: (server: unknown) => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Helper to get the cerAppPlugin (first plugin in the returned array)
|
|
45
|
+
function getCerPlugin(userConfig = {}): TestPlugin {
|
|
46
|
+
return cerApp(userConfig)[0] as unknown as TestPlugin
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Minimal resolved config that mirrors what Vite passes to configResolved
|
|
50
|
+
const FAKE_RESOLVED = { root: '/project' }
|
|
51
|
+
|
|
52
|
+
describe('cerApp()', () => {
|
|
53
|
+
it('returns an array of plugins', () => {
|
|
54
|
+
const plugins = cerApp()
|
|
55
|
+
expect(Array.isArray(plugins)).toBe(true)
|
|
56
|
+
expect(plugins.length).toBeGreaterThan(0)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('first plugin is named @jasonshimmy/vite-plugin-cer-app', () => {
|
|
60
|
+
const plugin = getCerPlugin()
|
|
61
|
+
expect(plugin.name).toBe('@jasonshimmy/vite-plugin-cer-app')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('cerApp plugin — config hook', () => {
|
|
66
|
+
it('returns build.target: esnext', () => {
|
|
67
|
+
const plugin = getCerPlugin()
|
|
68
|
+
const result = plugin.config({ root: '/project' }, { command: 'build', mode: 'production' }) as Record<string, unknown>
|
|
69
|
+
expect((result.build as Record<string, unknown>).target).toBe('esnext')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('config hook resolves the root', () => {
|
|
73
|
+
const plugin = getCerPlugin()
|
|
74
|
+
const result = plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
75
|
+
expect(result).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('cerApp plugin — configResolved hook', () => {
|
|
80
|
+
it('does not throw when called', () => {
|
|
81
|
+
const plugin = getCerPlugin()
|
|
82
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
83
|
+
expect(() => plugin.configResolved(FAKE_RESOLVED)).not.toThrow()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('cerApp plugin — resolveId hook', () => {
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
const plugin = getCerPlugin()
|
|
90
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
91
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('resolves virtual:cer-routes to \\0virtual:cer-routes', () => {
|
|
95
|
+
const plugin = getCerPlugin()
|
|
96
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
97
|
+
expect(plugin.resolveId('virtual:cer-routes')).toBe('\0virtual:cer-routes')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('resolves virtual:cer-layouts', () => {
|
|
101
|
+
const plugin = getCerPlugin()
|
|
102
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
103
|
+
expect(plugin.resolveId('virtual:cer-layouts')).toBe('\0virtual:cer-layouts')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('resolves virtual:cer-components', () => {
|
|
107
|
+
const plugin = getCerPlugin()
|
|
108
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
109
|
+
expect(plugin.resolveId('virtual:cer-components')).toBe('\0virtual:cer-components')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('resolves virtual:cer-plugins', () => {
|
|
113
|
+
const plugin = getCerPlugin()
|
|
114
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
115
|
+
expect(plugin.resolveId('virtual:cer-plugins')).toBe('\0virtual:cer-plugins')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('resolves virtual:cer-server-api', () => {
|
|
119
|
+
const plugin = getCerPlugin()
|
|
120
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
121
|
+
expect(plugin.resolveId('virtual:cer-server-api')).toBe('\0virtual:cer-server-api')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('resolves virtual:cer-loading', () => {
|
|
125
|
+
const plugin = getCerPlugin()
|
|
126
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
127
|
+
expect(plugin.resolveId('virtual:cer-loading')).toBe('\0virtual:cer-loading')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('resolves virtual:cer-error', () => {
|
|
131
|
+
const plugin = getCerPlugin()
|
|
132
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
133
|
+
expect(plugin.resolveId('virtual:cer-error')).toBe('\0virtual:cer-error')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('returns undefined for unknown ids', () => {
|
|
137
|
+
const plugin = getCerPlugin()
|
|
138
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
139
|
+
expect(plugin.resolveId('some-unknown-id')).toBeUndefined()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('cerApp plugin — load hook', () => {
|
|
144
|
+
it('returns null for unknown resolved ids', async () => {
|
|
145
|
+
const plugin = getCerPlugin()
|
|
146
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
147
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
148
|
+
const result = await plugin.load('\0unknown-module')
|
|
149
|
+
expect(result).toBeNull()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('loads virtual:cer-routes module code', async () => {
|
|
153
|
+
const plugin = getCerPlugin()
|
|
154
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
155
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
156
|
+
const result = await plugin.load('\0virtual:cer-routes')
|
|
157
|
+
expect(result).toBe('// routes')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('loads virtual:cer-layouts module code', async () => {
|
|
161
|
+
const plugin = getCerPlugin()
|
|
162
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
163
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
164
|
+
const result = await plugin.load('\0virtual:cer-layouts')
|
|
165
|
+
expect(result).toBe('// layouts')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('loads virtual:cer-components module code', async () => {
|
|
169
|
+
const plugin = getCerPlugin()
|
|
170
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
171
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
172
|
+
const result = await plugin.load('\0virtual:cer-components')
|
|
173
|
+
expect(result).toBe('// components')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('loads virtual:cer-loading module code', async () => {
|
|
177
|
+
const plugin = getCerPlugin()
|
|
178
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
179
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
180
|
+
const result = await plugin.load('\0virtual:cer-loading')
|
|
181
|
+
expect(result).toBe('// loading')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('loads virtual:cer-error module code', async () => {
|
|
185
|
+
const plugin = getCerPlugin()
|
|
186
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
187
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
188
|
+
const result = await plugin.load('\0virtual:cer-error')
|
|
189
|
+
expect(result).toBe('// error')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('returns cached code on repeated load calls', async () => {
|
|
193
|
+
const { generateRoutesCode } = await import('../../plugin/virtual/routes.js')
|
|
194
|
+
vi.mocked(generateRoutesCode).mockClear()
|
|
195
|
+
|
|
196
|
+
// Use a SINGLE plugin instance so the module cache persists across calls
|
|
197
|
+
const plugin = getCerPlugin()
|
|
198
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
199
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
200
|
+
|
|
201
|
+
const first = await plugin.load('\0virtual:cer-routes')
|
|
202
|
+
const second = await plugin.load('\0virtual:cer-routes')
|
|
203
|
+
|
|
204
|
+
expect(first).toBe(second)
|
|
205
|
+
// generateRoutesCode should only be called once (cache hit on second call)
|
|
206
|
+
expect(generateRoutesCode).toHaveBeenCalledTimes(1)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('loads virtual:cer-app-config and exports appConfig', async () => {
|
|
210
|
+
const plugin = getCerPlugin({ mode: 'ssg' })
|
|
211
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
212
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
213
|
+
const result = await plugin.load('\0virtual:cer-app-config') as string
|
|
214
|
+
expect(result).toContain('appConfig')
|
|
215
|
+
expect(result).toContain('ssg')
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('cerApp plugin — transform hook', () => {
|
|
220
|
+
it('returns null for virtual module ids', async () => {
|
|
221
|
+
const plugin = getCerPlugin()
|
|
222
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
223
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
224
|
+
const result = plugin.transform('// code', '\0virtual:cer-routes')
|
|
225
|
+
expect(result).toBeNull()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('calls autoImportTransform for regular files', async () => {
|
|
229
|
+
const { autoImportTransform } = await import('../../plugin/transforms/auto-import.js')
|
|
230
|
+
const plugin = getCerPlugin()
|
|
231
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
232
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
233
|
+
|
|
234
|
+
plugin.transform('const x = 1', '/project/app/pages/index.ts')
|
|
235
|
+
expect(autoImportTransform).toHaveBeenCalled()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('returns null when autoImportTransform returns null', async () => {
|
|
239
|
+
const plugin = getCerPlugin()
|
|
240
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
241
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
242
|
+
const result = plugin.transform('const x = 1', '/project/app/pages/index.ts')
|
|
243
|
+
expect(result).toBeNull()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('returns { code, map } when autoImportTransform returns a string', async () => {
|
|
247
|
+
const { autoImportTransform } = await import('../../plugin/transforms/auto-import.js')
|
|
248
|
+
vi.mocked(autoImportTransform).mockReturnValueOnce('transformed code')
|
|
249
|
+
const plugin = getCerPlugin()
|
|
250
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
251
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
252
|
+
const result = plugin.transform('const x = 1', '/project/app/pages/index.ts') as { code: string; map: null }
|
|
253
|
+
expect(result).toEqual({ code: 'transformed code', map: null })
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('returns null when autoImports.runtime is false', async () => {
|
|
257
|
+
const plugin = getCerPlugin({ autoImports: { runtime: false } })
|
|
258
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
259
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
260
|
+
const result = plugin.transform('const x = 1', '/project/app/pages/index.ts')
|
|
261
|
+
expect(result).toBeNull()
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('cerApp plugin — buildStart hook', () => {
|
|
266
|
+
it('calls scanComposableExports on build start', async () => {
|
|
267
|
+
const { scanComposableExports } = await import('../../plugin/dts-generator.js')
|
|
268
|
+
vi.mocked(scanComposableExports).mockClear()
|
|
269
|
+
const plugin = getCerPlugin()
|
|
270
|
+
plugin.config({ root: '/project' }, { command: 'build', mode: 'production' })
|
|
271
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
272
|
+
await plugin.buildStart()
|
|
273
|
+
expect(scanComposableExports).toHaveBeenCalledTimes(1)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('calls writeAutoImportDts on build start', async () => {
|
|
277
|
+
const { writeAutoImportDts } = await import('../../plugin/dts-generator.js')
|
|
278
|
+
vi.mocked(writeAutoImportDts).mockClear()
|
|
279
|
+
const plugin = getCerPlugin()
|
|
280
|
+
plugin.config({ root: '/project' }, { command: 'build', mode: 'production' })
|
|
281
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
282
|
+
await plugin.buildStart()
|
|
283
|
+
expect(writeAutoImportDts).toHaveBeenCalledTimes(1)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('calls writeTsconfigPaths on build start', async () => {
|
|
287
|
+
const { writeTsconfigPaths } = await import('../../plugin/dts-generator.js')
|
|
288
|
+
vi.mocked(writeTsconfigPaths).mockClear()
|
|
289
|
+
const plugin = getCerPlugin()
|
|
290
|
+
plugin.config({ root: '/project' }, { command: 'build', mode: 'production' })
|
|
291
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
292
|
+
await plugin.buildStart()
|
|
293
|
+
expect(writeTsconfigPaths).toHaveBeenCalledTimes(1)
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
describe('cerApp plugin — configureServer hook', () => {
|
|
298
|
+
it('calls scanComposableExports on server configure', async () => {
|
|
299
|
+
const { scanComposableExports } = await import('../../plugin/dts-generator.js')
|
|
300
|
+
vi.mocked(scanComposableExports).mockClear()
|
|
301
|
+
const plugin = getCerPlugin()
|
|
302
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
303
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
304
|
+
|
|
305
|
+
const mockServer = {
|
|
306
|
+
watcher: { on: vi.fn() },
|
|
307
|
+
moduleGraph: { getModuleById: vi.fn().mockReturnValue(null), invalidateModule: vi.fn() },
|
|
308
|
+
ws: { send: vi.fn() },
|
|
309
|
+
}
|
|
310
|
+
await plugin.configureServer(mockServer)
|
|
311
|
+
expect(scanComposableExports).toHaveBeenCalled()
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('calls configureCerDevServer on server configure', async () => {
|
|
315
|
+
const { configureCerDevServer } = await import('../../plugin/dev-server.js')
|
|
316
|
+
vi.mocked(configureCerDevServer).mockClear()
|
|
317
|
+
const plugin = getCerPlugin()
|
|
318
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
319
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
320
|
+
|
|
321
|
+
const mockServer = {
|
|
322
|
+
watcher: { on: vi.fn() },
|
|
323
|
+
moduleGraph: { getModuleById: vi.fn().mockReturnValue(null), invalidateModule: vi.fn() },
|
|
324
|
+
ws: { send: vi.fn() },
|
|
325
|
+
}
|
|
326
|
+
await plugin.configureServer(mockServer)
|
|
327
|
+
expect(configureCerDevServer).toHaveBeenCalled()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('invokes the file-change watcher callback on add event', async () => {
|
|
331
|
+
const { createWatcher } = await import('../../plugin/scanner.js')
|
|
332
|
+
const { scanComposableExports } = await import('../../plugin/dts-generator.js')
|
|
333
|
+
vi.mocked(scanComposableExports).mockClear()
|
|
334
|
+
|
|
335
|
+
let capturedCallback: ((event: string, file: string) => void) | null = null
|
|
336
|
+
vi.mocked(createWatcher).mockImplementationOnce((_watcher, _dirs, cb) => {
|
|
337
|
+
capturedCallback = cb
|
|
338
|
+
return { on: vi.fn(), close: vi.fn() } as unknown as ReturnType<typeof createWatcher>
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const plugin = getCerPlugin()
|
|
342
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
343
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
344
|
+
|
|
345
|
+
const mockServer = {
|
|
346
|
+
watcher: { on: vi.fn() },
|
|
347
|
+
moduleGraph: { getModuleById: vi.fn().mockReturnValue(null), invalidateModule: vi.fn() },
|
|
348
|
+
ws: { send: vi.fn() },
|
|
349
|
+
}
|
|
350
|
+
await plugin.configureServer(mockServer)
|
|
351
|
+
|
|
352
|
+
// Simulate an 'add' event on a pages file — covers getDirtyVirtualIds and watcher callback
|
|
353
|
+
await capturedCallback!('add', '/project/app/pages/new-page.ts')
|
|
354
|
+
expect((mockServer.ws.send as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith({ type: 'full-reload' })
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('re-scans composables when a composable file is added', async () => {
|
|
358
|
+
const { createWatcher } = await import('../../plugin/scanner.js')
|
|
359
|
+
const { scanComposableExports } = await import('../../plugin/dts-generator.js')
|
|
360
|
+
vi.mocked(scanComposableExports).mockClear()
|
|
361
|
+
|
|
362
|
+
let capturedCallback: ((event: string, file: string) => void) | null = null
|
|
363
|
+
vi.mocked(createWatcher).mockImplementationOnce((_watcher, _dirs, cb) => {
|
|
364
|
+
capturedCallback = cb
|
|
365
|
+
return { on: vi.fn(), close: vi.fn() } as unknown as ReturnType<typeof createWatcher>
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const plugin = getCerPlugin()
|
|
369
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
370
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
371
|
+
|
|
372
|
+
const mockServer = {
|
|
373
|
+
watcher: { on: vi.fn() },
|
|
374
|
+
moduleGraph: { getModuleById: vi.fn().mockReturnValue(null), invalidateModule: vi.fn() },
|
|
375
|
+
ws: { send: vi.fn() },
|
|
376
|
+
}
|
|
377
|
+
await plugin.configureServer(mockServer)
|
|
378
|
+
|
|
379
|
+
const callsBeforeEvent = vi.mocked(scanComposableExports).mock.calls.length
|
|
380
|
+
await capturedCallback!('add', '/project/app/composables/use-new.ts')
|
|
381
|
+
expect(vi.mocked(scanComposableExports).mock.calls.length).toBeGreaterThan(callsBeforeEvent)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('does not trigger HMR on non-add/unlink events', async () => {
|
|
385
|
+
const { createWatcher } = await import('../../plugin/scanner.js')
|
|
386
|
+
|
|
387
|
+
let capturedCallback: ((event: string, file: string) => void) | null = null
|
|
388
|
+
vi.mocked(createWatcher).mockImplementationOnce((_watcher, _dirs, cb) => {
|
|
389
|
+
capturedCallback = cb
|
|
390
|
+
return { on: vi.fn(), close: vi.fn() } as unknown as ReturnType<typeof createWatcher>
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const plugin = getCerPlugin()
|
|
394
|
+
plugin.config({ root: '/project' }, { command: 'serve', mode: 'development' })
|
|
395
|
+
plugin.configResolved(FAKE_RESOLVED)
|
|
396
|
+
|
|
397
|
+
const wsSend = vi.fn()
|
|
398
|
+
const mockServer = {
|
|
399
|
+
watcher: { on: vi.fn() },
|
|
400
|
+
moduleGraph: { getModuleById: vi.fn().mockReturnValue(null), invalidateModule: vi.fn() },
|
|
401
|
+
ws: { send: wsSend },
|
|
402
|
+
}
|
|
403
|
+
await plugin.configureServer(mockServer)
|
|
404
|
+
wsSend.mockClear()
|
|
405
|
+
|
|
406
|
+
await capturedCallback!('change', '/project/app/pages/index.ts')
|
|
407
|
+
expect(wsSend).not.toHaveBeenCalled()
|
|
408
|
+
})
|
|
409
|
+
})
|