From b418577bfa1f9c851f5ea6320afa99935f56824a Mon Sep 17 00:00:00 2001 From: alina sireneva Date: Thu, 25 Apr 2024 05:25:56 +0300 Subject: [PATCH] refactor(test): refactored and improved deno/bun vitest polyfills a bit --- .config/vite-utils/fixup-bun-test.ts | 128 ------------------ .config/vite-utils/polyfills-bun.ts | 31 +++++ .../{fixup-deno-test.ts => polyfills-deno.ts} | 39 +----- .../{chai-setup.ts => polyfills.ts} | 30 ++++ .config/vite-utils/test-setup.mts | 3 +- .config/vite.bun.mts | 13 +- .config/vite.deno.mts | 22 +-- package.json | 2 + .../src/network/transports/obfuscated.test.ts | 4 +- packages/web/src/websocket.test.ts | 2 +- pnpm-lock.yaml | 6 + 11 files changed, 87 insertions(+), 193 deletions(-) delete mode 100644 .config/vite-utils/fixup-bun-test.ts create mode 100644 .config/vite-utils/polyfills-bun.ts rename .config/vite-utils/{fixup-deno-test.ts => polyfills-deno.ts} (60%) rename .config/vite-utils/{chai-setup.ts => polyfills.ts} (74%) diff --git a/.config/vite-utils/fixup-bun-test.ts b/.config/vite-utils/fixup-bun-test.ts deleted file mode 100644 index 28edfb2b..00000000 --- a/.config/vite-utils/fixup-bun-test.ts +++ /dev/null @@ -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) - }, - }), - {}, - ), -} diff --git a/.config/vite-utils/polyfills-bun.ts b/.config/vite-utils/polyfills-bun.ts new file mode 100644 index 00000000..dfd88472 --- /dev/null +++ b/.config/vite-utils/polyfills-bun.ts @@ -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) + }, + }), + {}, + ), +} diff --git a/.config/vite-utils/fixup-deno-test.ts b/.config/vite-utils/polyfills-deno.ts similarity index 60% rename from .config/vite-utils/fixup-deno-test.ts rename to .config/vite-utils/polyfills-deno.ts index 5d0fb827..564280b0 100644 --- a/.config/vite-utils/fixup-deno-test.ts +++ b/.config/vite-utils/polyfills-deno.ts @@ -1,18 +1,19 @@ // @ts-expect-error no typings import { describe as _describe, it, beforeEach, afterEach, beforeAll, afterAll } from 'jsr:@std/testing/bdd' // @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 import * as chai from 'npm:chai' // @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 { setupChai } from './chai-setup' +import { setupChai, stubGlobal, unstubAllGlobals, waitFor } from './polyfills' export { it, beforeEach, afterEach, beforeAll, afterAll } setupChai(chai, vitestExpect) +// https://github.com/denoland/deno_std/issues/2213 Object.defineProperty(it, 'each', { value: (items: any[][]) => (name: string, fn: Function) => { return items.map((item) => { @@ -21,10 +22,10 @@ Object.defineProperty(it, 'each', { }, }) +// https://github.com/denoland/deno_std/issues/4634 export const describe = (...args) => { const fn = args.find((arg) => typeof arg === 'function') if (fn.toString().startsWith('async')) { - // https://github.com/denoland/deno_std/issues/4634 return } @@ -36,40 +37,12 @@ describe.ignore = _describe.ignore 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 = { ...vitestSpy, mocked: (fn: any) => fn, stubGlobal, unstubAllGlobals, - waitFor: async (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 - }, + waitFor, // 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( (acc, name) => ({ diff --git a/.config/vite-utils/chai-setup.ts b/.config/vite-utils/polyfills.ts similarity index 74% rename from .config/vite-utils/chai-setup.ts rename to .config/vite-utils/polyfills.ts index b6a9bfa9..f020ab89 100644 --- a/.config/vite-utils/chai-setup.ts +++ b/.config/vite-utils/polyfills.ts @@ -55,3 +55,33 @@ export function setupChai(chai: any, vitestExpect: any) { 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 +} diff --git a/.config/vite-utils/test-setup.mts b/.config/vite-utils/test-setup.mts index 38c5f7e3..c402f362 100644 --- a/.config/vite-utils/test-setup.mts +++ b/.config/vite-utils/test-setup.mts @@ -1,8 +1,7 @@ import { setPlatform } from '../../packages/core/src/platform.js' // @ts-expect-error no .env here -const TEST_ENV = import.meta.env.TEST_ENV -if (TEST_ENV === 'browser' || TEST_ENV === 'deno') { +if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') { setPlatform(new (await import('../../packages/web/src/platform.js')).WebPlatform()) } else { setPlatform(new (await import('../../packages/node/src/common-internals-node/platform.js')).NodePlatform()) diff --git a/.config/vite.bun.mts b/.config/vite.bun.mts index ed8bffd8..66f62f5e 100644 --- a/.config/vite.bun.mts +++ b/.config/vite.bun.mts @@ -6,7 +6,7 @@ import { fixupCjs } from './vite-utils/fixup-cjs' import { testSetup } from './vite-utils/test-setup-plugin' 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({ build: { @@ -20,17 +20,14 @@ export default defineConfig({ skipTests: [ // uses timers 'core/src/network/config-manager.test.ts', - // incompatible spies - 'core/src/utils/crypto/mtproto.test.ts', - // snapshot format - 'tl-utils/src/codegen/errors.test.ts' + 'core/src/network/persistent-connection.test.ts', ], }), formats: ['es'], }, rollupOptions: { external: [ - 'zlib', + 'node:zlib', 'vitest', 'stream', 'net', @@ -66,7 +63,7 @@ export default defineConfig({ plugins: [ fixupCjs(), { - name: 'fix-vitest', + name: 'polyfills', transform(code) { if (!code.includes('vitest')) return code code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => { @@ -86,7 +83,7 @@ export default defineConfig({ let code = `import {${newNames.join(', ')}} from 'bun:test'` if (namesFromFixup.length) { - code += `\nimport { ${namesFromFixup.join(', ')} } from '${FIXUP_TEST}'` + code += `\nimport { ${namesFromFixup.join(', ')} } from '${POLYFILLS}'` } return code }) diff --git a/.config/vite.deno.mts b/.config/vite.deno.mts index e0bb6f18..073611d4 100644 --- a/.config/vite.deno.mts +++ b/.config/vite.deno.mts @@ -4,7 +4,7 @@ import { fixupCjs } from './vite-utils/fixup-cjs' import { testSetup } from './vite-utils/test-setup-plugin' 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({ build: { @@ -25,22 +25,6 @@ export default defineConfig({ }, rollupOptions: { 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?):/, ], output: { @@ -61,13 +45,13 @@ export default defineConfig({ plugins: [ fixupCjs(), { - name: 'fix-vitest', + name: 'polyfills', transform(code) { if (!code.includes('vitest')) return code code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => { const namesParsed = names.split(',').map((name) => name.trim()) - return `import {${namesParsed.join(', ')}} from '${FIXUP_TEST}'` + return `import {${namesParsed.join(', ')}} from '${POLYFILLS}'` }) return code }, diff --git a/package.json b/package.json index 17e28d38..ebd5c48a 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "@typescript-eslint/parser": "6.4.0", "@vitest/browser": "1.4.0", "@vitest/coverage-v8": "1.4.0", + "@vitest/expect": "1.4.0", + "@vitest/spy": "1.4.0", "@vitest/ui": "1.4.0", "chai": "^5.1.0", "cjs-module-lexer": "1.2.3", diff --git a/packages/core/src/network/transports/obfuscated.test.ts b/packages/core/src/network/transports/obfuscated.test.ts index be574df5..805b74ff 100644 --- a/packages/core/src/network/transports/obfuscated.test.ts +++ b/packages/core/src/network/transports/obfuscated.test.ts @@ -140,13 +140,13 @@ describe('ObfuscatedPacketCodec', () => { expect(spyCreateAesCtr).toHaveBeenCalledTimes(2) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 1, - p.hexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'), + u8HexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'), u8HexDecode('936b33fa7f97bae025102532233abb26'), true, ) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 2, - p.hexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'), + u8HexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'), u8HexDecode('db6aeee6883f45f95def566dadb4b610'), false, ) diff --git a/packages/web/src/websocket.test.ts b/packages/web/src/websocket.test.ts index 13c7e51a..ff1dd508 100644 --- a/packages/web/src/websocket.test.ts +++ b/packages/web/src/websocket.test.ts @@ -83,7 +83,7 @@ describe('WebSocketTransport', () => { await t.send(p.hexDecode('00010203040506070809')) - expect(socket.send).toHaveBeenCalledWith(p.hexDecode('af020630c8ef14bcf53af33853ea')) + expect(socket.send).toHaveBeenCalledWith(u8HexDecode('af020630c8ef14bcf53af33853ea')) }) it('should correctly close', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27b2801a..cdb03f9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,12 @@ importers: '@vitest/coverage-v8': specifier: 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': specifier: 1.4.0 version: 1.4.0(vitest@1.4.0)