@ricsam/quickjs-test-environment 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/describe.ts"],
3
+ "sources": ["../../../src/globals/describe.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState, RegisteredSuite, RegisteredTest } from \"../types.cjs\";\n\nlet suiteIdCounter = 0;\nlet testIdCounter = 0;\n\nexport function createSuite(\n name: string,\n parent: RegisteredSuite | null,\n modifier: RegisteredSuite[\"modifier\"] = \"none\"\n): RegisteredSuite {\n return {\n id: `suite_${++suiteIdCounter}`,\n name,\n path: parent ? [...parent.path, name] : [name],\n tests: [],\n beforeAll: [],\n afterAll: [],\n beforeEach: [],\n afterEach: [],\n children: [],\n parent,\n modifier,\n };\n}\n\nexport function setupDescribe(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Helper to create describe function with modifier\n const createDescribeFn = (modifier: RegisteredSuite[\"modifier\"]) => {\n return context.newFunction(\"describe\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n // Create new suite\n const suite = createSuite(name, state.currentSuite, modifier);\n\n // Add to parent or root\n if (state.currentSuite) {\n state.currentSuite.children.push(suite);\n } else {\n state.suites.push(suite);\n }\n\n // Execute the describe body to discover nested tests\n const previousSuite = state.currentSuite;\n state.currentSuite = suite;\n\n try {\n const result = context.callFunction(fnHandle, context.undefined);\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Error in describe \"${name}\": ${error}`);\n }\n result.value.dispose();\n } finally {\n state.currentSuite = previousSuite;\n }\n\n return context.undefined;\n });\n };\n\n // describe()\n const describeFn = createDescribeFn(\"none\");\n context.setProp(context.global, \"describe\", describeFn);\n handles.push(describeFn);\n\n // describe.skip()\n const describeSkipFn = createDescribeFn(\"skip\");\n context.setProp(describeFn, \"skip\", describeSkipFn);\n handles.push(describeSkipFn);\n\n // describe.only()\n const describeOnlyFn = createDescribeFn(\"only\");\n context.setProp(describeFn, \"only\", describeOnlyFn);\n handles.push(describeOnlyFn);\n\n // describe.todo() - fn is optional\n const describeTodoFn = context.newFunction(\n \"describe.todo\",\n (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n const suite = createSuite(name, state.currentSuite, \"todo\");\n\n if (state.currentSuite) {\n state.currentSuite.children.push(suite);\n } else {\n state.suites.push(suite);\n }\n\n // Only execute body if provided and is a function\n if (fnHandle && context.typeof(fnHandle) === \"function\") {\n const previousSuite = state.currentSuite;\n state.currentSuite = suite;\n\n try {\n const result = context.callFunction(fnHandle, context.undefined);\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n } finally {\n state.currentSuite = previousSuite;\n }\n }\n\n return context.undefined;\n }\n );\n context.setProp(describeFn, \"todo\", describeTodoFn);\n handles.push(describeTodoFn);\n\n return handles;\n}\n\nexport function setupIt(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Helper to create it/test function with modifier\n const createItFn = (modifier: RegisteredTest[\"modifier\"]) => {\n return context.newFunction(\"it\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n if (!state.currentSuite) {\n // Create implicit root suite for top-level tests\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n\n // Dup the function handle to keep it alive\n const fnDup = fnHandle.dup();\n\n const test: RegisteredTest = {\n id: `test_${++testIdCounter}`,\n name,\n fn: fnDup,\n suite: state.currentSuite,\n modifier,\n };\n\n state.currentSuite.tests.push(test);\n\n return context.undefined;\n });\n };\n\n // it()\n const itFn = createItFn(\"none\");\n context.setProp(context.global, \"it\", itFn);\n handles.push(itFn);\n\n // it.skip()\n const itSkipFn = createItFn(\"skip\");\n context.setProp(itFn, \"skip\", itSkipFn);\n handles.push(itSkipFn);\n\n // it.only()\n const itOnlyFn = createItFn(\"only\");\n context.setProp(itFn, \"only\", itOnlyFn);\n handles.push(itOnlyFn);\n\n // it.todo() - fn is optional\n const itTodoFn = context.newFunction(\"it.todo\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n if (!state.currentSuite) {\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n\n // fn is optional for todo - use undefined dup if not provided\n const fnDup =\n fnHandle && context.typeof(fnHandle) === \"function\"\n ? fnHandle.dup()\n : context.undefined.dup();\n\n const test: RegisteredTest = {\n id: `test_${++testIdCounter}`,\n name,\n fn: fnDup,\n suite: state.currentSuite,\n modifier: \"todo\",\n };\n\n state.currentSuite.tests.push(test);\n\n return context.undefined;\n });\n context.setProp(itFn, \"todo\", itTodoFn);\n handles.push(itTodoFn);\n\n // test() - alias for it()\n const testFn = itFn.dup();\n context.setProp(context.global, \"test\", testFn);\n handles.push(testFn);\n\n // test.skip/only/todo - point to same functions as it.*\n context.setProp(testFn, \"skip\", itSkipFn);\n context.setProp(testFn, \"only\", itOnlyFn);\n context.setProp(testFn, \"todo\", itTodoFn);\n\n return handles;\n}\n"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/expect.ts"],
3
+ "sources": ["../../../src/globals/expect.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState } from \"../types.cjs\";\n\nexport function setupExpect(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Implement expect entirely in JavaScript for simplicity\n const expectCode = `\n(function() {\n // Deep equality check\n function deepEqual(a, b) {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (typeof a === 'object') {\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n\n if (Array.isArray(a)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!deepEqual(a[key], b[key])) return false;\n }\n return true;\n }\n\n return false;\n }\n\n // Strict equality (checks undefined values and array holes)\n function strictEqual(a, b) {\n if (!deepEqual(a, b)) return false;\n\n if (typeof a === 'object' && a !== null) {\n if (Array.isArray(a)) {\n // Check for sparse arrays\n for (let i = 0; i < a.length; i++) {\n if ((i in a) !== (i in b)) return false;\n }\n } else {\n // Check for undefined values\n const keysA = Object.keys(a);\n for (const key of keysA) {\n if (a[key] === undefined && !(key in b)) return false;\n }\n }\n }\n\n return true;\n }\n\n // Format value for error messages\n function format(value) {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'symbol') return value.toString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n\n // Create assertion error\n function AssertionError(message, matcherName, expected, actual) {\n const error = new Error(message);\n error.name = 'AssertionError';\n error.matcherName = matcherName;\n error.expected = expected;\n error.actual = actual;\n return error;\n }\n\n // Create matchers object\n function createMatchers(actual, isNot) {\n const matchers = {\n // Strict equality (===)\n toBe(expected) {\n const pass = actual === expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be \\${format(expected)}\\`,\n 'toBe',\n expected,\n actual\n );\n }\n },\n\n // Deep equality\n toEqual(expected) {\n const pass = deepEqual(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to equal \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to equal \\${format(expected)}\\`,\n 'toEqual',\n expected,\n actual\n );\n }\n },\n\n // Strict deep equality\n toStrictEqual(expected) {\n const pass = strictEqual(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to strictly equal \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to strictly equal \\${format(expected)}\\`,\n 'toStrictEqual',\n expected,\n actual\n );\n }\n },\n\n // Truthy/Falsy\n toBeTruthy() {\n const pass = !!actual;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be truthy\\`\n : \\`Expected \\${format(actual)} to be truthy\\`,\n 'toBeTruthy',\n 'truthy value',\n actual\n );\n }\n },\n\n toBeFalsy() {\n const pass = !actual;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be falsy\\`\n : \\`Expected \\${format(actual)} to be falsy\\`,\n 'toBeFalsy',\n 'falsy value',\n actual\n );\n }\n },\n\n // Null/Undefined/Defined\n toBeNull() {\n const pass = actual === null;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be null\\`\n : \\`Expected \\${format(actual)} to be null\\`,\n 'toBeNull',\n null,\n actual\n );\n }\n },\n\n toBeUndefined() {\n const pass = actual === undefined;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be undefined\\`\n : \\`Expected \\${format(actual)} to be undefined\\`,\n 'toBeUndefined',\n undefined,\n actual\n );\n }\n },\n\n toBeDefined() {\n const pass = actual !== undefined;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} to be undefined\\`\n : \\`Expected \\${format(actual)} to be defined\\`,\n 'toBeDefined',\n 'defined value',\n actual\n );\n }\n },\n\n toBeNaN() {\n const pass = Number.isNaN(actual);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be NaN\\`\n : \\`Expected \\${format(actual)} to be NaN\\`,\n 'toBeNaN',\n NaN,\n actual\n );\n }\n },\n\n // Numeric comparisons\n toBeGreaterThan(expected) {\n const pass = actual > expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be greater than \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be greater than \\${format(expected)}\\`,\n 'toBeGreaterThan',\n expected,\n actual\n );\n }\n },\n\n toBeGreaterThanOrEqual(expected) {\n const pass = actual >= expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be greater than or equal to \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be greater than or equal to \\${format(expected)}\\`,\n 'toBeGreaterThanOrEqual',\n expected,\n actual\n );\n }\n },\n\n toBeLessThan(expected) {\n const pass = actual < expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be less than \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be less than \\${format(expected)}\\`,\n 'toBeLessThan',\n expected,\n actual\n );\n }\n },\n\n toBeLessThanOrEqual(expected) {\n const pass = actual <= expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be less than or equal to \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be less than or equal to \\${format(expected)}\\`,\n 'toBeLessThanOrEqual',\n expected,\n actual\n );\n }\n },\n\n // Contains\n toContain(expected) {\n let pass = false;\n if (typeof actual === 'string') {\n pass = actual.includes(expected);\n } else if (Array.isArray(actual)) {\n pass = actual.includes(expected);\n }\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to contain \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to contain \\${format(expected)}\\`,\n 'toContain',\n expected,\n actual\n );\n }\n },\n\n // Length\n toHaveLength(expected) {\n const actualLength = actual?.length;\n const pass = actualLength === expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected length not to be \\${expected}, but got \\${actualLength}\\`\n : \\`Expected length to be \\${expected}, but got \\${actualLength}\\`,\n 'toHaveLength',\n expected,\n actualLength\n );\n }\n },\n\n // Property\n toHaveProperty(key, value) {\n const hasKey = key in Object(actual);\n let pass = hasKey;\n if (hasKey && arguments.length > 1) {\n pass = deepEqual(actual[key], value);\n }\n if (isNot ? pass : !pass) {\n const message = arguments.length > 1\n ? (isNot\n ? \\`Expected \\${format(actual)} not to have property \"\\${key}\" with value \\${format(value)}\\`\n : \\`Expected \\${format(actual)} to have property \"\\${key}\" with value \\${format(value)}\\`)\n : (isNot\n ? \\`Expected \\${format(actual)} not to have property \"\\${key}\"\\`\n : \\`Expected \\${format(actual)} to have property \"\\${key}\"\\`);\n throw AssertionError(message, 'toHaveProperty', value, actual?.[key]);\n }\n },\n\n // Throws\n toThrow(expected) {\n if (typeof actual !== 'function') {\n throw AssertionError('Expected a function', 'toThrow', 'function', typeof actual);\n }\n\n let threw = false;\n let error;\n try {\n actual();\n } catch (e) {\n threw = true;\n error = e;\n }\n\n let pass = threw;\n if (threw && expected !== undefined) {\n if (typeof expected === 'string') {\n pass = error?.message?.includes(expected);\n } else if (expected instanceof RegExp) {\n pass = expected.test(error?.message);\n } else if (expected instanceof Error) {\n pass = error?.message === expected.message;\n }\n }\n\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected function not to throw\\`\n : threw\n ? \\`Expected error message to match \\${format(expected)}, got \\${format(error?.message)}\\`\n : \\`Expected function to throw\\`,\n 'toThrow',\n expected,\n error\n );\n }\n },\n\n // Match\n toMatch(pattern) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n const pass = regex.test(actual);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to match \\${format(pattern)}\\`\n : \\`Expected \\${format(actual)} to match \\${format(pattern)}\\`,\n 'toMatch',\n pattern,\n actual\n );\n }\n },\n\n // Match object\n toMatchObject(expected) {\n function matches(actual, expected) {\n if (typeof expected !== 'object' || expected === null) {\n return deepEqual(actual, expected);\n }\n for (const key of Object.keys(expected)) {\n if (!matches(actual?.[key], expected[key])) return false;\n }\n return true;\n }\n\n const pass = matches(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to match object \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to match object \\${format(expected)}\\`,\n 'toMatchObject',\n expected,\n actual\n );\n }\n },\n\n // Instance of\n toBeInstanceOf(expected) {\n const pass = actual instanceof expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be instance of \\${expected?.name || expected}\\`\n : \\`Expected \\${format(actual)} to be instance of \\${expected?.name || expected}\\`,\n 'toBeInstanceOf',\n expected?.name || expected,\n actual?.constructor?.name || actual\n );\n }\n },\n };\n\n // Add .not modifier\n if (!isNot) {\n matchers.not = createMatchers(actual, true);\n }\n\n return matchers;\n }\n\n // Create promise matchers\n function createPromiseMatchers(promise, isRejects) {\n return {\n async toBe(expected) {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBe', 'rejection', result);\n }\n expect(result).toBe(expected);\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBe(expected);\n }\n },\n async toEqual(expected) {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toEqual', 'rejection', result);\n }\n expect(result).toEqual(expected);\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toEqual(expected);\n }\n },\n async toBeTruthy() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeTruthy', 'rejection', result);\n }\n expect(result).toBeTruthy();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeTruthy();\n }\n },\n async toBeFalsy() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeFalsy', 'rejection', result);\n }\n expect(result).toBeFalsy();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeFalsy();\n }\n },\n async toBeNull() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeNull', 'rejection', result);\n }\n expect(result).toBeNull();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeNull();\n }\n },\n async toBeUndefined() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeUndefined', 'rejection', result);\n }\n expect(result).toBeUndefined();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeUndefined();\n }\n },\n async toBeDefined() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeDefined', 'rejection', result);\n }\n expect(result).toBeDefined();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeDefined();\n }\n },\n async toThrow(expected) {\n if (!isRejects) {\n throw AssertionError('toThrow can only be used with rejects', 'toThrow', 'rejects', 'resolves');\n }\n try {\n await promise;\n throw AssertionError('Expected promise to reject', 'rejects.toThrow', 'rejection', 'resolved');\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (expected !== undefined) {\n if (typeof expected === 'string' && !error?.message?.includes(expected)) {\n throw AssertionError(\n \\`Expected error message to include \"\\${expected}\", got \"\\${error?.message}\"\\`,\n 'rejects.toThrow',\n expected,\n error?.message\n );\n }\n if (expected instanceof RegExp && !expected.test(error?.message)) {\n throw AssertionError(\n \\`Expected error message to match \\${expected}, got \"\\${error?.message}\"\\`,\n 'rejects.toThrow',\n expected,\n error?.message\n );\n }\n }\n }\n }\n };\n }\n\n // Main expect function\n function expect(actual) {\n const matchers = createMatchers(actual, false);\n\n // Add .resolves modifier\n matchers.resolves = createPromiseMatchers(Promise.resolve(actual), false);\n\n // Add .rejects modifier\n matchers.rejects = createPromiseMatchers(actual, true);\n\n return matchers;\n }\n\n // Expose globally\n globalThis.expect = expect;\n})();\n`;\n\n const result = context.evalCode(expectCode);\n if (result.error) {\n const errorDump = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to setup expect: ${errorDump}`);\n }\n result.value.dispose();\n\n return handles;\n}\n"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/hooks.ts"],
3
+ "sources": ["../../../src/globals/hooks.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState, RegisteredSuite } from \"../types.cjs\";\nimport { createSuite } from \"./describe.cjs\";\n\n/**\n * Helper to ensure we have a current suite for hook registration.\n * Creates an implicit root suite if called at top level.\n */\nfunction ensureSuite(state: TestState): RegisteredSuite {\n if (!state.currentSuite) {\n // Create implicit root suite for top-level hooks\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n return state.currentSuite;\n}\n\nexport function setupHooks(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // beforeAll - runs once before all tests in the suite\n const beforeAllFn = context.newFunction(\"beforeAll\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.beforeAll.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"beforeAll\", beforeAllFn);\n handles.push(beforeAllFn);\n\n // afterAll - runs once after all tests in the suite\n const afterAllFn = context.newFunction(\"afterAll\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.afterAll.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"afterAll\", afterAllFn);\n handles.push(afterAllFn);\n\n // beforeEach - runs before each test in the suite and nested suites\n const beforeEachFn = context.newFunction(\"beforeEach\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.beforeEach.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"beforeEach\", beforeEachFn);\n handles.push(beforeEachFn);\n\n // afterEach - runs after each test in the suite and nested suites\n const afterEachFn = context.newFunction(\"afterEach\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.afterEach.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"afterEach\", afterEachFn);\n handles.push(afterEachFn);\n\n return handles;\n}\n"
6
6
  ],
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-test-environment",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "commonjs"
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/describe.ts"],
3
+ "sources": ["../../../src/globals/describe.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState, RegisteredSuite, RegisteredTest } from \"../types.mjs\";\n\nlet suiteIdCounter = 0;\nlet testIdCounter = 0;\n\nexport function createSuite(\n name: string,\n parent: RegisteredSuite | null,\n modifier: RegisteredSuite[\"modifier\"] = \"none\"\n): RegisteredSuite {\n return {\n id: `suite_${++suiteIdCounter}`,\n name,\n path: parent ? [...parent.path, name] : [name],\n tests: [],\n beforeAll: [],\n afterAll: [],\n beforeEach: [],\n afterEach: [],\n children: [],\n parent,\n modifier,\n };\n}\n\nexport function setupDescribe(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Helper to create describe function with modifier\n const createDescribeFn = (modifier: RegisteredSuite[\"modifier\"]) => {\n return context.newFunction(\"describe\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n // Create new suite\n const suite = createSuite(name, state.currentSuite, modifier);\n\n // Add to parent or root\n if (state.currentSuite) {\n state.currentSuite.children.push(suite);\n } else {\n state.suites.push(suite);\n }\n\n // Execute the describe body to discover nested tests\n const previousSuite = state.currentSuite;\n state.currentSuite = suite;\n\n try {\n const result = context.callFunction(fnHandle, context.undefined);\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Error in describe \"${name}\": ${error}`);\n }\n result.value.dispose();\n } finally {\n state.currentSuite = previousSuite;\n }\n\n return context.undefined;\n });\n };\n\n // describe()\n const describeFn = createDescribeFn(\"none\");\n context.setProp(context.global, \"describe\", describeFn);\n handles.push(describeFn);\n\n // describe.skip()\n const describeSkipFn = createDescribeFn(\"skip\");\n context.setProp(describeFn, \"skip\", describeSkipFn);\n handles.push(describeSkipFn);\n\n // describe.only()\n const describeOnlyFn = createDescribeFn(\"only\");\n context.setProp(describeFn, \"only\", describeOnlyFn);\n handles.push(describeOnlyFn);\n\n // describe.todo() - fn is optional\n const describeTodoFn = context.newFunction(\n \"describe.todo\",\n (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n const suite = createSuite(name, state.currentSuite, \"todo\");\n\n if (state.currentSuite) {\n state.currentSuite.children.push(suite);\n } else {\n state.suites.push(suite);\n }\n\n // Only execute body if provided and is a function\n if (fnHandle && context.typeof(fnHandle) === \"function\") {\n const previousSuite = state.currentSuite;\n state.currentSuite = suite;\n\n try {\n const result = context.callFunction(fnHandle, context.undefined);\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n } finally {\n state.currentSuite = previousSuite;\n }\n }\n\n return context.undefined;\n }\n );\n context.setProp(describeFn, \"todo\", describeTodoFn);\n handles.push(describeTodoFn);\n\n return handles;\n}\n\nexport function setupIt(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Helper to create it/test function with modifier\n const createItFn = (modifier: RegisteredTest[\"modifier\"]) => {\n return context.newFunction(\"it\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n if (!state.currentSuite) {\n // Create implicit root suite for top-level tests\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n\n // Dup the function handle to keep it alive\n const fnDup = fnHandle.dup();\n\n const test: RegisteredTest = {\n id: `test_${++testIdCounter}`,\n name,\n fn: fnDup,\n suite: state.currentSuite,\n modifier,\n };\n\n state.currentSuite.tests.push(test);\n\n return context.undefined;\n });\n };\n\n // it()\n const itFn = createItFn(\"none\");\n context.setProp(context.global, \"it\", itFn);\n handles.push(itFn);\n\n // it.skip()\n const itSkipFn = createItFn(\"skip\");\n context.setProp(itFn, \"skip\", itSkipFn);\n handles.push(itSkipFn);\n\n // it.only()\n const itOnlyFn = createItFn(\"only\");\n context.setProp(itFn, \"only\", itOnlyFn);\n handles.push(itOnlyFn);\n\n // it.todo() - fn is optional\n const itTodoFn = context.newFunction(\"it.todo\", (nameHandle, fnHandle) => {\n const name = context.getString(nameHandle);\n\n if (!state.currentSuite) {\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n\n // fn is optional for todo - use undefined dup if not provided\n const fnDup =\n fnHandle && context.typeof(fnHandle) === \"function\"\n ? fnHandle.dup()\n : context.undefined.dup();\n\n const test: RegisteredTest = {\n id: `test_${++testIdCounter}`,\n name,\n fn: fnDup,\n suite: state.currentSuite,\n modifier: \"todo\",\n };\n\n state.currentSuite.tests.push(test);\n\n return context.undefined;\n });\n context.setProp(itFn, \"todo\", itTodoFn);\n handles.push(itTodoFn);\n\n // test() - alias for it()\n const testFn = itFn.dup();\n context.setProp(context.global, \"test\", testFn);\n handles.push(testFn);\n\n // test.skip/only/todo - point to same functions as it.*\n context.setProp(testFn, \"skip\", itSkipFn);\n context.setProp(testFn, \"only\", itOnlyFn);\n context.setProp(testFn, \"todo\", itTodoFn);\n\n return handles;\n}\n"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/expect.ts"],
3
+ "sources": ["../../../src/globals/expect.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState } from \"../types.mjs\";\n\nexport function setupExpect(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // Implement expect entirely in JavaScript for simplicity\n const expectCode = `\n(function() {\n // Deep equality check\n function deepEqual(a, b) {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (typeof a === 'object') {\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n\n if (Array.isArray(a)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!deepEqual(a[key], b[key])) return false;\n }\n return true;\n }\n\n return false;\n }\n\n // Strict equality (checks undefined values and array holes)\n function strictEqual(a, b) {\n if (!deepEqual(a, b)) return false;\n\n if (typeof a === 'object' && a !== null) {\n if (Array.isArray(a)) {\n // Check for sparse arrays\n for (let i = 0; i < a.length; i++) {\n if ((i in a) !== (i in b)) return false;\n }\n } else {\n // Check for undefined values\n const keysA = Object.keys(a);\n for (const key of keysA) {\n if (a[key] === undefined && !(key in b)) return false;\n }\n }\n }\n\n return true;\n }\n\n // Format value for error messages\n function format(value) {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'symbol') return value.toString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n\n // Create assertion error\n function AssertionError(message, matcherName, expected, actual) {\n const error = new Error(message);\n error.name = 'AssertionError';\n error.matcherName = matcherName;\n error.expected = expected;\n error.actual = actual;\n return error;\n }\n\n // Create matchers object\n function createMatchers(actual, isNot) {\n const matchers = {\n // Strict equality (===)\n toBe(expected) {\n const pass = actual === expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be \\${format(expected)}\\`,\n 'toBe',\n expected,\n actual\n );\n }\n },\n\n // Deep equality\n toEqual(expected) {\n const pass = deepEqual(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to equal \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to equal \\${format(expected)}\\`,\n 'toEqual',\n expected,\n actual\n );\n }\n },\n\n // Strict deep equality\n toStrictEqual(expected) {\n const pass = strictEqual(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to strictly equal \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to strictly equal \\${format(expected)}\\`,\n 'toStrictEqual',\n expected,\n actual\n );\n }\n },\n\n // Truthy/Falsy\n toBeTruthy() {\n const pass = !!actual;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be truthy\\`\n : \\`Expected \\${format(actual)} to be truthy\\`,\n 'toBeTruthy',\n 'truthy value',\n actual\n );\n }\n },\n\n toBeFalsy() {\n const pass = !actual;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be falsy\\`\n : \\`Expected \\${format(actual)} to be falsy\\`,\n 'toBeFalsy',\n 'falsy value',\n actual\n );\n }\n },\n\n // Null/Undefined/Defined\n toBeNull() {\n const pass = actual === null;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be null\\`\n : \\`Expected \\${format(actual)} to be null\\`,\n 'toBeNull',\n null,\n actual\n );\n }\n },\n\n toBeUndefined() {\n const pass = actual === undefined;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be undefined\\`\n : \\`Expected \\${format(actual)} to be undefined\\`,\n 'toBeUndefined',\n undefined,\n actual\n );\n }\n },\n\n toBeDefined() {\n const pass = actual !== undefined;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} to be undefined\\`\n : \\`Expected \\${format(actual)} to be defined\\`,\n 'toBeDefined',\n 'defined value',\n actual\n );\n }\n },\n\n toBeNaN() {\n const pass = Number.isNaN(actual);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be NaN\\`\n : \\`Expected \\${format(actual)} to be NaN\\`,\n 'toBeNaN',\n NaN,\n actual\n );\n }\n },\n\n // Numeric comparisons\n toBeGreaterThan(expected) {\n const pass = actual > expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be greater than \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be greater than \\${format(expected)}\\`,\n 'toBeGreaterThan',\n expected,\n actual\n );\n }\n },\n\n toBeGreaterThanOrEqual(expected) {\n const pass = actual >= expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be greater than or equal to \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be greater than or equal to \\${format(expected)}\\`,\n 'toBeGreaterThanOrEqual',\n expected,\n actual\n );\n }\n },\n\n toBeLessThan(expected) {\n const pass = actual < expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be less than \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be less than \\${format(expected)}\\`,\n 'toBeLessThan',\n expected,\n actual\n );\n }\n },\n\n toBeLessThanOrEqual(expected) {\n const pass = actual <= expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be less than or equal to \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to be less than or equal to \\${format(expected)}\\`,\n 'toBeLessThanOrEqual',\n expected,\n actual\n );\n }\n },\n\n // Contains\n toContain(expected) {\n let pass = false;\n if (typeof actual === 'string') {\n pass = actual.includes(expected);\n } else if (Array.isArray(actual)) {\n pass = actual.includes(expected);\n }\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to contain \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to contain \\${format(expected)}\\`,\n 'toContain',\n expected,\n actual\n );\n }\n },\n\n // Length\n toHaveLength(expected) {\n const actualLength = actual?.length;\n const pass = actualLength === expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected length not to be \\${expected}, but got \\${actualLength}\\`\n : \\`Expected length to be \\${expected}, but got \\${actualLength}\\`,\n 'toHaveLength',\n expected,\n actualLength\n );\n }\n },\n\n // Property\n toHaveProperty(key, value) {\n const hasKey = key in Object(actual);\n let pass = hasKey;\n if (hasKey && arguments.length > 1) {\n pass = deepEqual(actual[key], value);\n }\n if (isNot ? pass : !pass) {\n const message = arguments.length > 1\n ? (isNot\n ? \\`Expected \\${format(actual)} not to have property \"\\${key}\" with value \\${format(value)}\\`\n : \\`Expected \\${format(actual)} to have property \"\\${key}\" with value \\${format(value)}\\`)\n : (isNot\n ? \\`Expected \\${format(actual)} not to have property \"\\${key}\"\\`\n : \\`Expected \\${format(actual)} to have property \"\\${key}\"\\`);\n throw AssertionError(message, 'toHaveProperty', value, actual?.[key]);\n }\n },\n\n // Throws\n toThrow(expected) {\n if (typeof actual !== 'function') {\n throw AssertionError('Expected a function', 'toThrow', 'function', typeof actual);\n }\n\n let threw = false;\n let error;\n try {\n actual();\n } catch (e) {\n threw = true;\n error = e;\n }\n\n let pass = threw;\n if (threw && expected !== undefined) {\n if (typeof expected === 'string') {\n pass = error?.message?.includes(expected);\n } else if (expected instanceof RegExp) {\n pass = expected.test(error?.message);\n } else if (expected instanceof Error) {\n pass = error?.message === expected.message;\n }\n }\n\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected function not to throw\\`\n : threw\n ? \\`Expected error message to match \\${format(expected)}, got \\${format(error?.message)}\\`\n : \\`Expected function to throw\\`,\n 'toThrow',\n expected,\n error\n );\n }\n },\n\n // Match\n toMatch(pattern) {\n const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;\n const pass = regex.test(actual);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to match \\${format(pattern)}\\`\n : \\`Expected \\${format(actual)} to match \\${format(pattern)}\\`,\n 'toMatch',\n pattern,\n actual\n );\n }\n },\n\n // Match object\n toMatchObject(expected) {\n function matches(actual, expected) {\n if (typeof expected !== 'object' || expected === null) {\n return deepEqual(actual, expected);\n }\n for (const key of Object.keys(expected)) {\n if (!matches(actual?.[key], expected[key])) return false;\n }\n return true;\n }\n\n const pass = matches(actual, expected);\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to match object \\${format(expected)}\\`\n : \\`Expected \\${format(actual)} to match object \\${format(expected)}\\`,\n 'toMatchObject',\n expected,\n actual\n );\n }\n },\n\n // Instance of\n toBeInstanceOf(expected) {\n const pass = actual instanceof expected;\n if (isNot ? pass : !pass) {\n throw AssertionError(\n isNot\n ? \\`Expected \\${format(actual)} not to be instance of \\${expected?.name || expected}\\`\n : \\`Expected \\${format(actual)} to be instance of \\${expected?.name || expected}\\`,\n 'toBeInstanceOf',\n expected?.name || expected,\n actual?.constructor?.name || actual\n );\n }\n },\n };\n\n // Add .not modifier\n if (!isNot) {\n matchers.not = createMatchers(actual, true);\n }\n\n return matchers;\n }\n\n // Create promise matchers\n function createPromiseMatchers(promise, isRejects) {\n return {\n async toBe(expected) {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBe', 'rejection', result);\n }\n expect(result).toBe(expected);\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBe(expected);\n }\n },\n async toEqual(expected) {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toEqual', 'rejection', result);\n }\n expect(result).toEqual(expected);\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toEqual(expected);\n }\n },\n async toBeTruthy() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeTruthy', 'rejection', result);\n }\n expect(result).toBeTruthy();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeTruthy();\n }\n },\n async toBeFalsy() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeFalsy', 'rejection', result);\n }\n expect(result).toBeFalsy();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeFalsy();\n }\n },\n async toBeNull() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeNull', 'rejection', result);\n }\n expect(result).toBeNull();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeNull();\n }\n },\n async toBeUndefined() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeUndefined', 'rejection', result);\n }\n expect(result).toBeUndefined();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeUndefined();\n }\n },\n async toBeDefined() {\n try {\n const result = await promise;\n if (isRejects) {\n throw AssertionError('Expected promise to reject', 'rejects.toBeDefined', 'rejection', result);\n }\n expect(result).toBeDefined();\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (!isRejects) throw error;\n expect(error).toBeDefined();\n }\n },\n async toThrow(expected) {\n if (!isRejects) {\n throw AssertionError('toThrow can only be used with rejects', 'toThrow', 'rejects', 'resolves');\n }\n try {\n await promise;\n throw AssertionError('Expected promise to reject', 'rejects.toThrow', 'rejection', 'resolved');\n } catch (error) {\n if (error.name === 'AssertionError') throw error;\n if (expected !== undefined) {\n if (typeof expected === 'string' && !error?.message?.includes(expected)) {\n throw AssertionError(\n \\`Expected error message to include \"\\${expected}\", got \"\\${error?.message}\"\\`,\n 'rejects.toThrow',\n expected,\n error?.message\n );\n }\n if (expected instanceof RegExp && !expected.test(error?.message)) {\n throw AssertionError(\n \\`Expected error message to match \\${expected}, got \"\\${error?.message}\"\\`,\n 'rejects.toThrow',\n expected,\n error?.message\n );\n }\n }\n }\n }\n };\n }\n\n // Main expect function\n function expect(actual) {\n const matchers = createMatchers(actual, false);\n\n // Add .resolves modifier\n matchers.resolves = createPromiseMatchers(Promise.resolve(actual), false);\n\n // Add .rejects modifier\n matchers.rejects = createPromiseMatchers(actual, true);\n\n return matchers;\n }\n\n // Expose globally\n globalThis.expect = expect;\n})();\n`;\n\n const result = context.evalCode(expectCode);\n if (result.error) {\n const errorDump = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to setup expect: ${errorDump}`);\n }\n result.value.dispose();\n\n return handles;\n}\n"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/globals/hooks.ts"],
3
+ "sources": ["../../../src/globals/hooks.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { TestState, RegisteredSuite } from \"../types.mjs\";\nimport { createSuite } from \"./describe.mjs\";\n\n/**\n * Helper to ensure we have a current suite for hook registration.\n * Creates an implicit root suite if called at top level.\n */\nfunction ensureSuite(state: TestState): RegisteredSuite {\n if (!state.currentSuite) {\n // Create implicit root suite for top-level hooks\n const rootSuite = createSuite(\"(root)\", null);\n state.suites.push(rootSuite);\n state.currentSuite = rootSuite;\n }\n return state.currentSuite;\n}\n\nexport function setupHooks(\n context: QuickJSContext,\n state: TestState\n): QuickJSHandle[] {\n const handles: QuickJSHandle[] = [];\n\n // beforeAll - runs once before all tests in the suite\n const beforeAllFn = context.newFunction(\"beforeAll\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.beforeAll.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"beforeAll\", beforeAllFn);\n handles.push(beforeAllFn);\n\n // afterAll - runs once after all tests in the suite\n const afterAllFn = context.newFunction(\"afterAll\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.afterAll.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"afterAll\", afterAllFn);\n handles.push(afterAllFn);\n\n // beforeEach - runs before each test in the suite and nested suites\n const beforeEachFn = context.newFunction(\"beforeEach\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.beforeEach.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"beforeEach\", beforeEachFn);\n handles.push(beforeEachFn);\n\n // afterEach - runs after each test in the suite and nested suites\n const afterEachFn = context.newFunction(\"afterEach\", (fnHandle) => {\n const suite = ensureSuite(state);\n const fnDup = fnHandle.dup();\n suite.afterEach.push(fnDup);\n return context.undefined;\n });\n context.setProp(context.global, \"afterEach\", afterEachFn);\n handles.push(afterEachFn);\n\n return handles;\n}\n"
6
6
  ],
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-test-environment",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-test-environment",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Test environment for QuickJS with Bun/Jest/Vitest-compatible primitives",
5
5
  "main": "./dist/cjs/index.cjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "quickjs-emscripten": ">=0.31.0"
20
20
  },
21
21
  "dependencies": {
22
- "@ricsam/quickjs-core": "^0.2.2"
22
+ "@ricsam/quickjs-core": "^0.2.3"
23
23
  },
24
24
  "author": "Richard Samuelsson",
25
25
  "license": "MIT",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes