Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: web worker support #726

Merged
merged 28 commits into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1f39a54
feat: worker support
sheremet-va Feb 10, 2022
3edc8f0
chore: fix worker support
sheremet-va Feb 10, 2022
4c4485b
Merge branch 'main' of https://github.com/antfu-sponsors/vitest into …
sheremet-va Feb 10, 2022
659dac9
chore: remove importScripts from supported for now
sheremet-va Feb 10, 2022
7f5ecf2
feat: add web worker as a package
sheremet-va Feb 17, 2022
18ecd82
Merge branch 'main' of https://github.com/antfu-sponsors/vitest into …
sheremet-va Feb 17, 2022
59464c2
chore: remove inline worker from vitest
sheremet-va Feb 17, 2022
9d2e0c0
chore: build web-worker
sheremet-va Feb 17, 2022
4297fad
chore: clean up
sheremet-va Feb 17, 2022
487137f
chore: move mocker types
sheremet-va Feb 17, 2022
b8ba7a2
chore: move spy and pendingIds inside mocker to static objects to mak…
sheremet-va Feb 17, 2022
c5ac144
Merge branch 'main' of https://github.com/antfu-sponsors/vitest into …
sheremet-va Feb 18, 2022
7548eda
chore: move VitestRunner to runtime since it is run inside a worker, …
sheremet-va Feb 18, 2022
6575696
chore: update lockfile
sheremet-va Feb 18, 2022
c0e70fd
chore: move __vitest_worker__ to src/index.ts
sheremet-va Feb 18, 2022
576be04
Merge branch 'main' of https://github.com/antfu-sponsors/vitest into …
sheremet-va Mar 1, 2022
fb2dd56
Merge branch 'main' of https://github.com/antfu-sponsors/vitest into …
sheremet-va Mar 8, 2022
52684db
chore: fix types
sheremet-va Mar 8, 2022
ec329c4
chore: linting, fixes
sheremet-va Mar 8, 2022
c4ba574
chore: make __vitest_worker__ a global type
sheremet-va Mar 8, 2022
c83374d
chore: fix __vitest_worker__ check
sheremet-va Mar 8, 2022
331a56f
chore: fix types conflict
sheremet-va Mar 8, 2022
f0e2ee0
Merge branch 'main' into worker-suppot
antfu Mar 11, 2022
6dd21b7
chore: lock
antfu Mar 11, 2022
23d9c8c
chore: update
antfu Mar 11, 2022
f3e0e7a
chore: update
antfu Mar 11, 2022
727cc68
Merge branch 'main' into worker-suppot
antfu Mar 16, 2022
03117cf
refactor: global state usage
antfu Mar 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/vitest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Plugin as PrettyFormatPlugin } from 'pretty-format'
import type { Any, Anything } from './integrations/chai/jest-asymmetric-matchers'
import type { MatcherState, MatchersObject } from './integrations/chai/types'
import type { Constructable, InlineConfig } from './types'
import type { Constructable, InlineConfig, WorkerGlobalState } from './types'

export { suite, test, describe, it } from './runtime/suite'

Expand All @@ -13,6 +13,9 @@ export * from './integrations/vi'
export * from './types'
export * from './api/types'

export { VitestRunner } from './runtime/execute'
export type { ExecuteOptions } from './runtime/execute'

type VitestInlineConfig = InlineConfig

