@ricsam/isolate-test-environment 0.1.1 → 0.1.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,25 +1,6 @@
1
- import type ivm from "isolated-vm";
2
-
3
- export interface TestEnvironmentHandle {
4
- dispose(): void;
5
- }
6
-
7
- export interface TestResults {
8
- passed: number;
9
- failed: number;
10
- total: number;
11
- results: TestResult[];
12
- }
13
-
14
- export interface TestResult {
15
- name: string;
16
- passed: boolean;
17
- error?: string;
18
- duration: number;
19
- skipped?: boolean;
20
- }
21
-
22
- const testEnvironmentCode = `
1
+ // @bun
2
+ // packages/test-environment/src/index.ts
3
+ var testEnvironmentCode = `
23
4
  (function() {
24
5
  // ============================================================
25
6
  // Internal State
@@ -609,47 +590,23 @@ const testEnvironmentCode = `
609
590
  globalThis.__resetTestEnvironment = __resetTestEnvironment;
610
591
  })();
611
592
  `;
612
-
613
- /**
614
- * Setup test environment primitives in an isolated-vm context
615
- *
616
- * Provides Jest/Vitest-compatible test primitives:
617
- * - describe, test, it
618
- * - beforeEach, afterEach, beforeAll, afterAll
619
- * - expect matchers
620
- *
621
- * @example
622
- * const handle = await setupTestEnvironment(context);
623
- *
624
- * await context.eval(`
625
- * describe("my tests", () => {
626
- * test("example", () => {
627
- * expect(1 + 1).toBe(2);
628
- * });
629
- * });
630
- * `);
631
- */
632
- export async function setupTestEnvironment(
633
- context: ivm.Context
634
- ): Promise<TestEnvironmentHandle> {
593
+ async function setupTestEnvironment(context) {
635
594
  context.evalSync(testEnvironmentCode);
636
-
637
595
  return {
638
596
  dispose() {
639
- // Reset the test environment state
640
597
  try {
641
598
  context.evalSync("__resetTestEnvironment()");
642
- } catch {
643
- // Context may already be released
644
- }
645
- },
599
+ } catch {}
600
+ }
646
601
  };
647
602
  }
648
-
649
- /**
650
- * Run tests in the context and return results
651
- */
652
- export async function runTests(context: ivm.Context): Promise<TestResults> {
603
+ async function runTests(context) {
653
604
  const resultJson = await context.eval("__runAllTests()", { promise: true });
654
- return JSON.parse(resultJson as string);
605
+ return JSON.parse(resultJson);
655
606
  }
607
+ export {
608
+ setupTestEnvironment,
609
+ runTests
610
+ };
611
+
612
+ //# debugId=5EADDD25575186D264756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": [
5
+ "import type ivm from \"isolated-vm\";\n\nexport interface TestEnvironmentHandle {\n dispose(): void;\n}\n\nexport interface TestResults {\n passed: number;\n failed: number;\n total: number;\n results: TestResult[];\n}\n\nexport interface TestResult {\n name: string;\n passed: boolean;\n error?: string;\n duration: number;\n skipped?: boolean;\n}\n\nconst testEnvironmentCode = `\n(function() {\n // ============================================================\n // Internal State\n // ============================================================\n\n function createSuite(name, skip = false, only = false) {\n return {\n name,\n tests: [],\n children: [],\n beforeAll: [],\n afterAll: [],\n beforeEach: [],\n afterEach: [],\n skip,\n only,\n };\n }\n\n const rootSuite = createSuite('root');\n let currentSuite = rootSuite;\n const suiteStack = [rootSuite];\n\n // ============================================================\n // Deep Equality Helper\n // ============================================================\n\n function deepEqual(a, b) {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object' || a === null || b === null) return false;\n\n if (Array.isArray(a) !== Array.isArray(b)) return false;\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 (!keysB.includes(key)) return false;\n if (!deepEqual(a[key], b[key])) return false;\n }\n return true;\n }\n\n function strictDeepEqual(a, b) {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object' || a === null || b === null) return false;\n\n // Check prototypes\n if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;\n\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n\n // For arrays, check sparse arrays (holes)\n if (Array.isArray(a)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n const aHasIndex = i in a;\n const bHasIndex = i in b;\n if (aHasIndex !== bHasIndex) return false;\n if (aHasIndex && !strictDeepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n // Check for undefined properties vs missing properties\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 (!keysB.includes(key)) return false;\n if (!strictDeepEqual(a[key], b[key])) return false;\n }\n\n // Check for symbol properties\n const symbolsA = Object.getOwnPropertySymbols(a);\n const symbolsB = Object.getOwnPropertySymbols(b);\n if (symbolsA.length !== symbolsB.length) return false;\n\n for (const sym of symbolsA) {\n if (!symbolsB.includes(sym)) return false;\n if (!strictDeepEqual(a[sym], b[sym])) return false;\n }\n\n return true;\n }\n\n function getNestedProperty(obj, path) {\n const parts = path.split('.');\n let current = obj;\n for (const part of parts) {\n if (current == null || !(part in current)) {\n return { exists: false };\n }\n current = current[part];\n }\n return { exists: true, value: current };\n }\n\n function formatValue(val) {\n if (val === null) return 'null';\n if (val === undefined) return 'undefined';\n if (typeof val === 'string') return JSON.stringify(val);\n if (typeof val === 'object') {\n try {\n return JSON.stringify(val);\n } catch {\n return String(val);\n }\n }\n return String(val);\n }\n\n // ============================================================\n // expect() Implementation\n // ============================================================\n\n function expect(actual) {\n function createMatchers(negated = false) {\n const assert = (condition, message) => {\n const pass = negated ? !condition : condition;\n if (!pass) {\n throw new Error(message);\n }\n };\n\n const matchers = {\n toBe(expected) {\n assert(\n actual === expected,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be \\${formatValue(expected)}\\`\n : \\`Expected \\${formatValue(actual)} to be \\${formatValue(expected)}\\`\n );\n },\n\n toEqual(expected) {\n assert(\n deepEqual(actual, expected),\n negated\n ? \\`Expected \\${formatValue(actual)} not to equal \\${formatValue(expected)}\\`\n : \\`Expected \\${formatValue(actual)} to equal \\${formatValue(expected)}\\`\n );\n },\n\n toStrictEqual(expected) {\n assert(\n strictDeepEqual(actual, expected),\n negated\n ? \\`Expected \\${formatValue(actual)} not to strictly equal \\${formatValue(expected)}\\`\n : \\`Expected \\${formatValue(actual)} to strictly equal \\${formatValue(expected)}\\`\n );\n },\n\n toBeTruthy() {\n assert(\n !!actual,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be truthy\\`\n : \\`Expected \\${formatValue(actual)} to be truthy\\`\n );\n },\n\n toBeFalsy() {\n assert(\n !actual,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be falsy\\`\n : \\`Expected \\${formatValue(actual)} to be falsy\\`\n );\n },\n\n toBeNull() {\n assert(\n actual === null,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be null\\`\n : \\`Expected \\${formatValue(actual)} to be null\\`\n );\n },\n\n toBeUndefined() {\n assert(\n actual === undefined,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be undefined\\`\n : \\`Expected \\${formatValue(actual)} to be undefined\\`\n );\n },\n\n toBeDefined() {\n assert(\n actual !== undefined,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be defined\\`\n : \\`Expected \\${formatValue(actual)} to be defined\\`\n );\n },\n\n toContain(item) {\n let contains = false;\n if (Array.isArray(actual)) {\n contains = actual.includes(item);\n } else if (typeof actual === 'string') {\n contains = actual.includes(item);\n }\n assert(\n contains,\n negated\n ? \\`Expected \\${formatValue(actual)} not to contain \\${formatValue(item)}\\`\n : \\`Expected \\${formatValue(actual)} to contain \\${formatValue(item)}\\`\n );\n },\n\n toThrow(expected) {\n if (typeof actual !== 'function') {\n throw new Error('toThrow requires a function');\n }\n\n let threw = false;\n let error = null;\n try {\n actual();\n } catch (e) {\n threw = true;\n error = e;\n }\n\n if (expected !== undefined) {\n const matches = threw && (\n (typeof expected === 'string' && error.message.includes(expected)) ||\n (expected instanceof RegExp && expected.test(error.message)) ||\n (typeof expected === 'function' && error instanceof expected)\n );\n assert(\n matches,\n negated\n ? \\`Expected function not to throw \\${formatValue(expected)}\\`\n : \\`Expected function to throw \\${formatValue(expected)}, but \\${threw ? \\`threw: \\${error.message}\\` : 'did not throw'}\\`\n );\n } else {\n assert(\n threw,\n negated\n ? \\`Expected function not to throw\\`\n : \\`Expected function to throw\\`\n );\n }\n },\n\n toBeInstanceOf(cls) {\n assert(\n actual instanceof cls,\n negated\n ? \\`Expected \\${formatValue(actual)} not to be instance of \\${cls.name || cls}\\`\n : \\`Expected \\${formatValue(actual)} to be instance of \\${cls.name || cls}\\`\n );\n },\n\n toHaveLength(length) {\n const actualLength = actual?.length;\n assert(\n actualLength === length,\n negated\n ? \\`Expected length not to be \\${length}, but got \\${actualLength}\\`\n : \\`Expected length to be \\${length}, but got \\${actualLength}\\`\n );\n },\n\n toMatch(pattern) {\n let matches = false;\n if (typeof pattern === 'string') {\n matches = actual.includes(pattern);\n } else if (pattern instanceof RegExp) {\n matches = pattern.test(actual);\n }\n assert(\n matches,\n negated\n ? \\`Expected \\${formatValue(actual)} not to match \\${pattern}\\`\n : \\`Expected \\${formatValue(actual)} to match \\${pattern}\\`\n );\n },\n\n toHaveProperty(path, value) {\n const prop = getNestedProperty(actual, path);\n const hasProperty = prop.exists;\n const valueMatches = arguments.length < 2 || deepEqual(prop.value, value);\n\n assert(\n hasProperty && valueMatches,\n negated\n ? \\`Expected \\${formatValue(actual)} not to have property \\${path}\\${arguments.length >= 2 ? \\` with value \\${formatValue(value)}\\` : ''}\\`\n : \\`Expected \\${formatValue(actual)} to have property \\${path}\\${arguments.length >= 2 ? \\` with value \\${formatValue(value)}\\` : ''}\\`\n );\n },\n };\n\n return matchers;\n }\n\n const matchers = createMatchers(false);\n matchers.not = createMatchers(true);\n\n return matchers;\n }\n\n // ============================================================\n // Test Registration Functions\n // ============================================================\n\n function describe(name, fn) {\n const suite = createSuite(name);\n currentSuite.children.push(suite);\n\n const parentSuite = currentSuite;\n currentSuite = suite;\n suiteStack.push(suite);\n\n fn();\n\n suiteStack.pop();\n currentSuite = parentSuite;\n }\n\n describe.skip = function(name, fn) {\n const suite = createSuite(name, true, false);\n currentSuite.children.push(suite);\n\n const parentSuite = currentSuite;\n currentSuite = suite;\n suiteStack.push(suite);\n\n fn();\n\n suiteStack.pop();\n currentSuite = parentSuite;\n };\n\n describe.only = function(name, fn) {\n const suite = createSuite(name, false, true);\n currentSuite.children.push(suite);\n\n const parentSuite = currentSuite;\n currentSuite = suite;\n suiteStack.push(suite);\n\n fn();\n\n suiteStack.pop();\n currentSuite = parentSuite;\n };\n\n function test(name, fn) {\n currentSuite.tests.push({\n name,\n fn,\n skip: false,\n only: false,\n });\n }\n\n test.skip = function(name, fn) {\n currentSuite.tests.push({\n name,\n fn,\n skip: true,\n only: false,\n });\n };\n\n test.only = function(name, fn) {\n currentSuite.tests.push({\n name,\n fn,\n skip: false,\n only: true,\n });\n };\n\n test.todo = function(name) {\n currentSuite.tests.push({\n name,\n fn: null,\n skip: false,\n only: false,\n todo: true,\n });\n };\n\n const it = test;\n it.skip = test.skip;\n it.only = test.only;\n it.todo = test.todo;\n\n // ============================================================\n // Lifecycle Hooks\n // ============================================================\n\n function beforeEach(fn) {\n currentSuite.beforeEach.push(fn);\n }\n\n function afterEach(fn) {\n currentSuite.afterEach.push(fn);\n }\n\n function beforeAll(fn) {\n currentSuite.beforeAll.push(fn);\n }\n\n function afterAll(fn) {\n currentSuite.afterAll.push(fn);\n }\n\n // ============================================================\n // Test Runner\n // ============================================================\n\n function checkForOnly(suite) {\n if (suite.only) return true;\n for (const t of suite.tests) {\n if (t.only) return true;\n }\n for (const child of suite.children) {\n if (checkForOnly(child)) return true;\n }\n return false;\n }\n\n function suiteHasOnly(suite) {\n if (suite.only) return true;\n for (const t of suite.tests) {\n if (t.only) return true;\n }\n for (const child of suite.children) {\n if (suiteHasOnly(child)) return true;\n }\n return false;\n }\n\n async function __runAllTests() {\n const results = [];\n const hasOnly = checkForOnly(rootSuite);\n\n async function runSuite(suite, parentHooks, namePath) {\n // Skip if this suite doesn't have any .only when .only exists elsewhere\n if (hasOnly && !suiteHasOnly(suite)) return;\n\n // Skip if suite is marked as skip\n if (suite.skip) {\n // Mark all tests in this suite as skipped\n for (const t of suite.tests) {\n results.push({\n name: namePath ? namePath + ' > ' + t.name : t.name,\n passed: true,\n skipped: true,\n duration: 0,\n });\n }\n return;\n }\n\n // Run beforeAll hooks\n for (const hook of suite.beforeAll) {\n await hook();\n }\n\n // Run tests\n for (const t of suite.tests) {\n const testName = namePath ? namePath + ' > ' + t.name : t.name;\n\n // Skip if .only is used and this test isn't .only AND the suite doesn't have .only\n if (hasOnly && !t.only && !suite.only) continue;\n\n // Skip if test is marked as skip\n if (t.skip) {\n results.push({\n name: testName,\n passed: true,\n skipped: true,\n duration: 0,\n });\n continue;\n }\n\n // Handle todo tests (no function provided)\n if (t.todo) {\n results.push({\n name: testName,\n passed: true,\n skipped: true,\n duration: 0,\n });\n continue;\n }\n\n const start = Date.now();\n try {\n // Run all beforeEach hooks (parent first, then current)\n for (const hook of [...parentHooks.beforeEach, ...suite.beforeEach]) {\n await hook();\n }\n\n // Run test\n await t.fn();\n\n // Run all afterEach hooks (current first, then parent)\n for (const hook of [...suite.afterEach, ...parentHooks.afterEach]) {\n await hook();\n }\n\n results.push({\n name: testName,\n passed: true,\n duration: Date.now() - start,\n });\n } catch (err) {\n results.push({\n name: testName,\n passed: false,\n error: err.message || String(err),\n duration: Date.now() - start,\n });\n }\n }\n\n // Run child suites\n for (const child of suite.children) {\n const childPath = namePath ? namePath + ' > ' + child.name : child.name;\n await runSuite(child, {\n beforeEach: [...parentHooks.beforeEach, ...suite.beforeEach],\n afterEach: [...suite.afterEach, ...parentHooks.afterEach],\n }, childPath);\n }\n\n // Run afterAll hooks\n for (const hook of suite.afterAll) {\n await hook();\n }\n }\n\n await runSuite(rootSuite, { beforeEach: [], afterEach: [] }, '');\n\n const passed = results.filter(r => r.passed && !r.skipped).length;\n const failed = results.filter(r => !r.passed).length;\n const skipped = results.filter(r => r.skipped).length;\n\n return JSON.stringify({\n passed,\n failed,\n skipped,\n total: results.length,\n results,\n });\n }\n\n // Reset function to clear state between runs\n function __resetTestEnvironment() {\n rootSuite.tests = [];\n rootSuite.children = [];\n rootSuite.beforeAll = [];\n rootSuite.afterAll = [];\n rootSuite.beforeEach = [];\n rootSuite.afterEach = [];\n currentSuite = rootSuite;\n suiteStack.length = 0;\n suiteStack.push(rootSuite);\n }\n\n // ============================================================\n // Expose Globals\n // ============================================================\n\n globalThis.describe = describe;\n globalThis.test = test;\n globalThis.it = it;\n globalThis.expect = expect;\n globalThis.beforeEach = beforeEach;\n globalThis.afterEach = afterEach;\n globalThis.beforeAll = beforeAll;\n globalThis.afterAll = afterAll;\n globalThis.__runAllTests = __runAllTests;\n globalThis.__resetTestEnvironment = __resetTestEnvironment;\n})();\n`;\n\n/**\n * Setup test environment primitives in an isolated-vm context\n *\n * Provides Jest/Vitest-compatible test primitives:\n * - describe, test, it\n * - beforeEach, afterEach, beforeAll, afterAll\n * - expect matchers\n *\n * @example\n * const handle = await setupTestEnvironment(context);\n *\n * await context.eval(`\n * describe(\"my tests\", () => {\n * test(\"example\", () => {\n * expect(1 + 1).toBe(2);\n * });\n * });\n * `);\n */\nexport async function setupTestEnvironment(\n context: ivm.Context\n): Promise<TestEnvironmentHandle> {\n context.evalSync(testEnvironmentCode);\n\n return {\n dispose() {\n // Reset the test environment state\n try {\n context.evalSync(\"__resetTestEnvironment()\");\n } catch {\n // Context may already be released\n }\n },\n };\n}\n\n/**\n * Run tests in the context and return results\n */\nexport async function runTests(context: ivm.Context): Promise<TestResults> {\n const resultJson = await context.eval(\"__runAllTests()\", { promise: true });\n return JSON.parse(resultJson as string);\n}\n"
6
+ ],
7
+ "mappings": ";;AAqBA,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkmB5B,eAAsB,oBAAoB,CACxC,SACgC;AAAA,EAChC,QAAQ,SAAS,mBAAmB;AAAA,EAEpC,OAAO;AAAA,IACL,OAAO,GAAG;AAAA,MAER,IAAI;AAAA,QACF,QAAQ,SAAS,0BAA0B;AAAA,QAC3C,MAAM;AAAA;AAAA,EAIZ;AAAA;AAMF,eAAsB,QAAQ,CAAC,SAA4C;AAAA,EACzE,MAAM,aAAa,MAAM,QAAQ,KAAK,mBAAmB,EAAE,SAAS,KAAK,CAAC;AAAA,EAC1E,OAAO,KAAK,MAAM,UAAoB;AAAA;",
8
+ "debugId": "5EADDD25575186D264756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@ricsam/isolate-test-environment",
3
+ "version": "0.1.3",
4
+ "type": "module"
5
+ }
@@ -0,0 +1,41 @@
1
+ import type ivm from "isolated-vm";
2
+ export interface TestEnvironmentHandle {
3
+ dispose(): void;
4
+ }
5
+ export interface TestResults {
6
+ passed: number;
7
+ failed: number;
8
+ total: number;
9
+ results: TestResult[];
10
+ }
11
+ export interface TestResult {
12
+ name: string;
13
+ passed: boolean;
14
+ error?: string;
15
+ duration: number;
16
+ skipped?: boolean;
17
+ }
18
+ /**
19
+ * Setup test environment primitives in an isolated-vm context
20
+ *
21
+ * Provides Jest/Vitest-compatible test primitives:
22
+ * - describe, test, it
23
+ * - beforeEach, afterEach, beforeAll, afterAll
24
+ * - expect matchers
25
+ *
26
+ * @example
27
+ * const handle = await setupTestEnvironment(context);
28
+ *
29
+ * await context.eval(`
30
+ * describe("my tests", () => {
31
+ * test("example", () => {
32
+ * expect(1 + 1).toBe(2);
33
+ * });
34
+ * });
35
+ * `);
36
+ */
37
+ export declare function setupTestEnvironment(context: ivm.Context): Promise<TestEnvironmentHandle>;
38
+ /**
39
+ * Run tests in the context and return results
40
+ */
41
+ export declare function runTests(context: ivm.Context): Promise<TestResults>;
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Global Type Definitions for @ricsam/isolate-test-environment
3
+ *
4
+ * These types define the globals injected by setupTestEnvironment() into an isolated-vm context.
5
+ * Use these types to typecheck user code that will run inside the V8 isolate.
6
+ *
7
+ * @example
8
+ * describe("Math operations", () => {
9
+ * it("should add numbers", () => {
10
+ * expect(1 + 1).toBe(2);
11
+ * });
12
+ * });
13
+ */
14
+
15
+ export {};
16
+
17
+ declare global {
18
+ // ============================================
19
+ // Test Structure
20
+ // ============================================
21
+
22
+ /**
23
+ * Define a test suite.
24
+ *
25
+ * @param name - The name of the test suite
26
+ * @param fn - Function containing tests and nested suites
27
+ *
28
+ * @example
29
+ * describe("Calculator", () => {
30
+ * it("adds numbers", () => {
31
+ * expect(1 + 1).toBe(2);
32
+ * });
33
+ * });
34
+ */
35
+ function describe(name: string, fn: () => void): void;
36
+
37
+ namespace describe {
38
+ /**
39
+ * Skip this suite and all its tests.
40
+ */
41
+ function skip(name: string, fn: () => void): void;
42
+
43
+ /**
44
+ * Only run this suite (and other .only suites).
45
+ */
46
+ function only(name: string, fn: () => void): void;
47
+
48
+ /**
49
+ * Mark suite as todo (skipped with different status).
50
+ */
51
+ function todo(name: string, fn?: () => void): void;
52
+ }
53
+
54
+ /**
55
+ * Define a test case.
56
+ *
57
+ * @param name - The name of the test
58
+ * @param fn - The test function (can be async)
59
+ *
60
+ * @example
61
+ * it("should work", () => {
62
+ * expect(true).toBe(true);
63
+ * });
64
+ *
65
+ * it("should work async", async () => {
66
+ * const result = await Promise.resolve(42);
67
+ * expect(result).toBe(42);
68
+ * });
69
+ */
70
+ function it(name: string, fn: () => void | Promise<void>): void;
71
+
72
+ namespace it {
73
+ /**
74
+ * Skip this test.
75
+ */
76
+ function skip(name: string, fn?: () => void | Promise<void>): void;
77
+
78
+ /**
79
+ * Only run this test (and other .only tests).
80
+ */
81
+ function only(name: string, fn: () => void | Promise<void>): void;
82
+
83
+ /**
84
+ * Mark test as todo.
85
+ */
86
+ function todo(name: string, fn?: () => void | Promise<void>): void;
87
+ }
88
+
89
+ /**
90
+ * Alias for it().
91
+ */
92
+ function test(name: string, fn: () => void | Promise<void>): void;
93
+
94
+ namespace test {
95
+ /**
96
+ * Skip this test.
97
+ */
98
+ function skip(name: string, fn?: () => void | Promise<void>): void;
99
+
100
+ /**
101
+ * Only run this test (and other .only tests).
102
+ */
103
+ function only(name: string, fn: () => void | Promise<void>): void;
104
+
105
+ /**
106
+ * Mark test as todo.
107
+ */
108
+ function todo(name: string, fn?: () => void | Promise<void>): void;
109
+ }
110
+
111
+ // ============================================
112
+ // Lifecycle Hooks
113
+ // ============================================
114
+
115
+ /**
116
+ * Run once before all tests in the current suite.
117
+ *
118
+ * @param fn - Setup function (can be async)
119
+ */
120
+ function beforeAll(fn: () => void | Promise<void>): void;
121
+
122
+ /**
123
+ * Run once after all tests in the current suite.
124
+ *
125
+ * @param fn - Teardown function (can be async)
126
+ */
127
+ function afterAll(fn: () => void | Promise<void>): void;
128
+
129
+ /**
130
+ * Run before each test in the current suite (and nested suites).
131
+ *
132
+ * @param fn - Setup function (can be async)
133
+ */
134
+ function beforeEach(fn: () => void | Promise<void>): void;
135
+
136
+ /**
137
+ * Run after each test in the current suite (and nested suites).
138
+ *
139
+ * @param fn - Teardown function (can be async)
140
+ */
141
+ function afterEach(fn: () => void | Promise<void>): void;
142
+
143
+ // ============================================
144
+ // Assertions
145
+ // ============================================
146
+
147
+ /**
148
+ * Matchers for assertions.
149
+ */
150
+ interface Matchers<T> {
151
+ /**
152
+ * Strict equality (===).
153
+ */
154
+ toBe(expected: T): void;
155
+
156
+ /**
157
+ * Deep equality.
158
+ */
159
+ toEqual(expected: unknown): void;
160
+
161
+ /**
162
+ * Deep equality with type checking.
163
+ */
164
+ toStrictEqual(expected: unknown): void;
165
+
166
+ /**
167
+ * Check if value is truthy.
168
+ */
169
+ toBeTruthy(): void;
170
+
171
+ /**
172
+ * Check if value is falsy.
173
+ */
174
+ toBeFalsy(): void;
175
+
176
+ /**
177
+ * Check if value is null.
178
+ */
179
+ toBeNull(): void;
180
+
181
+ /**
182
+ * Check if value is undefined.
183
+ */
184
+ toBeUndefined(): void;
185
+
186
+ /**
187
+ * Check if value is defined (not undefined).
188
+ */
189
+ toBeDefined(): void;
190
+
191
+ /**
192
+ * Check if value is NaN.
193
+ */
194
+ toBeNaN(): void;
195
+
196
+ /**
197
+ * Check if number is greater than expected.
198
+ */
199
+ toBeGreaterThan(n: number): void;
200
+
201
+ /**
202
+ * Check if number is greater than or equal to expected.
203
+ */
204
+ toBeGreaterThanOrEqual(n: number): void;
205
+
206
+ /**
207
+ * Check if number is less than expected.
208
+ */
209
+ toBeLessThan(n: number): void;
210
+
211
+ /**
212
+ * Check if number is less than or equal to expected.
213
+ */
214
+ toBeLessThanOrEqual(n: number): void;
215
+
216
+ /**
217
+ * Check if array/string contains item/substring.
218
+ */
219
+ toContain(item: unknown): void;
220
+
221
+ /**
222
+ * Check length of array/string.
223
+ */
224
+ toHaveLength(length: number): void;
225
+
226
+ /**
227
+ * Check if object has property (optionally with value).
228
+ */
229
+ toHaveProperty(key: string, value?: unknown): void;
230
+
231
+ /**
232
+ * Check if function throws.
233
+ */
234
+ toThrow(expected?: string | RegExp | Error): void;
235
+
236
+ /**
237
+ * Check if string matches pattern.
238
+ */
239
+ toMatch(pattern: string | RegExp): void;
240
+
241
+ /**
242
+ * Check if object matches subset of properties.
243
+ */
244
+ toMatchObject(object: object): void;
245
+
246
+ /**
247
+ * Check if value is instance of class.
248
+ */
249
+ toBeInstanceOf(constructor: Function): void;
250
+
251
+ /**
252
+ * Negate the matcher.
253
+ */
254
+ not: Matchers<T>;
255
+
256
+ /**
257
+ * Await promise and check resolved value.
258
+ */
259
+ resolves: Matchers<Awaited<T>>;
260
+
261
+ /**
262
+ * Await promise and check rejection.
263
+ */
264
+ rejects: Matchers<unknown>;
265
+ }
266
+
267
+ /**
268
+ * Create an expectation for a value.
269
+ *
270
+ * @param actual - The value to test
271
+ * @returns Matchers for the value
272
+ *
273
+ * @example
274
+ * expect(1 + 1).toBe(2);
275
+ * expect({ a: 1 }).toEqual({ a: 1 });
276
+ * expect(() => { throw new Error(); }).toThrow();
277
+ * expect(promise).resolves.toBe(42);
278
+ */
279
+ function expect<T>(actual: T): Matchers<T>;
280
+ }
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@ricsam/isolate-test-environment",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Test environment for isolated-vm with Jest/Vitest-compatible primitives",
5
- "type": "module",
6
- "main": "./src/index.ts",
7
- "types": "./src/index.ts",
5
+ "main": "./dist/cjs/index.cjs",
6
+ "types": "./dist/types/index.d.ts",
8
7
  "exports": {
9
8
  ".": {
10
- "import": "./src/index.ts",
11
- "types": "./src/index.ts"
9
+ "types": "./dist/types/index.d.ts",
10
+ "require": "./dist/cjs/index.cjs",
11
+ "import": "./dist/mjs/index.mjs"
12
+ },
13
+ "./isolate": {
14
+ "types": "./dist/types/isolate.d.ts"
12
15
  }
13
16
  },
