fleet-memory/hindsight-integrations/ai-sdk/tests/vitest-compat.ts
Nicolò Boschi 72c25c97e3
feat(typescript-client): Deno compatibility (#607)
* feat(typescript-client): add Deno compatibility

- Switch build from tsc to tsup for dual CJS + ESM output with proper exports field
- Add deno_setup.ts preload that injects Jest-compatible globals (describe/test/expect) via @std/testing/bdd and @std/expect
- Fix generated client.gen.ts: exclude hey-api internal `client` field from RequestInit spread to avoid conflict with Deno.HttpClient
- Add test:deno npm script using --unstable-sloppy-imports and --preload
- Add test-typescript-client-deno CI job using denoland/setup-deno@v2 (v2.x)
- Update docs: rename page to TypeScript / JavaScript Client, add Deno installation section

* feat: add Deno compatibility to ai-sdk and chat integrations

- Switch ai-sdk and chat builds from tsc to tsup (ESM bundle, eliminates
  extension-less import issues in Deno)
- Add deno.json import map to ai-sdk redirecting 'vitest' to a custom
  vitest-compat.ts shim and bare npm specifiers to npm: URLs
- Add vitest-compat.ts shim implementing vi.fn()/vi.spyOn()/vi.mocked()
  using @std/expect's Symbol.for("@MOCK") interface so toHaveBeenCalledWith
  and other mock matchers work under Deno
- Add test:deno script to ai-sdk (all 30 tests pass under Deno)

* ci: add Deno test job for ai-sdk integration

Adds a new test-ai-sdk-integration-deno CI job that runs the ai-sdk
unit tests under Deno LTS, verifying Deno compatibility of the package.

* fix: remove broken link to non-existent n8n blog post in streamlit post

* fix: patch client.gen.ts for Deno compatibility during generation

Add a post-generation patch step to generate-clients.sh that removes
the hey-api internal 'client' field from the RequestInit spread in
client.gen.ts. Deno's Request constructor rejects 'client' because it
conflicts with the Deno.HttpClient option name.
2026-03-18 14:25:35 +01:00

82 lines
3 KiB
TypeScript

/**
* Vitest compatibility shim for running tests under Deno.
*
* Redirected from 'vitest' via deno.json import map.
* Implements vi.fn() / vi.spyOn() / vi.mocked() using the @std/expect
* MockCall interface so that @std/expect matchers (toHaveBeenCalledWith, etc.) work.
*/
import { beforeAll, beforeEach, afterAll, afterEach, describe, it } from "jsr:@std/testing/bdd";
import { expect } from "jsr:@std/expect";
// @std/expect recognises mock functions via this well-known symbol (@std/expect 1.0.18+)
const MOCK_SYMBOL = Symbol.for("@MOCK");
type MockCall = {
args: unknown[];
returned?: unknown;
thrown?: unknown;
timestamp: number;
returns: boolean;
throws: boolean;
};
function createMock(impl?: (...args: unknown[]) => unknown) {
let currentImpl = impl;
const calls: MockCall[] = [];
const mockInfo = { calls };
const mockFn = function (this: unknown, ...args: unknown[]) {
const call: MockCall = {
args,
timestamp: Date.now(),
returns: false,
throws: false,
};
calls.push(call);
try {
const result = currentImpl ? currentImpl.apply(this, args) : undefined;
call.returned = result;
call.returns = true;
return result;
} catch (err) {
call.thrown = err;
call.throws = true;
throw err;
}
};
// @std/expect mock interface — required for toHaveBeenCalledWith etc.
(mockFn as any)[MOCK_SYMBOL] = mockInfo;
// vitest-style chainable methods
(mockFn as any).mockReturnValue = (val: unknown) => { currentImpl = () => val; return mockFn; };
(mockFn as any).mockResolvedValue = (val: unknown) => { currentImpl = () => Promise.resolve(val); return mockFn; };
(mockFn as any).mockRejectedValue = (val: unknown) => { currentImpl = () => Promise.reject(val); return mockFn; };
(mockFn as any).mockImplementation = (fn: (...args: unknown[]) => unknown) => { currentImpl = fn; return mockFn; };
(mockFn as any).mockReset = () => { calls.length = 0; currentImpl = undefined; return mockFn; };
(mockFn as any).mockClear = () => { calls.length = 0; return mockFn; };
(mockFn as any).mockRestore = () => {};
return mockFn;
}
export const vi = {
fn: (impl?: (...args: unknown[]) => unknown) => createMock(impl),
// vi.mocked() is a TypeScript type cast — just return the same value at runtime
mocked: <T>(fn: T): T => fn,
spyOn: <T extends Record<string, unknown>>(obj: T, method: keyof T) => {
const original = obj[method];
const mock = createMock(typeof original === "function" ? (original as (...args: unknown[]) => unknown) : undefined);
const restore = () => {
obj[method] = original;
};
(mock as any).mockRestore = restore;
obj[method] = mock as unknown as T[keyof T];
return mock;
},
};
export { describe, it, it as test, beforeAll, beforeEach, afterAll, afterEach, expect };