declare module 'vite' {
Expand Down Expand Up @@ -40,6 +43,8 @@ type Promisify<O> = {
}

declare global {
let __vitest_worker__: WorkerGlobalState
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved

// support augmenting jest.Matchers by other libraries
namespace jest {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
5 changes: 3 additions & 2 deletions packages/vitest/src/integrations/jest-mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { util } from 'chai'
import type { SpyImpl } from 'tinyspy'
import * as tinyspy from 'tinyspy'

Expand Down Expand Up @@ -228,7 +227,9 @@ function enhanceSpy<TArgs extends any[], TReturns>(
stub.mockRejectedValueOnce = (val: unknown) =>
stub.mockImplementationOnce(() => Promise.reject(val))

util.addProperty(stub, 'mock', () => mockContext)
Object.defineProperty(stub, 'mock', {
get: () => mockContext,
})

stub.willCall(function(this: unknown, ...args) {
instances.push(this)
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import { parseStacktrace } from '../utils/source-map'
import type { VitestMocker } from '../node/mocker'
import type { VitestMocker } from '../runtime/mocker'
import { FakeTimers } from './timers'
import type { EnhancedSpy, MaybeMocked, MaybeMockedDeep } from './jest-mock'
import { fn, isMockFunction, spies, spyOn } from './jest-mock'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { ViteNodeRunner } from 'vite-node/client'
import type { ModuleCache, ViteNodeResolveId, ViteNodeRunnerOptions } from 'vite-node'
import type { SuiteMocks } from './mocker'
import type { SuiteMocks } from '../types/mocker'
import { VitestMocker } from './mocker'

export type ResolveIdFunction = (id: string, importer?: string) => Promise<ViteNodeResolveId | null>

export interface ExecuteOptions extends ViteNodeRunnerOptions {
files: string[]
mockMap: SuiteMocks
resolveId: ResolveIdFunction
}

export async function executeInViteNode(options: ExecuteOptions) {
export async function executeInViteNode(options: ExecuteOptions & { files: string[] }) {
const runner = new VitestRunner(options)

// provide the vite define variable in this context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@ import type { ModuleCache } from 'vite-node'
import { toFilePath } from 'vite-node/utils'
import { isWindows, mergeSlashes, normalizeId } from '../utils'
import { distDir } from '../constants'
import type { PendingSuiteMock } from '../types/mocker'
import type { ExecuteOptions } from './execute'

export type SuiteMocks = Record<string, Record<string, string | null | (() => unknown)>>

type Callback = (...args: any[]) => unknown

interface PendingSuiteMock {
id: string
importer: string
type: 'mock' | 'unmock'
factory?: () => unknown
}

function getObjectType(value: unknown): string {
return Object.prototype.toString.apply(value).slice(8, -1)
}
Expand All @@ -40,15 +32,14 @@ function mockPrototype(spyOn: typeof import('../integrations/jest-mock')['spyOn'
return newProto
}

const pendingIds: PendingSuiteMock[] = []

export class VitestMocker {
private static pendingIds: PendingSuiteMock[] = []
private static spyModule?: typeof import('../integrations/jest-mock')

private request!: (dep: string) => unknown

private root: string

private callbacks: Record<string, ((...args: any[]) => unknown)[]> = {}
private spy?: typeof import('../integrations/jest-mock')

constructor(
public options: ExecuteOptions,
Expand Down Expand Up @@ -96,15 +87,15 @@ export class VitestMocker {
}

private async resolveMocks() {
await Promise.all(pendingIds.map(async(mock) => {
await Promise.all(VitestMocker.pendingIds.map(async(mock) => {
const { path, external } = await this.resolvePath(mock.id, mock.importer)
if (mock.type === 'unmock')
this.unmockPath(path)
if (mock.type === 'mock')
this.mockPath(path, external, mock.factory)
}))

pendingIds.length = 0
VitestMocker.pendingIds = []
}

private async callFunctionMock(dep: string, mock: () => any) {
Expand Down Expand Up @@ -166,7 +157,7 @@ export class VitestMocker {
}

public mockObject(obj: any) {
if (!this.spy)
if (!VitestMocker.spyModule)
throw new Error('Internal Vitest error: Spy function is not defined.')

const type = getObjectType(obj)
Expand All @@ -178,7 +169,7 @@ export class VitestMocker {

const newObj = { ...obj }

const proto = mockPrototype(this.spy.spyOn, Object.getPrototypeOf(obj))
const proto = mockPrototype(VitestMocker.spyModule.spyOn, Object.getPrototypeOf(obj))
Object.setPrototypeOf(newObj, proto)

// eslint-disable-next-line no-restricted-syntax
Expand All @@ -187,7 +178,7 @@ export class VitestMocker {
const type = getObjectType(obj[k])

if (type.includes('Function') && !obj[k]._isMockFunction) {
this.spy.spyOn(newObj, k).mockImplementation(() => {})
VitestMocker.spyModule.spyOn(newObj, k).mockImplementation(() => {})
Object.defineProperty(newObj[k], 'length', { value: 0 }) // tinyspy retains length, but jest doesnt
}
}
Expand Down Expand Up @@ -239,8 +230,8 @@ export class VitestMocker {
}

private async ensureSpy() {
if (this.spy) return
this.spy = await this.request(resolve(distDir, 'jest-mock.js')) as typeof import('../integrations/jest-mock')
if (VitestMocker.spyModule) return
VitestMocker.spyModule = await this.request(resolve(distDir, 'jest-mock.js')) as typeof import('../integrations/jest-mock')
}

public async requestWithMock(dep: string) {
Expand Down Expand Up @@ -268,11 +259,11 @@ export class VitestMocker {
}

public queueMock(id: string, importer: string, factory?: () => unknown) {
pendingIds.push({ type: 'mock', id, importer, factory })
VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory })
}

public queueUnmock(id: string, importer: string) {
pendingIds.push({ type: 'unmock', id, importer })
VitestMocker.pendingIds.push({ type: 'unmock', id, importer })
}

public withRequest(request: (dep: string) => unknown) {
Expand Down
11 changes: 4 additions & 7 deletions packages/vitest/src/runtime/worker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { resolve } from 'pathe'
import { createBirpc } from 'birpc'
import type { ModuleCache, ResolvedConfig, WorkerContext, WorkerGlobalState, WorkerRPC } from '../types'
import type { ModuleCache, ResolvedConfig, WorkerContext, WorkerRPC } from '../types'
import { distDir } from '../constants'
import { executeInViteNode } from '../node/execute'
import { executeInViteNode } from './execute'
import { rpc } from './rpc'

let _viteNode: {
run: (files: string[], config: ResolvedConfig) => Promise<void>
collect: (files: string[], config: ResolvedConfig) => Promise<void>
}
let __vitest_worker__: WorkerGlobalState
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved

const moduleCache: Map<string, ModuleCache> = new Map()
const mockMap = {}

Expand Down Expand Up @@ -67,6 +67,7 @@ function init(ctx: WorkerContext) {
ctx,
moduleCache,
config,
mockMap,
rpc: createBirpc<WorkerRPC>(
{},
{
Expand All @@ -93,7 +94,3 @@ export async function run(ctx: WorkerContext) {
const { run } = await startViteNode(ctx)
return run(ctx.files, ctx.config)
}

declare global {
let __vitest_worker__: import('vitest').WorkerGlobalState
}
8 changes: 8 additions & 0 deletions packages/vitest/src/types/mocker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type SuiteMocks = Record<string, Record<string, string | null | (() => unknown)>>

export interface PendingSuiteMock {
id: string
importer: string
type: 'mock' | 'unmock'
factory?: () => unknown
}
2 changes: 2 additions & 0 deletions packages/vitest/src/types/worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { MessagePort } from 'worker_threads'
import type { FetchFunction, ModuleCache, RawSourceMap, ViteNodeResolveId } from 'vite-node'
import type { BirpcReturn } from 'birpc'
import type { SuiteMocks } from './mocker'
import type { ResolvedConfig } from './config'
import type { File, TaskResultPack, Test } from './tasks'
import type { SnapshotResult } from './snapshot'
Expand Down Expand Up @@ -37,4 +38,5 @@ export interface WorkerGlobalState {
current?: Test
filepath?: string
moduleCache: Map<string, ModuleCache>
mockMap: SuiteMocks
}
60 changes: 60 additions & 0 deletions packages/web-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# @vitest/addon-web-worker

> Web Worker support for Vitest testing. Doesn't require JSDom.

Simulates Web Worker, but in the same thread. Supports both `new Worker(url)` and `import from './worker?worker`.

## Installing

```sh
# with npm
npm install -D @vitest/addon-web-worker

# with pnpm
pnpm install -D @vitest/addon-web-worker

# with yarn
yarn install -D @vitest/addon-web-worker
```

## Usage

Just import `@vitest/addon-web-worker` in your test file to test only in current suite.

Or add `@vitest/addon-web-worker` in your `setupFiles`, if you want to have a global support.

```ts
import { defineConfig } from 'vitest/node'

export default defineConfig({
test: {
setupFiles: ['@vitest/addon-web-worker']
}
})
```

## Examples

```ts
// worker.ts
self.onmessage = (e) => {
self.postMessage(`${e.data} world`)
}

// worker.test.ts
import '@vitest/addon-web-worker'
import MyWorker from '../worker?worker'

const worker = new MyWorker()
// new Worker is also supported
const worker = new Worker(new URL('../src/worker.ts', import.meta.url))

worker.postMessage('hello')
worker.onmessage = (e) => {
// e.data equals to 'hello world'
}
```

## Notice

- Does not support `onmessage = () => {}`. Please, use `self.onmessage = () => {}`.
1 change: 1 addition & 0 deletions packages/web-worker/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

37 changes: 37 additions & 0 deletions packages/web-worker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@vitest/web-worker",
"version": "1.0.0",
"description": "Web Worker support for testing in Vitest",
"type": "module",
"scripts": {
"build": "rimraf dist && rollup -c",
"dev": "rollup -c --watch --watch.include=src/**",
"prepublishOnly": "nr build",
"typecheck": "tsc --noEmit"
},
"files": [
"dist",
"*.d.ts"
],
"exports": {
".": {
"import": "./dist/index.js",
"default": "./dist/index.js",
"types": "./index.d.ts"
},
"./pure": {
"import": "./dist/pure.js",
"default": "./dist/pure.js",
"types": "./pure.d.ts"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./index.d.ts",
"devDependencies": {
"rollup": "^2.67.2"
},
"peerDependencies": {
"vitest": "*"
}
}
3 changes: 3 additions & 0 deletions packages/web-worker/pure.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare function defineInlineWorker(): void;

export { defineInlineWorker };