14
17
  "scripts": {
@@ -20,12 +23,36 @@
20
23
  "@ricsam/isolate-core": "*",
21
24
  "isolated-vm": "^6"
22
25
  },
23
- "devDependencies": {
24
- "@ricsam/isolate-test-utils": "*",
25
- "@types/node": "^24",
26
- "typescript": "^5"
27
- },
28
26
  "peerDependencies": {
29
27
  "isolated-vm": "^6"
30
- }
31
- }
28
+ },
29
+ "author": "Richard Samuelsson",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/ricsam/isolate.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/ricsam/isolate/issues"
37
+ },
38
+ "homepage": "https://github.com/ricsam/isolate#readme",
39
+ "keywords": [
40
+ "isolated-vm",
41
+ "sandbox",
42
+ "javascript",
43
+ "runtime",
44
+ "fetch",
45
+ "filesystem",
46
+ "streams",
47
+ "v8",
48
+ "isolate"
49
+ ],
50
+ "module": "./dist/mjs/index.mjs",
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "files": [
55
+ "dist",
56
+ "README.md"
57
+ ]
58
+ }
package/CHANGELOG.md DELETED
@@ -1,9 +0,0 @@
1
- # @ricsam/isolate-test-environment
2
-
3
- ## 0.1.1
4
-
5
- ### Patch Changes
6
-
7
- - initial release
8
- - Updated dependencies
9
- - @ricsam/isolate-core@0.1.1