Skip to content

Commit

Permalink
feat: init @vitest/browser package (#1302)
Browse files Browse the repository at this point in the history
Co-authored-by: Joaquín Sánchez Jiménez <joaquin.sanchez@fi2net.es>
Co-authored-by: userquin <userquin@gmail.com>
Co-authored-by: M. Bagher Abiat <zorofight94@gmail.com>
  • Loading branch information
4 people committed Jul 19, 2022
1 parent 7c2138f commit 88033bc
Show file tree
Hide file tree
Showing 61 changed files with 795 additions and 91 deletions.
1 change: 1 addition & 0 deletions docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ vitest related /src/index.ts /src/hello-world.js
| `--mode <name>` | Override Vite mode (default: `test`) |
| `--globals` | Inject APIs globally |
| `--dom` | Mock browser api with happy-dom |
| `--browser` | Run tests in browser |
| `--environment <env>` | Runner environment (default: `node`) |
| `--passWithNoTests` | Pass when no tests found |
| `--allowOnly` | Allow tests and suites that are marked as `only` (default: false in CI, true otherwise) |
Expand Down
4 changes: 4 additions & 0 deletions examples/vitesse/src/auto-import.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ declare global {
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
Expand Down Expand Up @@ -48,4 +50,6 @@ declare global {
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
2 changes: 1 addition & 1 deletion examples/vitesse/src/components.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

declare module '@vue/runtime-core' {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"test": "vitest --api -r test/core",
"test:run": "vitest run -r test/core",
"test:all": "cross-env CI=true pnpm -r --stream run test --allowOnly",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !test-fails run test --allowOnly",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !test-fails --filter !test-browser run test --allowOnly",
"test:ci:single-thread": "cross-env CI=true pnpm -r --stream --filter !test-fails run test --allowOnly --no-threads",
"typecheck": "tsc --noEmit",
"ui:build": "vite build packages/ui",
Expand All @@ -40,6 +40,8 @@
"@types/lodash": "^4.14.182",
"@types/node": "^18.0.4",
"@types/ws": "^8.5.3",
"@vitest/browser": "workspace:*",
"@vitest/ui": "workspace:*",
"bumpp": "^8.2.1",
"c8": "^7.11.3",
"cross-env": "^7.0.3",
Expand Down
33 changes: 33 additions & 0 deletions packages/browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# @vitest/browser

Browser runner for Vitest.

> ⚠️ This package is **not yet ready** and it's for preview only. While this package will be released along with other packages, it will not follow semver for breaking changes until we mark it as ready. **Do not use it in production**.
## Progress

Current Status: **Working in progress**

- [x] Init package and integration
- [x] Stub node packages for Vitest runtime
- [ ] Works in development mode
- [ ] Better log in terminal
- [ ] Fulfill tests (using Browser only APIs, Vue and React components)
- [ ] Show progress and error on the browser page
- [ ] Headless mode in CI
- [ ] Docs

Related PRs

- [#1302](https://github.com/vitest-dev/vitest/pull/1302)

## Development Setup

At project root:

```bash
pnpm dev

cd test/browser
pnpm vitest --browser
```
88 changes: 88 additions & 0 deletions packages/browser/client/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { VitestClient } from '@vitest/ws-client'
import { createClient } from '@vitest/ws-client'
// eslint-disable-next-line no-restricted-imports
import type { ResolvedConfig } from 'vitest'

// @ts-expect-error mocking some node apis
globalThis.process = { env: {}, argv: [], stdout: { write: () => {} } }
globalThis.global = globalThis

export const PORT = import.meta.hot ? '51204' : location.port
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
export const ENTRY_URL = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
}//${HOST}/__vitest_api__`

let config: ResolvedConfig | undefined
const browserHashMap = new Map<string, string>()

export const client = createClient(ENTRY_URL, {
handlers: {
async onPathsCollected(paths) {
if (!paths)
return

// const config = __vitest_worker__.config
const now = `${new Date().getTime()}`
paths.forEach((i) => {
browserHashMap.set(i, now)
})

await runTests(paths, config, client)
},
},
})

const ws = client.ws

async function loadConfig() {
let retries = 5
do {
try {
await new Promise(resolve => setTimeout(resolve, 150))
config = await client.rpc.getConfig()
return
}
catch (_) {
// just ignore
}
}
while (--retries > 0)

throw new Error('cannot load configuration after 5 retries')
}

ws.addEventListener('open', async () => {
await loadConfig()

// @ts-expect-error mocking vitest apis
globalThis.__vitest_worker__ = {
config,
browserHashMap,
rpc: client.rpc,
}

// @ts-expect-error mocking vitest apis
globalThis.__vitest_mocker__ = {}
const paths = await client.rpc.getPaths()

const now = `${new Date().getTime()}`
paths.forEach(i => browserHashMap.set(i, now))

const iFrame = document.getElementById('vitest-ui') as HTMLIFrameElement
iFrame.setAttribute('src', '/__vitest__/')

await runTests(paths, config, client)
})

async function runTests(paths: string[], config: any, client: VitestClient) {
const name = '/__vitest_index__'
const { startTests, setupGlobalEnv } = (await import(name)) as unknown as typeof import('vitest/browser')

await setupGlobalEnv(config as any)

await startTests(paths, config as any)

await client.rpc.onFinished()
await client.rpc.onWatcherStart()
}
29 changes: 29 additions & 0 deletions packages/browser/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vitest Browser Runner</title>
<style>
html {
overflow: hidden;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
#vitest-ui {
width: 100vw;
height: 100vh;
border: none;
}
</style>
</head>
<body>
<iframe id="vitest-ui" src=""></iframe>
<script type="module" src="./client/main.ts"></script>
</body>
</html>
85 changes: 85 additions & 0 deletions packages/browser/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { fileURLToPath } from 'url'
// eslint-disable-next-line no-restricted-imports
import { resolve } from 'path'
import { builtinModules } from 'module'
import { polyfillPath } from 'modern-node-polyfills'
import sirv from 'sirv'
import type { Plugin } from 'vite'
import { resolvePath } from 'mlly'

const stubs = [
'fs',
'local-pkg',
'module',
'noop',
'perf_hooks',
]

const polyfills = [
'util',
'tty',
'process',
'path',
'buffer',
]

export default (base = '/'): Plugin[] => {
const pkgRoot = resolve(fileURLToPath(import.meta.url), '../..')
const distRoot = resolve(pkgRoot, 'dist')

return [
{
enforce: 'pre',
name: 'vitest:browser',
async resolveId(id, _, ctx) {
if (ctx.ssr)
return

if (id === '/__vitest_index__') {
const result = await resolvePath('vitest/browser')
return result
}

if (stubs.includes(id))
return resolve(pkgRoot, 'stubs', id)

if (polyfills.includes(id))
return polyfillPath(normalizeId(id))

return null
},
async configureServer(server) {
server.middlewares.use(
base,
sirv(resolve(distRoot, 'client'), {
single: false,
dev: true,
}),
)
},
},
{
name: 'modern-node-polyfills',
async resolveId(id, _, ctx) {
if (ctx.ssr || !builtinModules.includes(id))
return

id = normalizeId(id)
return { id: await polyfillPath(id), moduleSideEffects: false }
},
},
]
}

function normalizeId(id: string, base?: string): string {
if (base && id.startsWith(base))
id = `/${id.slice(base.length)}`

return id
.replace(/^\/@id\/__x00__/, '\0') // virtual modules start with `\0`
.replace(/^\/@id\//, '')
.replace(/^__vite-browser-external:/, '')
.replace(/^node:/, '')
.replace(/[?&]v=\w+/, '?') // remove ?v= query
.replace(/\?$/, '') // remove end query mark
}
49 changes: 49 additions & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@vitest/browser",
"type": "module",
"version": "0.4.1",
"description": "Browser running for Vitest",
"repository": {
"type": "git",
"url": "git+https://github.com/vitest-dev/vitest.git",
"directory": "packages/browser"
},
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./*": "./*"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"stubs"
],
"scripts": {
"build": "rimraf dist && pnpm build:node && pnpm build:client && pnpm copy",
"build:client": "vite build",
"build:node": "rollup -c",
"dev:client": "vite build --watch",
"dev:node": "rollup -c --watch --watch.include=node/**",
"dev": "rimraf dist && run-p dev:node dev:client",
"copy": "esmo scripts/copy-ui-to-browser.ts"
},
"dependencies": {
"local-pkg": "^0.4.1",
"mlly": "^0.5.2",
"modern-node-polyfills": "^0.0.7",
"rollup-plugin-node-polyfills": "^0.2.1",
"sirv": "^2.0.2"
},
"devDependencies": {
"@types/ws": "^8.2.2",
"@vitest/ws-client": "workspace:*",
"picocolors": "^1.0.0",
"rollup": "^2.67.2",
"vitest": "workspace:*"
}
}
5 changes: 5 additions & 0 deletions packages/browser/public/favicon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions packages/browser/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import esbuild from 'rollup-plugin-esbuild'
import dts from 'rollup-plugin-dts'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import alias from '@rollup/plugin-alias'
import pkg from './package.json'

const external = [
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies || {}),
'worker_threads',
]

const plugins = [
alias({
entries: [{ find: /^node:(.+)$/, replacement: '$1' }],
}),
resolve({
preferBuiltins: true,
}),
json(),
commonjs(),
esbuild({
target: 'node14',
}),
]

export default () => [
{
input: [
'./node/index.ts',
],
output: {
dir: 'dist',
format: 'esm',
},
external,
plugins,
},
{
input: './node/index.ts',
output: {
file: 'dist/index.d.ts',
format: 'esm',
},
external,
plugins: [dts()],
},
]

0 comments on commit 88033bc

Please sign in to comment.