refactor(test): refactored and improved deno/bun vitest polyfills a bit
This commit is contained in:
parent
641df422cc
commit
b418577bfa
11 changed files with 87 additions and 193 deletions
|
@ -1,128 +0,0 @@
|
||||||
const bunTest = require('bun:test')
|
|
||||||
const console = require('console') // https://github.com/oven-sh/bun/issues/6044
|
|
||||||
const chaiExpect = require('chai').expect
|
|
||||||
const bunExpect = bunTest.expect
|
|
||||||
|
|
||||||
class BunTestUnsupportedError extends Error {
|
|
||||||
constructor(readonly feature) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _wrapBunExpect(args, bun, invert = false) {
|
|
||||||
let chai = chaiExpect(...args)
|
|
||||||
if (invert) chai = chai.not
|
|
||||||
|
|
||||||
return new Proxy(bun, {
|
|
||||||
get: (target, prop, receiver) => {
|
|
||||||
if (prop === 'eq') return (...args) => chai.eq(...args)
|
|
||||||
if (prop === 'eql') return (...args) => chai.eql(...args)
|
|
||||||
if (prop === 'throws') return (...args) => chai.throws(...args)
|
|
||||||
if (prop === 'is') return chai.is
|
|
||||||
if (prop === 'to') return chai.to
|
|
||||||
if (prop === 'false') return chai.false
|
|
||||||
if (prop === 'true') return chai.true
|
|
||||||
if (prop === 'deep') return chai.deep
|
|
||||||
|
|
||||||
if (prop === 'toMatchInlineSnapshot') {
|
|
||||||
return (expected, options) => {
|
|
||||||
let snapshot
|
|
||||||
if (typeof args[0] === 'string') {
|
|
||||||
const snapshot = '"' + args[0] + '"'
|
|
||||||
return chaiExpect(snapshot).eql(expected.trim())
|
|
||||||
} else {
|
|
||||||
const obj = eval('(' + expected + ')') // idc lol
|
|
||||||
return chaiExpect(args[0]).eql(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prop === 'not') {
|
|
||||||
const not = bun.not
|
|
||||||
return _wrapBunExpect(args, not, !invert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prop === 'rejects') {
|
|
||||||
if (typeof args[0] === 'function') {
|
|
||||||
const newArgs = [args[0](), ...args.slice(1)]
|
|
||||||
return _wrapBunExpect(newArgs, bunExpect(...newArgs), invert).rejects
|
|
||||||
}
|
|
||||||
|
|
||||||
return bun.rejects
|
|
||||||
}
|
|
||||||
if (prop === 'resolves') {
|
|
||||||
return bun.resolves
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prop === 'toHaveBeenCalledOnce' || prop === 'toHaveBeenCalledTimes' || prop === 'toMatchSnapshot') {
|
|
||||||
throw new BunTestUnsupportedError(prop)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Reflect.get(target, prop, receiver).bind(bun)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function expect(...args) {
|
|
||||||
return _wrapBunExpect(args, bunExpect(...args))
|
|
||||||
}
|
|
||||||
|
|
||||||
expect.any = bunExpect.any
|
|
||||||
|
|
||||||
const stubbedGlobal = new Map()
|
|
||||||
function stubGlobal(name, value) {
|
|
||||||
stubbedGlobal.set(name, globalThis[name])
|
|
||||||
globalThis[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
function unstubAllGlobals() {
|
|
||||||
for (const [name, value] of stubbedGlobal) {
|
|
||||||
globalThis[name] = value
|
|
||||||
}
|
|
||||||
stubbedGlobal.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _wrapRunner = (name, fn) => {
|
|
||||||
const handleError = (err) => {
|
|
||||||
if (err instanceof BunTestUnsupportedError) {
|
|
||||||
console.warn(`skipping "${name}" - ${err.feature} is currently not supported in bun:test`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
return (...args) => {
|
|
||||||
try {
|
|
||||||
const res = fn(...args)
|
|
||||||
if (res instanceof Promise) {
|
|
||||||
return res.catch(handleError)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
} catch (e) {
|
|
||||||
return handleError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const it = (name, fn) => bunTest.it(name, _wrapRunner(name, fn))
|
|
||||||
it.only = (name, fn) => bunTest.it.only(name, _wrapRunner(name, fn))
|
|
||||||
it.skip = (name, fn) => bunTest.it.skip(name, _wrapRunner(name, fn))
|
|
||||||
it.each = (table) => (name, fn) => bunTest.it.each(table)(name, _wrapRunner(name, fn))
|
|
||||||
|
|
||||||
export { it }
|
|
||||||
|
|
||||||
export const vi = {
|
|
||||||
...bunTest.jest,
|
|
||||||
...bunTest.vi,
|
|
||||||
mocked: (fn) => fn,
|
|
||||||
stubGlobal,
|
|
||||||
unstubAllGlobals,
|
|
||||||
...['setSystemTime', 'advanceTimersByTimeAsync', 'advanceTimersByTime', 'waitFor', 'doMock'].reduce(
|
|
||||||
(acc, name) => ({
|
|
||||||
...acc,
|
|
||||||
[name]: () => {
|
|
||||||
throw new BunTestUnsupportedError(name)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
}
|
|
31
.config/vite-utils/polyfills-bun.ts
Normal file
31
.config/vite-utils/polyfills-bun.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { describe, it, beforeEach, afterEach, beforeAll, afterAll, jest, vi as bunVi } from 'bun:test'
|
||||||
|
// https://github.com/oven-sh/bun/issues/6044
|
||||||
|
import console from 'console'
|
||||||
|
import * as chai from 'chai'
|
||||||
|
import * as vitestExpect from '@vitest/expect'
|
||||||
|
import * as vitestSpy from '@vitest/spy'
|
||||||
|
import { setupChai, stubGlobal, unstubAllGlobals, waitFor } from './polyfills'
|
||||||
|
|
||||||
|
setupChai(chai, vitestExpect)
|
||||||
|
|
||||||
|
export { it, beforeEach, afterEach, beforeAll, afterAll }
|
||||||
|
export const expect = chai.expect
|
||||||
|
|
||||||
|
export const vi = {
|
||||||
|
...jest,
|
||||||
|
...bunVi,
|
||||||
|
...vitestSpy,
|
||||||
|
mocked: (fn) => fn,
|
||||||
|
stubGlobal,
|
||||||
|
unstubAllGlobals,
|
||||||
|
waitFor,
|
||||||
|
...['setSystemTime', 'advanceTimersByTimeAsync', 'advanceTimersByTime', 'doMock'].reduce(
|
||||||
|
(acc, name) => ({
|
||||||
|
...acc,
|
||||||
|
[name]: () => {
|
||||||
|
throw new Error(name)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
// @ts-expect-error no typings
|
// @ts-expect-error no typings
|
||||||
import { describe as _describe, it, beforeEach, afterEach, beforeAll, afterAll } from 'jsr:@std/testing/bdd'
|
import { describe as _describe, it, beforeEach, afterEach, beforeAll, afterAll } from 'jsr:@std/testing/bdd'
|
||||||
// @ts-expect-error no typings
|
// @ts-expect-error no typings
|
||||||
import * as vitestSpy from 'npm:@vitest/spy'
|
import * as vitestSpy from 'npm:@vitest/spy@1.4.0'
|
||||||
// @ts-expect-error no typings
|
// @ts-expect-error no typings
|
||||||
import * as chai from 'npm:chai'
|
import * as chai from 'npm:chai'
|
||||||
// @ts-expect-error no typings
|
// @ts-expect-error no typings
|
||||||
import * as vitestExpect from 'npm:@vitest/expect'
|
import * as vitestExpect from 'npm:@vitest/expect@1.4.0'
|
||||||
import util from 'node:util'
|
import util from 'node:util'
|
||||||
import { setupChai } from './chai-setup'
|
import { setupChai, stubGlobal, unstubAllGlobals, waitFor } from './polyfills'
|
||||||
|
|
||||||
export { it, beforeEach, afterEach, beforeAll, afterAll }
|
export { it, beforeEach, afterEach, beforeAll, afterAll }
|
||||||
|
|
||||||
setupChai(chai, vitestExpect)
|
setupChai(chai, vitestExpect)
|
||||||
|
|
||||||
|
// https://github.com/denoland/deno_std/issues/2213
|
||||||
Object.defineProperty(it, 'each', {
|
Object.defineProperty(it, 'each', {
|
||||||
value: (items: any[][]) => (name: string, fn: Function) => {
|
value: (items: any[][]) => (name: string, fn: Function) => {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
|
@ -21,10 +22,10 @@ Object.defineProperty(it, 'each', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// https://github.com/denoland/deno_std/issues/4634
|
||||||
export const describe = (...args) => {
|
export const describe = (...args) => {
|
||||||
const fn = args.find((arg) => typeof arg === 'function')
|
const fn = args.find((arg) => typeof arg === 'function')
|
||||||
if (fn.toString().startsWith('async')) {
|
if (fn.toString().startsWith('async')) {
|
||||||
// https://github.com/denoland/deno_std/issues/4634
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,40 +37,12 @@ describe.ignore = _describe.ignore
|
||||||
|
|
||||||
export const expect = chai.expect
|
export const expect = chai.expect
|
||||||
|
|
||||||
const stubbedGlobal = new Map()
|
|
||||||
function stubGlobal(name, value) {
|
|
||||||
stubbedGlobal.set(name, globalThis[name])
|
|
||||||
globalThis[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
function unstubAllGlobals() {
|
|
||||||
for (const [name, value] of stubbedGlobal) {
|
|
||||||
globalThis[name] = value
|
|
||||||
}
|
|
||||||
stubbedGlobal.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const vi = {
|
export const vi = {
|
||||||
...vitestSpy,
|
...vitestSpy,
|
||||||
mocked: (fn: any) => fn,
|
mocked: (fn: any) => fn,
|
||||||
stubGlobal,
|
stubGlobal,
|
||||||
unstubAllGlobals,
|
unstubAllGlobals,
|
||||||
waitFor: async (fn: Function) => {
|
waitFor,
|
||||||
// less customizations than vi.waitFor but it's good enough for now
|
|
||||||
const timeout = Date.now() + 5000
|
|
||||||
|
|
||||||
let lastError: unknown
|
|
||||||
while (Date.now() < timeout) {
|
|
||||||
try {
|
|
||||||
return await fn()
|
|
||||||
} catch (e) {
|
|
||||||
lastError = e
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError
|
|
||||||
},
|
|
||||||
// todo use @sinonjs/fake-timers (see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/mock/timers.ts)
|
// todo use @sinonjs/fake-timers (see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/mock/timers.ts)
|
||||||
...['setSystemTime', 'advanceTimersByTimeAsync', 'advanceTimersByTime', 'doMock'].reduce(
|
...['setSystemTime', 'advanceTimersByTimeAsync', 'advanceTimersByTime', 'doMock'].reduce(
|
||||||
(acc, name) => ({
|
(acc, name) => ({
|
|
@ -55,3 +55,33 @@ export function setupChai(chai: any, vitestExpect: any) {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stubbedGlobal = new Map()
|
||||||
|
export function stubGlobal(name: string, value: any) {
|
||||||
|
stubbedGlobal.set(name, globalThis[name])
|
||||||
|
globalThis[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unstubAllGlobals() {
|
||||||
|
for (const [name, value] of stubbedGlobal) {
|
||||||
|
globalThis[name] = value
|
||||||
|
}
|
||||||
|
stubbedGlobal.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitFor(fn: Function) {
|
||||||
|
// less customizations than vi.waitFor but it's good enough for now
|
||||||
|
const timeout = Date.now() + 5000
|
||||||
|
|
||||||
|
let lastError: unknown
|
||||||
|
while (Date.now() < timeout) {
|
||||||
|
try {
|
||||||
|
return await fn()
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import { setPlatform } from '../../packages/core/src/platform.js'
|
import { setPlatform } from '../../packages/core/src/platform.js'
|
||||||
|
|
||||||
// @ts-expect-error no .env here
|
// @ts-expect-error no .env here
|
||||||
const TEST_ENV = import.meta.env.TEST_ENV
|
if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') {
|
||||||
if (TEST_ENV === 'browser' || TEST_ENV === 'deno') {
|
|
||||||
setPlatform(new (await import('../../packages/web/src/platform.js')).WebPlatform())
|
setPlatform(new (await import('../../packages/web/src/platform.js')).WebPlatform())
|
||||||
} else {
|
} else {
|
||||||
setPlatform(new (await import('../../packages/node/src/common-internals-node/platform.js')).NodePlatform())
|
setPlatform(new (await import('../../packages/node/src/common-internals-node/platform.js')).NodePlatform())
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { fixupCjs } from './vite-utils/fixup-cjs'
|
||||||
import { testSetup } from './vite-utils/test-setup-plugin'
|
import { testSetup } from './vite-utils/test-setup-plugin'
|
||||||
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
||||||
|
|
||||||
const FIXUP_TEST = resolve(__dirname, 'vite-utils/fixup-bun-test.ts')
|
const POLYFILLS = resolve(__dirname, 'vite-utils/polyfills-bun.ts')
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
|
@ -20,17 +20,14 @@ export default defineConfig({
|
||||||
skipTests: [
|
skipTests: [
|
||||||
// uses timers
|
// uses timers
|
||||||
'core/src/network/config-manager.test.ts',
|
'core/src/network/config-manager.test.ts',
|
||||||
// incompatible spies
|
'core/src/network/persistent-connection.test.ts',
|
||||||
'core/src/utils/crypto/mtproto.test.ts',
|
|
||||||
// snapshot format
|
|
||||||
'tl-utils/src/codegen/errors.test.ts'
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [
|
external: [
|
||||||
'zlib',
|
'node:zlib',
|
||||||
'vitest',
|
'vitest',
|
||||||
'stream',
|
'stream',
|
||||||
'net',
|
'net',
|
||||||
|
@ -66,7 +63,7 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
fixupCjs(),
|
fixupCjs(),
|
||||||
{
|
{
|
||||||
name: 'fix-vitest',
|
name: 'polyfills',
|
||||||
transform(code) {
|
transform(code) {
|
||||||
if (!code.includes('vitest')) return code
|
if (!code.includes('vitest')) return code
|
||||||
code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => {
|
code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => {
|
||||||
|
@ -86,7 +83,7 @@ export default defineConfig({
|
||||||
let code = `import {${newNames.join(', ')}} from 'bun:test'`
|
let code = `import {${newNames.join(', ')}} from 'bun:test'`
|
||||||
|
|
||||||
if (namesFromFixup.length) {
|
if (namesFromFixup.length) {
|
||||||
code += `\nimport { ${namesFromFixup.join(', ')} } from '${FIXUP_TEST}'`
|
code += `\nimport { ${namesFromFixup.join(', ')} } from '${POLYFILLS}'`
|
||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { fixupCjs } from './vite-utils/fixup-cjs'
|
||||||
import { testSetup } from './vite-utils/test-setup-plugin'
|
import { testSetup } from './vite-utils/test-setup-plugin'
|
||||||
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
||||||
|
|
||||||
const FIXUP_TEST = resolve(__dirname, 'vite-utils/fixup-deno-test.ts')
|
const POLYFILLS = resolve(__dirname, 'vite-utils/polyfills-deno.ts')
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
|
@ -25,22 +25,6 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [
|
external: [
|
||||||
// todo which of these are actually needed?
|
|
||||||
'zlib',
|
|
||||||
'vitest',
|
|
||||||
'stream',
|
|
||||||
'net',
|
|
||||||
'crypto',
|
|
||||||
'module',
|
|
||||||
'fs',
|
|
||||||
'fs/promises',
|
|
||||||
'readline',
|
|
||||||
'worker_threads',
|
|
||||||
'events',
|
|
||||||
'path',
|
|
||||||
'util',
|
|
||||||
'os',
|
|
||||||
//
|
|
||||||
/^(jsr|npm|node|https?):/,
|
/^(jsr|npm|node|https?):/,
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
|
@ -61,13 +45,13 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
fixupCjs(),
|
fixupCjs(),
|
||||||
{
|
{
|
||||||
name: 'fix-vitest',
|
name: 'polyfills',
|
||||||
transform(code) {
|
transform(code) {
|
||||||
if (!code.includes('vitest')) return code
|
if (!code.includes('vitest')) return code
|
||||||
code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => {
|
code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => {
|
||||||
const namesParsed = names.split(',').map((name) => name.trim())
|
const namesParsed = names.split(',').map((name) => name.trim())
|
||||||
|
|
||||||
return `import {${namesParsed.join(', ')}} from '${FIXUP_TEST}'`
|
return `import {${namesParsed.join(', ')}} from '${POLYFILLS}'`
|
||||||
})
|
})
|
||||||
return code
|
return code
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,6 +42,8 @@
|
||||||
"@typescript-eslint/parser": "6.4.0",
|
"@typescript-eslint/parser": "6.4.0",
|
||||||
"@vitest/browser": "1.4.0",
|
"@vitest/browser": "1.4.0",
|
||||||
"@vitest/coverage-v8": "1.4.0",
|
"@vitest/coverage-v8": "1.4.0",
|
||||||
|
"@vitest/expect": "1.4.0",
|
||||||
|
"@vitest/spy": "1.4.0",
|
||||||
"@vitest/ui": "1.4.0",
|
"@vitest/ui": "1.4.0",
|
||||||
"chai": "^5.1.0",
|
"chai": "^5.1.0",
|
||||||
"cjs-module-lexer": "1.2.3",
|
"cjs-module-lexer": "1.2.3",
|
||||||
|
|
|
@ -140,13 +140,13 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
expect(spyCreateAesCtr).toHaveBeenCalledTimes(2)
|
expect(spyCreateAesCtr).toHaveBeenCalledTimes(2)
|
||||||
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
p.hexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'),
|
u8HexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'),
|
||||||
u8HexDecode('936b33fa7f97bae025102532233abb26'),
|
u8HexDecode('936b33fa7f97bae025102532233abb26'),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
p.hexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'),
|
u8HexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'),
|
||||||
u8HexDecode('db6aeee6883f45f95def566dadb4b610'),
|
u8HexDecode('db6aeee6883f45f95def566dadb4b610'),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
|
@ -83,7 +83,7 @@ describe('WebSocketTransport', () => {
|
||||||
|
|
||||||
await t.send(p.hexDecode('00010203040506070809'))
|
await t.send(p.hexDecode('00010203040506070809'))
|
||||||
|
|
||||||
expect(socket.send).toHaveBeenCalledWith(p.hexDecode('af020630c8ef14bcf53af33853ea'))
|
expect(socket.send).toHaveBeenCalledWith(u8HexDecode('af020630c8ef14bcf53af33853ea'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly close', async () => {
|
it('should correctly close', async () => {
|
||||||
|
|
|
@ -39,6 +39,12 @@ importers:
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: 1.4.0
|
specifier: 1.4.0
|
||||||
version: 1.4.0(vitest@1.4.0)
|
version: 1.4.0(vitest@1.4.0)
|
||||||
|
'@vitest/expect':
|
||||||
|
specifier: 1.4.0
|
||||||
|
version: 1.4.0
|
||||||
|
'@vitest/spy':
|
||||||
|
specifier: 1.4.0
|
||||||
|
version: 1.4.0
|
||||||
'@vitest/ui':
|
'@vitest/ui':
|
||||||
specifier: 1.4.0
|
specifier: 1.4.0
|
||||||
version: 1.4.0(vitest@1.4.0)
|
version: 1.4.0(vitest@1.4.0)
|
||||||
|
|
Loading…
Reference in a new issue