Skip to content

Commit

Permalink
chore: refactor script injector
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed May 3, 2024
1 parent 0f37793 commit 6451418
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 25 deletions.
49 changes: 49 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,55 @@ This option has no effect on tests running inside Node.js.
If you rely on spying on ES modules with `vi.spyOn`, you can enable this experimental feature to allow spying on module exports.
#### browser.indexScripts <Version>1.6.0</Version> {#browser-indexscripts}
- **Type:** `BrowserScript[]`
- **Default:** `[]`
Custom scripts that should be injected into the index HTML before test iframes are initiated. This HTML document only sets up iframes and doesn't actually import your code.
The script `src` and `content` will be processed by Vite plugins. Script should be provided in the following shape:
```ts
export interface BrowserScript {
/**
* If "content" is provided and type is "module", this will be its identifier.
*
* If you are using TypeScript, you can add `.ts` extension here for example.
* @default `injected-${index}.js`
*/
id?: string
/**
* JavaScript content to be injected. This string is processed by Vite plugins if type is "module".
*
* You can use `id` to give Vite a hint about the file extension.
*/
content?: string
/**
* Path to the script. This value is resolved by Vite so it can be a node module or a file path.
*/
src?: string
/**
* If the script should be loaded asynchronously.
*/
async?: boolean
/**
* Script type.
* @default 'module'
*/
type?: string
}
```
#### browser.testerScripts <Version>1.6.0</Version> {#browser-testerscripts}
- **Type:** `BrowserScript[]`
- **Default:** `[]`
Custom scripts that should be injected into the tester HTML before the tests environment is initiated. This is useful to inject polyfills required for Vitest browser implementation. It is recommended to use [`setupFiles`](#setupfiles) in almost all cases instead of this.
The script `src` and `content` will be processed by Vite plugins.
### clearMocks
- **Type:** `boolean`
Expand Down
31 changes: 23 additions & 8 deletions packages/browser/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fileURLToPath } from 'node:url'
import { readFile } from 'node:fs/promises'
import { basename, resolve } from 'pathe'
import { basename, join, resolve } from 'pathe'
import sirv from 'sirv'
import type { Plugin } from 'vite'
import type { Plugin, ViteDevServer } from 'vite'
import type { ResolvedConfig } from 'vitest'
import type { BrowserScript, WorkspaceProject } from 'vitest/node'
import { coverageConfigDefaults } from 'vitest/config'
Expand Down Expand Up @@ -37,6 +37,8 @@ export default (project: WorkspaceProject, base = '/'): Plugin[] => {
}
next()
})
let indexScripts: string | undefined
let testerScripts: string | undefined
server.middlewares.use(async (req, res, next) => {
if (!req.url)
return next()
Expand All @@ -59,10 +61,13 @@ export default (project: WorkspaceProject, base = '/'): Plugin[] => {
})

if (url.pathname === base) {
if (!indexScripts)
indexScripts = await formatScripts(project.config.browser.indexScripts, server)

const html = replacer(await runnerHtml, {
__VITEST_FAVICON__: favicon,
__VITEST_TITLE__: 'Vitest Browser Runner',
__VITEST_SCRIPTS__: formatScripts(project.config.browser.indexScripts),
__VITEST_SCRIPTS__: indexScripts,
__VITEST_INJECTOR__: injector,
})
res.write(html, 'utf-8')
Expand All @@ -74,10 +79,13 @@ export default (project: WorkspaceProject, base = '/'): Plugin[] => {
// if decoded test file is "__vitest_all__" or not in the list of known files, run all tests
const tests = decodedTestFile === '__vitest_all__' || !files.includes(decodedTestFile) ? '__vitest_browser_runner__.files' : JSON.stringify([decodedTestFile])

if (!testerScripts)
testerScripts = await formatScripts(project.config.browser.testerScripts, server)

const html = replacer(await testerHtml, {
__VITEST_FAVICON__: favicon,
__VITEST_TITLE__: 'Vitest Browser Tester',
__VITEST_SCRIPTS__: formatScripts(project.config.browser.testerScripts),
__VITEST_SCRIPTS__: testerScripts,
__VITEST_INJECTOR__: injector,
__VITEST_APPEND__:
// TODO: have only a single global variable to not pollute the global scope
Expand Down Expand Up @@ -236,10 +244,17 @@ function replacer(code: string, values: Record<string, string>) {
return code.replace(/{\s*(\w+)\s*}/g, (_, key) => values[key] ?? '')
}

function formatScripts(scripts: BrowserScript[] | undefined) {
async function formatScripts(scripts: BrowserScript[] | undefined, server: ViteDevServer) {
if (!scripts?.length)
return ''
return scripts.map(({ content, src, async, type = 'module' }) => {
return `<script type="${type}"${async ? ' async' : ''}${src ? ` src="${src}"` : ''}>${content || ''}</script>`
}).join('\n')
const promises = scripts.map(async ({ content, src, async, id, type = 'module' }, index) => {
const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : undefined) || src
const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`)
await server.moduleGraph.ensureEntryFromUrl(transformId)
const contentProcessed = content && type === 'module'
? (await server.pluginContainer.transform(content, transformId)).code
: content
return `<script type="${type}"${async ? ' async' : ''}${srcLink ? ` src="${srcLink}"` : ''}>${contentProcessed || ''}</script>`
})
return (await Promise.all(promises)).join('\n')
}
19 changes: 19 additions & 0 deletions packages/vitest/src/types/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,29 @@ export interface BrowserConfigOptions {
}

export interface BrowserScript {
/**
* If "content" is provided and type is "module", this will be its identifier.
*
* If you are using TypeScript, you can add `.ts` extension here for example.
* @default `injected-${index}.js`
*/
id?: string
/**
* JavaScript content to be injected. This string is processed by Vite plugins if type is "module".
*
* You can use `id` to give Vite a hint about the file extension.
*/
content?: string
/**
* Path to the script. This value is resolved by Vite so it can be a node module or a file path.
*/
src?: string
/**
* If the script should be loaded asynchronously.
*/
async?: boolean
/**
* Script type.
* @default 'module'
*/
type?: string
Expand Down
22 changes: 7 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/browser/injected-lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__injected.push(4)
7 changes: 7 additions & 0 deletions test/browser/injected-lib/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@vitest/injected-lib",
"type": "module",
"exports": {
"default": "./index.js"
}
}
2 changes: 2 additions & 0 deletions test/browser/injected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @ts-expect-error not typed global
;(__injected as string[]).push(3)
1 change: 1 addition & 0 deletions test/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@vitejs/plugin-basic-ssl": "^1.0.2",
"@vitest/browser": "workspace:*",
"@vitest/cjs-lib": "link:./cjs-lib",
"@vitest/injected-lib": "link:./injected-lib",
"execa": "^7.1.1",
"playwright": "^1.41.0",
"url": "^0.11.3",
Expand Down
4 changes: 2 additions & 2 deletions test/browser/specs/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ describe.each([
})

test(`[${description}] tests are actually running`, () => {
expect(browserResultJson.testResults).toHaveLength(14)
expect(passedTests).toHaveLength(12)
expect(browserResultJson.testResults).toHaveLength(15)
expect(passedTests).toHaveLength(13)

Check failure on line 30 in test/browser/specs/runner.test.ts

View workflow job for this annotation

GitHub Actions / test-browser-windows (chrome, chromium)

specs/runner.test.ts > [non parallel] running browser tests > [non parallel] tests are actually running

AssertionError: expected [ { …(6) }, { …(6) }, { …(6) }, …(9) ] to have a length of 13 but got 12 - Expected + Received - 13 + 12 ❯ specs/runner.test.ts:30:25

Check failure on line 30 in test/browser/specs/runner.test.ts

View workflow job for this annotation

GitHub Actions / test-browser-windows (chrome, chromium)

specs/runner.test.ts > [parallel] running browser tests > [parallel] tests are actually running

AssertionError: expected [ { …(6) }, { …(6) }, { …(6) }, …(9) ] to have a length of 13 but got 12 - Expected + Received - 13 + 12 ❯ specs/runner.test.ts:30:25

Check failure on line 30 in test/browser/specs/runner.test.ts

View workflow job for this annotation

GitHub Actions / test-browser-windows (edge, webkit)

specs/runner.test.ts > [non parallel] running browser tests > [non parallel] tests are actually running

AssertionError: expected [ { …(6) }, { …(6) }, { …(6) }, …(9) ] to have a length of 13 but got 12 - Expected + Received - 13 + 12 ❯ specs/runner.test.ts:30:25

Check failure on line 30 in test/browser/specs/runner.test.ts

View workflow job for this annotation

GitHub Actions / test-browser-windows (edge, webkit)

specs/runner.test.ts > [parallel] running browser tests > [parallel] tests are actually running

AssertionError: expected [ { …(6) }, { …(6) }, { …(6) }, …(9) ] to have a length of 13 but got 12 - Expected + Received - 13 + 12 ❯ specs/runner.test.ts:30:25
expect(failedTests).toHaveLength(2)

expect(stderr).not.toContain('has been externalized for browser compatibility')
Expand Down
10 changes: 10 additions & 0 deletions test/browser/test/injected.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect, test } from 'vitest'

test('injected values are correct', () => {
expect((globalThis as any).__injected).toEqual([
1,
2,
3,
4,
])
})
31 changes: 31 additions & 0 deletions test/browser/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@ export default defineConfig({
provider,
isolate: false,
slowHijackESM: true,
testerScripts: [
{
content: 'globalThis.__injected = []',
type: 'text/javascript',
},
{
content: '__injected.push(1)',
},
{
id: 'ts.ts',
content: '(__injected as string[]).push(2)',
},
{
src: './injected.ts',
},
{
src: '@vitest/injected-lib',
},
],
indexScripts: [
{
content: 'console.log("Hello, World");globalThis.__injected = []',
type: 'text/javascript',
},
{
content: 'import "./injected.ts"',
},
{
content: 'if(__injected[0] !== 3) throw new Error("injected not working")',
},
],
},
alias: {
'#src': resolve(dir, './src'),
Expand Down

0 comments on commit 6451418

Please sign in to comment.