diff --git a/docs/config/index.md b/docs/config/index.md index e32dde79acf1..381d570f495a 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -153,13 +153,14 @@ export default defineConfig({ ### environment -- **Type:** `'node' | 'jsdom' | 'happy-dom'` +- **Type:** `'node' | 'jsdom' | 'happy-dom' | 'edge-runtime'` - **Default:** `'node'` The environment that will be used for testing. The default environment in Vitest is a Node.js environment. If you are building a web application, you can use browser-like environment through either [`jsdom`](https://github.com/jsdom/jsdom) or [`happy-dom`](https://github.com/capricorn86/happy-dom) instead. +If you are building edge functions, you can use [`edge-runtime`](https://edge-runtime.vercel.app/packages/vm) environment By adding a `@vitest-environment` docblock or comment at the top of the file, you can specify another environment to be used for all tests in that file: diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index afb0b813f543..cfd44623b6d1 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -465,6 +465,13 @@ Repository: git://github.com/kpdecker/jsdiff.git --------------------------------------- +## eastasianwidth +License: MIT +By: Masaki Komagata +Repository: git://github.com/komagata/eastasianwidth.git + +--------------------------------------- + ## emoji-regex License: MIT By: Mathias Bynens diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 499371e49eed..38e00a724351 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -62,6 +62,7 @@ "prepublishOnly": "nr build" }, "peerDependencies": { + "@edge-runtime/vm": "*", "@vitest/ui": "*", "c8": "*", "happy-dom": "*", @@ -79,6 +80,9 @@ }, "jsdom": { "optional": true + }, + "@edge-runtime/vm": { + "optional": true } }, "dependencies": { @@ -94,6 +98,7 @@ }, "devDependencies": { "@antfu/install-pkg": "^0.1.0", + "@edge-runtime/vm": "1.1.0-beta.10", "@sinonjs/fake-timers": "^9.1.2", "@types/diff": "^5.0.2", "@types/jsdom": "^16.2.14", diff --git a/packages/vitest/src/integrations/env/edge-runtime.ts b/packages/vitest/src/integrations/env/edge-runtime.ts new file mode 100644 index 000000000000..e65db7fbf3ca --- /dev/null +++ b/packages/vitest/src/integrations/env/edge-runtime.ts @@ -0,0 +1,24 @@ +import { importModule } from 'local-pkg' +import type { Environment } from '../../types' +import { populateGlobal } from './utils' + +export default ({ + name: 'edge-runtime', + async setup(global) { + const { EdgeVM } = await importModule('@edge-runtime/vm') as typeof import('@edge-runtime/vm') + const vm = new EdgeVM({ + extend: (context) => { + context.global = context + context.Buffer = Buffer + return context + }, + }) + const { keys, originals } = populateGlobal(global, vm.context, { bindFunctions: true }) + return { + teardown(global) { + keys.forEach(key => delete global[key]) + originals.forEach((v, k) => global[k] = v) + }, + } + }, +}) diff --git a/packages/vitest/src/integrations/env/index.ts b/packages/vitest/src/integrations/env/index.ts index 911d7308e04e..aa130e77e3ee 100644 --- a/packages/vitest/src/integrations/env/index.ts +++ b/packages/vitest/src/integrations/env/index.ts @@ -1,9 +1,19 @@ import node from './node' import jsdom from './jsdom' import happy from './happy-dom' +import edge from './edge-runtime' export const environments = { node, jsdom, 'happy-dom': happy, + 'edge-runtime': edge, +} + +export const envs = Object.keys(environments) + +export const envPackageNames: Record, string> = { + 'jsdom': 'jsdom', + 'happy-dom': 'happy-dom', + 'edge-runtime': '@edge-runtime/vm', } diff --git a/packages/vitest/src/node/cli-api.ts b/packages/vitest/src/node/cli-api.ts index 948c154b9364..2eb7f719d87a 100644 --- a/packages/vitest/src/node/cli-api.ts +++ b/packages/vitest/src/node/cli-api.ts @@ -1,4 +1,5 @@ import type { UserConfig as ViteUserConfig } from 'vite' +import { envPackageNames } from '../integrations/env' import type { UserConfig } from '../types' import { ensurePackageInstalled } from '../utils' import { createVitest } from './create' @@ -37,7 +38,8 @@ export async function startVitest(cliFilters: string[], options: CliOptions, vit } if (ctx.config.environment && ctx.config.environment !== 'node') { - if (!await ensurePackageInstalled(ctx.config.environment)) { + const packageName = envPackageNames[ctx.config.environment] + if (!await ensurePackageInstalled(packageName)) { process.exitCode = 1 return false } diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index eb8112ade641..91df7487b2bc 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' import type { BuiltinEnvironment, ResolvedConfig } from '../types' import { getWorkerState, resetModules } from '../utils' +import { envs } from '../integrations/env' import { setupGlobalEnv, withEnv } from './setup' import { startTests } from './run' @@ -9,8 +10,6 @@ export async function run(files: string[], config: ResolvedConfig): Promise { diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index f5abed1209bf..28fc19b31873 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -8,7 +8,7 @@ import type { Reporter } from './reporter' import type { SnapshotStateOptions } from './snapshot' import type { Arrayable } from './general' -export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom' +export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' export type ApiConfig = Pick @@ -98,7 +98,7 @@ export interface InlineConfig { /** * Running environment * - * Supports 'node', 'jsdom', 'happy-dom' + * Supports 'node', 'jsdom', 'happy-dom', 'edge-runtime' * * @default 'node' */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b8be110ebb6..f7fe25d5f0f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -648,6 +648,7 @@ importers: packages/vitest: specifiers: '@antfu/install-pkg': ^0.1.0 + '@edge-runtime/vm': 1.1.0-beta.10 '@sinonjs/fake-timers': ^9.1.2 '@types/chai': ^4.3.1 '@types/chai-subset': ^1.3.3 @@ -706,6 +707,7 @@ importers: vite: 3.0.0-beta.4 devDependencies: '@antfu/install-pkg': 0.1.0 + '@edge-runtime/vm': 1.1.0-beta.10 '@sinonjs/fake-timers': 9.1.2 '@types/diff': 5.0.2 '@types/jsdom': 16.2.14 @@ -880,6 +882,16 @@ importers: pathe: 0.2.0 vitest: link:../../packages/vitest + test/vite-edge: + specifiers: + '@edge-runtime/vm': 1.1.0-beta.10 + '@vitest/web-worker': workspace:* + vitest: workspace:* + devDependencies: + '@edge-runtime/vm': 1.1.0-beta.10 + '@vitest/web-worker': link:../../packages/web-worker + vitest: link:../../packages/vitest + test/vite-node: specifiers: vite-node: workspace:* @@ -3057,6 +3069,16 @@ packages: react-dom: 18.1.0_react@18.1.0 dev: true + /@edge-runtime/primitives/1.1.0-beta.10: + resolution: {integrity: sha512-hv0i2ce35yspqlnmcSKV/GEKfk8WyB9aTSOk0ZMK6gOR6ZvBDCzDpdacIcGuktaTuDinwon2DLxXRhiAS2PjUg==} + dev: true + + /@edge-runtime/vm/1.1.0-beta.10: + resolution: {integrity: sha512-AHeIdWp1OUf4kvA4to56shXZzM66bR84LMNTbYGY7G3+BBr+VkUcvU+hWr8JYCOj/iAHi/fuE9r1TyrMm2pQaw==} + dependencies: + '@edge-runtime/primitives': 1.1.0-beta.10 + dev: true + /@emotion/babel-plugin/11.9.2_@babel+core@7.18.2: resolution: {integrity: sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==} peerDependencies: @@ -6301,7 +6323,7 @@ packages: dev: true /@types/form-data/0.0.33: - resolution: {integrity: sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=} + resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} dependencies: '@types/node': 17.0.40 dev: true diff --git a/test/vite-edge/package.json b/test/vite-edge/package.json new file mode 100644 index 000000000000..87b471e745ee --- /dev/null +++ b/test/vite-edge/package.json @@ -0,0 +1,14 @@ +{ + "name": "@vitest/test-edge-runtime", + "type": "module", + "private": true, + "scripts": { + "test": "vitest", + "coverage": "vitest run --coverage" + }, + "devDependencies": { + "@edge-runtime/vm": "1.1.0-beta.10", + "@vitest/web-worker": "workspace:*", + "vitest": "workspace:*" + } +} diff --git a/test/vite-edge/test/edge.test.ts b/test/vite-edge/test/edge.test.ts new file mode 100644 index 000000000000..3d20a70d50f0 --- /dev/null +++ b/test/vite-edge/test/edge.test.ts @@ -0,0 +1,19 @@ +/** + * @vitest-environment edge-runtime + */ +import { describe, expect, it } from 'vitest' +describe('edge runtime api', () => { + it('TextEncoder references the same global Uint8Array constructor', () => { + expect(new TextEncoder().encode('abc')).toBeInstanceOf(Uint8Array) + }) + + it('allows to run fetch', async () => { + const response = await fetch('https://vitest.dev') + expect(response.status).toEqual(200) + }) + + it('allows to run crypto', async () => { + const array = new Uint32Array(10) + expect(crypto.getRandomValues(array)).toHaveLength(array.length) + }) +}) diff --git a/test/vite-edge/test/node.test.ts b/test/vite-edge/test/node.test.ts new file mode 100644 index 000000000000..dad8440b3c38 --- /dev/null +++ b/test/vite-edge/test/node.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from 'vitest' + +test('node env should not have crypto', () => { + expect(global).not.toHaveProperty('crypto') +}) diff --git a/test/vite-edge/vitest.config.ts b/test/vite-edge/vitest.config.ts new file mode 100644 index 000000000000..b9c5dc4894e3 --- /dev/null +++ b/test/vite-edge/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + test: { + environment: 'node', + }, +})