diff --git a/js/src/jest/index.ts b/js/src/jest/index.ts index 1ef8c48aa..9c358d059 100644 --- a/js/src/jest/index.ts +++ b/js/src/jest/index.ts @@ -17,7 +17,7 @@ import { toBeSemanticCloseTo, } from "./matchers.js"; import { jestAsyncLocalStorageInstance, trackingEnabled } from "./globals.js"; -import expectWithGradedBy from "./vendor/chain.js"; +import { wrapExpect } from "./vendor/chain.js"; import type { SimpleEvaluator } from "./vendor/gradedBy.js"; declare global { @@ -339,11 +339,13 @@ const lsTest = Object.assign(wrapTestMethod(test), { each: eachMethod, }); +const wrappedExpect = wrapExpect(expect); + export { lsTest as test, lsTest as it, lsDescribe as describe, - expectWithGradedBy as expect, + wrappedExpect as expect, }; export { type SimpleEvaluator }; diff --git a/js/src/jest/vendor/chain.ts b/js/src/jest/vendor/chain.ts index 84699130b..a15bac8f3 100644 --- a/js/src/jest/vendor/chain.ts +++ b/js/src/jest/vendor/chain.ts @@ -1,7 +1,7 @@ /** * Adapted from https://github.com/mattphillips/jest-chain/blob/main/src/chain.js */ - +import { expect } from "@jest/globals"; import { gradedBy, SimpleEvaluator } from "./gradedBy.js"; class JestAssertionError extends Error { @@ -58,22 +58,39 @@ const addGradedBy = ( originalArgs: any[], staticPath: string[] = [] ) => { + let spreadMatchers = { ...matchers }; + // Handle Bun, which uses a class + if (Object.keys(matchers).length === 0) { + const prototypeProps = Object.getOwnPropertyNames( + Object.getPrototypeOf(matchers) + ); + spreadMatchers = Object.fromEntries( + prototypeProps.map((prop) => { + try { + return [prop, (matchers as any)[prop]]; + } catch (e) { + // Ignore bizarre Bun bug + return []; + } + }) + ) as any; + } return Object.assign({}, matchers, { gradedBy: function (evaluator: SimpleEvaluator) { const mappedMatchers: any = _wrapMatchers( - matchers, + spreadMatchers, evaluator, originalArgs ); // .not etc. - const staticMatchers = Object.keys(matchers) + const staticMatchers = Object.keys(spreadMatchers) .filter((name) => typeof (matchers as any)[name] !== "function") .map((name) => { return { [name]: Object.assign( {}, ..._wrapMatchers( - matchers, + spreadMatchers, evaluator, originalArgs, staticPath.concat(name) @@ -86,7 +103,7 @@ const addGradedBy = ( }); }; -export default function expectWithGradedBy(expect: any) { +export function wrapExpect(expect: any) { // proxy the expect function const expectProxy = Object.assign( (...args: any[]) => addGradedBy(expect(...args), args), // partially apply expect to get all matchers and chain them @@ -96,4 +113,4 @@ export default function expectWithGradedBy(expect: any) { return expectProxy; } -(globalThis as any).expect = expectWithGradedBy((globalThis as any).expect); +(globalThis as any).expect = wrapExpect((globalThis as any).expect);