@noego/wood 0.5.0 → 0.6.0

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.
Files changed (142) hide show
  1. package/README.md +31 -0
  2. package/dist/client/index.cjs +18 -11
  3. package/dist/client/index.cjs.map +1 -1
  4. package/dist/client/index.d.cts +21 -1
  5. package/dist/client/index.d.ts +21 -1
  6. package/dist/client/index.js +17 -11
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/codegen/renderer_app_generator.cjs +2 -9
  9. package/dist/codegen/renderer_app_generator.cjs.map +1 -1
  10. package/dist/codegen/renderer_app_generator.js +2 -9
  11. package/dist/codegen/renderer_app_generator.js.map +1 -1
  12. package/dist/controller/cleanup.cjs +39 -0
  13. package/dist/controller/cleanup.cjs.map +1 -0
  14. package/dist/controller/cleanup.d.cts +4 -0
  15. package/dist/controller/cleanup.d.ts +4 -0
  16. package/dist/controller/cleanup.js +15 -0
  17. package/dist/controller/cleanup.js.map +1 -0
  18. package/dist/controller/controller_lifecycle.cjs +26 -2
  19. package/dist/controller/controller_lifecycle.cjs.map +1 -1
  20. package/dist/controller/controller_lifecycle.d.cts +3 -1
  21. package/dist/controller/controller_lifecycle.d.ts +3 -1
  22. package/dist/controller/controller_lifecycle.js +24 -1
  23. package/dist/controller/controller_lifecycle.js.map +1 -1
  24. package/dist/frontend/frontend_lifecycle.cjs +5 -7
  25. package/dist/frontend/frontend_lifecycle.cjs.map +1 -1
  26. package/dist/frontend/frontend_lifecycle.d.cts +1 -14
  27. package/dist/frontend/frontend_lifecycle.d.ts +1 -14
  28. package/dist/frontend/frontend_lifecycle.js +5 -7
  29. package/dist/frontend/frontend_lifecycle.js.map +1 -1
  30. package/dist/frontend/frontend_surface.cjs +130 -104
  31. package/dist/frontend/frontend_surface.cjs.map +1 -1
  32. package/dist/frontend/frontend_surface.js +131 -105
  33. package/dist/frontend/frontend_surface.js.map +1 -1
  34. package/dist/frontend/work_registry.cjs +29 -4
  35. package/dist/frontend/work_registry.cjs.map +1 -1
  36. package/dist/frontend/work_registry.d.cts +2 -0
  37. package/dist/frontend/work_registry.d.ts +2 -0
  38. package/dist/frontend/work_registry.js +29 -4
  39. package/dist/frontend/work_registry.js.map +1 -1
  40. package/dist/index.d.cts +2 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/ipc/index.cjs +6 -2
  43. package/dist/ipc/index.cjs.map +1 -1
  44. package/dist/ipc/index.d.cts +9 -0
  45. package/dist/ipc/index.d.ts +9 -0
  46. package/dist/ipc/index.js +6 -2
  47. package/dist/ipc/index.js.map +1 -1
  48. package/dist/main/index.cjs +53 -35
  49. package/dist/main/index.cjs.map +1 -1
  50. package/dist/main/index.d.cts +11 -2
  51. package/dist/main/index.d.ts +11 -2
  52. package/dist/main/index.js +56 -40
  53. package/dist/main/index.js.map +1 -1
  54. package/dist/main/startup_failure_handler.cjs +67 -0
  55. package/dist/main/startup_failure_handler.cjs.map +1 -0
  56. package/dist/main/startup_failure_handler.d.cts +16 -0
  57. package/dist/main/startup_failure_handler.d.ts +16 -0
  58. package/dist/main/startup_failure_handler.js +43 -0
  59. package/dist/main/startup_failure_handler.js.map +1 -0
  60. package/dist/router/index.d.cts +3 -0
  61. package/dist/router/index.d.ts +3 -0
  62. package/dist/router/wood_router.cjs +204 -123
  63. package/dist/router/wood_router.cjs.map +1 -1
  64. package/dist/router/wood_router.d.cts +20 -0
  65. package/dist/router/wood_router.d.ts +20 -0
  66. package/dist/router/wood_router.js +206 -125
  67. package/dist/router/wood_router.js.map +1 -1
  68. package/dist/runtime/index.cjs +13 -0
  69. package/dist/runtime/index.cjs.map +1 -1
  70. package/dist/runtime/index.d.cts +8 -4
  71. package/dist/runtime/index.d.ts +8 -4
  72. package/dist/runtime/index.js +8 -0
  73. package/dist/runtime/index.js.map +1 -1
  74. package/dist/runtime/renderer_config_extension.cjs +1 -2
  75. package/dist/runtime/renderer_config_extension.cjs.map +1 -1
  76. package/dist/runtime/renderer_config_extension.d.cts +1 -1
  77. package/dist/runtime/renderer_config_extension.d.ts +1 -1
  78. package/dist/runtime/renderer_config_extension.js +1 -2
  79. package/dist/runtime/renderer_config_extension.js.map +1 -1
  80. package/dist/runtime/renderer_log_dispatcher.cjs +2 -0
  81. package/dist/runtime/renderer_log_dispatcher.cjs.map +1 -1
  82. package/dist/runtime/renderer_log_dispatcher.d.cts +1 -1
  83. package/dist/runtime/renderer_log_dispatcher.d.ts +1 -1
  84. package/dist/runtime/renderer_log_dispatcher.js +1 -0
  85. package/dist/runtime/renderer_log_dispatcher.js.map +1 -1
  86. package/dist/runtime/runtime.cjs +107 -57
  87. package/dist/runtime/runtime.cjs.map +1 -1
  88. package/dist/runtime/runtime.d.cts +12 -0
  89. package/dist/runtime/runtime.d.ts +12 -0
  90. package/dist/runtime/runtime.js +110 -59
  91. package/dist/runtime/runtime.js.map +1 -1
  92. package/dist/runtime/runtime_module.cjs +58 -0
  93. package/dist/runtime/runtime_module.cjs.map +1 -0
  94. package/dist/runtime/runtime_module.d.cts +22 -0
  95. package/dist/runtime/runtime_module.d.ts +22 -0
  96. package/dist/runtime/runtime_module.js +30 -0
  97. package/dist/runtime/runtime_module.js.map +1 -0
  98. package/dist/testing/create_controller_harness.d.cts +3 -0
  99. package/dist/testing/create_controller_harness.d.ts +3 -0
  100. package/dist/testing/create_router_bridge.d.cts +3 -0
  101. package/dist/testing/create_router_bridge.d.ts +3 -0
  102. package/dist/testing/create_test_router.d.cts +3 -0
  103. package/dist/testing/create_test_router.d.ts +3 -0
  104. package/dist/testing/index.cjs +3 -0
  105. package/dist/testing/index.cjs.map +1 -1
  106. package/dist/testing/index.d.cts +6 -1
  107. package/dist/testing/index.d.ts +6 -1
  108. package/dist/testing/index.js +2 -0
  109. package/dist/testing/index.js.map +1 -1
  110. package/dist/testing/registration_only_root.cjs +168 -0
  111. package/dist/testing/registration_only_root.cjs.map +1 -0
  112. package/dist/testing/registration_only_root.d.cts +46 -0
  113. package/dist/testing/registration_only_root.d.ts +46 -0
  114. package/dist/testing/registration_only_root.js +141 -0
  115. package/dist/testing/registration_only_root.js.map +1 -0
  116. package/dist/testing/test_wood_config.cjs +382 -70
  117. package/dist/testing/test_wood_config.cjs.map +1 -1
  118. package/dist/testing/test_wood_config.d.cts +25 -1
  119. package/dist/testing/test_wood_config.d.ts +25 -1
  120. package/dist/testing/test_wood_config.js +388 -71
  121. package/dist/testing/test_wood_config.js.map +1 -1
  122. package/dist/testing/wood_dom_animation.cjs +213 -0
  123. package/dist/testing/wood_dom_animation.cjs.map +1 -0
  124. package/dist/testing/wood_dom_animation.d.cts +89 -0
  125. package/dist/testing/wood_dom_animation.d.ts +89 -0
  126. package/dist/testing/wood_dom_animation.js +185 -0
  127. package/dist/testing/wood_dom_animation.js.map +1 -0
  128. package/dist/testing/wood_env_renderer.cjs +81 -32
  129. package/dist/testing/wood_env_renderer.cjs.map +1 -1
  130. package/dist/testing/wood_env_renderer.d.cts +32 -1
  131. package/dist/testing/wood_env_renderer.d.ts +32 -1
  132. package/dist/testing/wood_env_renderer.js +84 -32
  133. package/dist/testing/wood_env_renderer.js.map +1 -1
  134. package/dist/transport/in_process_router_transport.d.cts +3 -0
  135. package/dist/transport/in_process_router_transport.d.ts +3 -0
  136. package/dist/transport/index.d.cts +3 -0
  137. package/dist/transport/index.d.ts +3 -0
  138. package/package.json +8 -6
  139. package/src/components/NavigationShell.svelte +12 -7
  140. package/src/controller/cleanup.ts +8 -0
  141. package/src/controller/controller_lifecycle.ts +24 -0
  142. package/src/navigation/layout_preservation.ts +45 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/test_wood_config.ts"],"sourcesContent":["/**\n * `testWood(configPath)` — the canonical Wood testing entry point.\n *\n * One persistent immutable builder over the production `wood.config.yml`:\n *\n * const app = await testWood('wood.config.yml')\n * .select({ main: { module: 'backend_service' }, pages: ['settings.backend-service'] })\n * .classes([[BackendServiceLifecycleCoordinator, LifecycleStub]])\n * .methods([[LifecycleStub, { getStatus: test.once(test.returns(status)) }]])\n * .build();\n *\n * `select` decides what exists (main router, renderer pages, DOM); the\n * `@noego/testing` vocabulary (`use/classes/functions/values/methods`, exact\n * tokens) decides what is replaced — controllers included, since the router\n * resolves them through the same root. Controllers, middleware and views are\n * auto-bound from the config; the app composes its backend through the\n * optional `main.testing.compose` hook and mounts pages through\n * `renderer.testing.host`.\n *\n * Every build creates fresh `@noego/ioc` roots (main, and renderer when\n * pages are selected) — nothing global is touched, so environments coexist\n * in one process.\n */\nimport path from 'node:path';\nimport type { IContainer } from '@noego/ioc';\nimport { ExecutionContext } from '@noego/ioc';\nimport { testIoc, TestIocBuilder, ENV_REGISTRY } from '@noego/testing';\nimport type { ConfigMap, MethodsConfig, TestEnvironment, UseInput } from '@noego/testing';\nimport { WoodRouter } from '../router/wood_router.js';\nimport type { OperationDef } from '../types/index.js';\nimport type { WoodConfig } from '../types/config.js';\nimport type { FrontendApplicationSurface } from '../frontend/frontend_surface.js';\nimport type { FrontendPage } from '../frontend/frontend_page.js';\nimport type { RouterBridgeEvents } from './create_router_bridge.js';\nimport { TestWoodError } from './test_wood.js';\nimport {\n defaultImporter,\n importDefaultExport,\n loadWoodConfigSource,\n type ModuleImporter,\n type WoodConfigSource,\n} from './wood_config_source.js';\nimport {\n resolveSelection,\n selectCatalog,\n selectOperations,\n type ResolvedWoodSelection,\n type WoodTestSelection,\n} from './wood_test_selection.js';\nimport {\n buildRendererTier,\n type RendererTier,\n type WoodDomDriver,\n type WoodRendererHooks,\n} from './wood_env_renderer.js';\n\n/** What the app's `main.testing.compose` default export receives. */\nexport interface WoodComposeContext {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n readonly selection: ResolvedWoodSelection;\n /** The selected operations (the router will serve exactly these). */\n readonly operations: ReadonlyMap<string, OperationDef>;\n readonly importer: ModuleImporter;\n /**\n * Dispatch through the environment's router. Available from `start()`\n * onward (the router is built after compose returns); calling it during\n * compose itself fails.\n */\n call<T = unknown>(operation: string, input?: unknown): Promise<T>;\n}\n\n/** What the compose hook may hand back; every member is optional. */\nexport interface WoodComposeResult extends WoodRendererHooks {\n /**\n * Runs AFTER the test's substitutions are installed on the root (compose\n * itself runs before them, so production registrations never shadow a\n * seam). Seed world state and warm caches here — through the seamed graph,\n * exactly as the test will observe it.\n */\n start?(): void | Promise<void>;\n /**\n * Quiesce BEFORE the main root disposes its instances: silence push\n * delivery, drain detached work — everything that still needs the live\n * graph (database, tracer) the services' own dispose hooks run against.\n */\n drain?(): void | Promise<void>;\n /** Release resources AFTER the roots are disposed (close files, remove dirs). */\n dispose?(): void | Promise<void>;\n}\n\n/**\n * The app's backend composition on the root the builder hands it. Register\n * lazily: nothing expensive should run until a test resolves it.\n */\nexport type WoodComposeMain = (\n root: IContainer,\n context: WoodComposeContext,\n) => WoodComposeResult | void | Promise<WoodComposeResult | void>;\n\ntype Params = Record<string, string>;\n\nexport interface TestWoodEnvironment {\n /** The main-process IoC root of this environment. */\n readonly root: IContainer;\n readonly router: WoodRouter;\n /** The selected operations. */\n readonly operations: ReadonlyMap<string, OperationDef>;\n /** Execute a selected operation through the real pipeline. */\n call<T = unknown>(operation: string, input?: unknown): Promise<T>;\n /** Resolve a controller class through a fresh main scope (root seams apply). */\n controller<T>(controller: new (...args: never[]) => T): Promise<T>;\n /** Resolve any dependency from the main root. */\n get<T>(token: unknown): Promise<T>;\n\n /** Renderer surface — present only when `pages` was selected. */\n readonly frontend: FrontendApplicationSurface;\n /** Backend push emitter feeding renderer subscriptions (pages only). */\n readonly events: RouterBridgeEvents;\n readonly rendererRoot: IContainer;\n open(page: string, params?: Params, query?: Params): Promise<FrontendPage>;\n navigate(page: string, params?: Params, query?: Params): Promise<FrontendPage>;\n current(): FrontendPage | null;\n settle(): Promise<void>;\n\n /** DOM driver — present only when `dom` was selected. */\n readonly target: Element;\n click(selector: string): Promise<void>;\n type(selector: string, text: string): Promise<void>;\n submit(selector: string): Promise<void>;\n text(selector: string): Promise<string>;\n exists(selector: string): Promise<boolean>;\n\n /** Finalize `test.*` expectations; fails on undrained renderer work. */\n verify(): Promise<void>;\n dispose(): Promise<void>;\n readonly [ENV_REGISTRY]: unknown;\n}\n\ninterface BuilderState {\n readonly configPath: string;\n readonly selection: WoodTestSelection | null;\n readonly ioc: TestIocBuilder;\n readonly importer: ModuleImporter;\n}\n\nconst CONTAINER_REGISTRY_SYMBOL = Symbol.for('@noego/wood.container.registry');\n\nfunction globalRegistrySize(): number {\n const registry = (globalThis as Record<symbol, unknown>)[CONTAINER_REGISTRY_SYMBOL];\n return registry instanceof Map ? registry.size : 0;\n}\n\nfunction tierMissing(tier: 'pages' | 'dom', member: string): never {\n throw new TestWoodError(\n `the ${member} is not available: this environment was built without { ${tier} } — add it to select()`,\n );\n}\n\n/** A router over the selected aperture with every controller auto-bound from the config. */\nfunction buildRouter(\n root: IContainer,\n source: WoodConfigSource,\n operations: Map<string, OperationDef>,\n importer: ModuleImporter,\n): WoodRouter {\n const router = new WoodRouter({\n container: root,\n controllersDir: source.mainControllersDir,\n middlewareDir: source.mainMiddlewareDir,\n mode: 'dev',\n });\n const bound = new Set<string>();\n for (const operation of operations.values()) {\n if (bound.has(operation.controller)) continue;\n bound.add(operation.controller);\n const file = source.mainControllerFile(operation.controller);\n router.registerControllerLoader(operation.controller, () => importer(file));\n }\n router.loadOperations(operations);\n router.validateControllers();\n return router;\n}\n\nasync function runCompose(\n root: IContainer,\n source: WoodConfigSource,\n context: WoodComposeContext,\n): Promise<WoodComposeResult | null> {\n const composePath = source.hookModule(source.config.main.testing?.compose);\n if (!composePath) return null;\n const compose = await importDefaultExport<WoodComposeMain>(context.importer, composePath, 'main.testing.compose');\n if (typeof compose !== 'function') {\n throw new TestWoodError(`main.testing.compose default export must be a function (${composePath})`);\n }\n return (await ExecutionContext.run(root as never, () => compose(root, context))) ?? null;\n}\n\n/**\n * Dispose the main root under its own execution context (service teardown\n * hooks resolve collaborators such as tracers exactly like any other call),\n * naming every instance whose dispose hook failed.\n */\nasync function disposeRoot(env: TestEnvironment): Promise<void> {\n try {\n await ExecutionContext.run(env.root as never, () => env.dispose());\n } catch (error) {\n const failures = (error as { failures?: Array<{ token: string; error: unknown }> }).failures;\n if (!Array.isArray(failures)) throw error;\n const detail = failures\n .map((failure) => `${failure.token}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`)\n .join('\\n ');\n throw new TestWoodError(`main root disposal failed for ${failures.length} instance(s):\\n ${detail}`);\n }\n}\n\nfunction assertNoGlobalContainerLeak(before: number, source: WoodConfigSource): void {\n if (!process.env.VITEST) return;\n const after = globalRegistrySize();\n if (after > before) {\n throw new TestWoodError(\n `${source.config.main.testing?.compose ?? 'the build'} registered ${after - before} process-global ` +\n 'Wood container(s) (getContainer). Compose on the root the builder hands you so environments stay isolated.',\n );\n }\n}\n\n/** Build the environment from an already-resolved selection (split from the builder for budget). */\nasync function assembleEnvironment(\n state: BuilderState,\n selection: ResolvedWoodSelection,\n): Promise<TestWoodEnvironment> {\n const source = loadWoodConfigSource(state.configPath);\n const operations = selectOperations(source.operations, selection.main);\n const catalog = selection.pages ? selectCatalog(source.views(), selection.pages) : null;\n\n const registryBefore = globalRegistrySize();\n let composed: WoodComposeResult | null = null;\n let renderer: RendererTier | null = null;\n const controllerScopes: Array<{ dispose(): Promise<void> }> = [];\n let router: WoodRouter | null = null;\n const context: WoodComposeContext = {\n configPath: source.configPath,\n configDir: source.configDir,\n config: source.config,\n selection,\n operations,\n importer: state.importer,\n call: async <T>(operation: string, input?: unknown): Promise<T> => {\n if (!router) throw new TestWoodError('compose context call() is available from start() onward, not during compose');\n const live = router;\n return (await ExecutionContext.run(env.root as never, () => live.dispatch(operation, input))) as T;\n },\n };\n\n // Production composition lands on the fresh root FIRST; the test's\n // substitutions are applied on top of it by the ioc builder, so a seam\n // always shadows what the app registered for the same token.\n const env: TestEnvironment = await state.ioc.build({\n prepare: async (root) => {\n composed = await runCompose(root, source, context);\n },\n });\n const root = env.root;\n\n const dispose = async (): Promise<void> => {\n if (renderer) await renderer.dispose();\n for (const scope of controllerScopes.reverse()) await scope.dispose();\n await composed?.drain?.();\n try {\n await disposeRoot(env);\n } finally {\n await composed?.dispose?.();\n }\n };\n\n try {\n router = buildRouter(root, source, operations, state.importer);\n await ExecutionContext.run(root as never, () => composed?.start?.());\n assertNoGlobalContainerLeak(registryBefore, source);\n if (catalog) {\n renderer = await buildRendererTier({\n source,\n router,\n catalog,\n operations,\n importer: state.importer,\n hooks: composed,\n dom: selection.dom,\n });\n }\n return environmentSurface({ root, env, router, operations, renderer, controllerScopes, dispose });\n } catch (error) {\n await dispose().catch(() => {});\n throw error;\n }\n}\n\ninterface SurfaceParts {\n root: IContainer;\n env: TestEnvironment;\n router: WoodRouter;\n operations: Map<string, OperationDef>;\n renderer: RendererTier | null;\n controllerScopes: Array<{ dispose(): Promise<void> }>;\n dispose(): Promise<void>;\n}\n\nfunction environmentSurface(parts: SurfaceParts): TestWoodEnvironment {\n const { root, env, router, operations, renderer, controllerScopes, dispose } = parts;\n const dom = (): WoodDomDriver => renderer?.dom ?? tierMissing('dom', 'dom driver');\n const pages = (): RendererTier => renderer ?? tierMissing('pages', 'renderer');\n\n return {\n root,\n router,\n operations,\n call: async <T>(operation: string, input?: unknown): Promise<T> => {\n if (!operations.has(operation)) {\n throw new TestWoodError(\n `operation \"${operation}\" is not in the selected aperture; available operations:\\n ${[...operations.keys()].join('\\n ')}`,\n );\n }\n return (await ExecutionContext.run(root as never, () => router.dispatch(operation, input))) as T;\n },\n controller: async <T>(controller: new (...args: never[]) => T): Promise<T> => {\n const scope = root.extend();\n controllerScopes.push(scope as unknown as { dispose(): Promise<void> });\n if (!scope.isRegistered(controller)) scope.registerClass(controller);\n return (await scope.get(controller)) as T;\n },\n get: async <T>(token: unknown): Promise<T> => (await root.get(token)) as T,\n\n get frontend() { return pages().frontend; },\n get events() { return pages().events; },\n get rendererRoot() { return pages().rendererRoot; },\n open: async (page, params, query) => pages().open(page, params, query),\n navigate: async (page, params, query) => pages().navigate(page, params, query),\n current: () => pages().current(),\n settle: async () => pages().settle(),\n\n get target() { return dom().target; },\n click: async (selector) => dom().click(selector),\n type: async (selector, text) => dom().type(selector, text),\n submit: async (selector) => dom().submit(selector),\n text: async (selector) => dom().text(selector),\n exists: async (selector) => dom().exists(selector),\n\n // Method expectations only: `settle()` is the completion contract for\n // owned renderer work, and a test may legitimately verify while a\n // deliberately deferred stream is still in flight.\n verify: () => env.verify(),\n dispose,\n [ENV_REGISTRY]: (env as unknown as Record<symbol, unknown>)[ENV_REGISTRY],\n };\n}\n\nexport class TestWoodBuilder {\n private constructor(private readonly state: BuilderState) {}\n\n static create(configPath: string): TestWoodBuilder {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n return new TestWoodBuilder({\n configPath: path.resolve(configPath),\n selection: null,\n ioc: testIoc(),\n importer: defaultImporter,\n });\n }\n\n /** Declare which tiers exist: `main`, `pages`, `dom`. Replaces any earlier selection. */\n select(selection: WoodTestSelection): TestWoodBuilder {\n resolveSelection(selection);\n return this.derive({ selection });\n }\n\n /** Route hook/controller imports through a caller-owned importer (escape hatch). */\n importer(importer: ModuleImporter): TestWoodBuilder {\n if (typeof importer !== 'function') throw new TestWoodError('importer() expects a function');\n return this.derive({ importer });\n }\n\n /* ---- Composition: exact @noego/testing semantics on the main root ---- */\n\n use(preset: UseInput): TestWoodBuilder {\n return this.derive({ ioc: this.state.ioc.use(preset) });\n }\n\n classes(config: ConfigMap<new (...args: any[]) => unknown>): TestWoodBuilder {\n return this.derive({ ioc: this.state.ioc.classes(config) });\n }\n\n functions(config: ConfigMap<(...args: any[]) => any>): TestWoodBuilder {\n return this.derive({ ioc: this.state.ioc.functions(config) });\n }\n\n values(config: ConfigMap<unknown>): TestWoodBuilder {\n return this.derive({ ioc: this.state.ioc.values(config) });\n }\n\n methods(config: MethodsConfig): TestWoodBuilder {\n return this.derive({ ioc: this.state.ioc.methods(config) });\n }\n\n async build(): Promise<TestWoodEnvironment> {\n if (!this.state.selection) {\n throw new TestWoodError('select() a tier before build(): { main }, { pages }, or both');\n }\n return assembleEnvironment(this.state, resolveSelection(this.state.selection));\n }\n\n private derive(patch: Partial<BuilderState>): TestWoodBuilder {\n return new TestWoodBuilder({ ...this.state, ...patch });\n }\n}\n\nexport function testWood(configPath: string): TestWoodBuilder {\n return TestWoodBuilder.create(configPath);\n}\n"],"mappings":"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","names":["root"]}
1
+ {"version":3,"sources":["../../src/testing/test_wood_config.ts"],"sourcesContent":["import { disposeControllerLifecycle, guardControllerLifecycleOnce } from '../controller/controller_lifecycle.js';\n/**\n * `testWood(configPath)` — the canonical Wood testing entry point.\n *\n * One persistent immutable builder over the production `wood.config.yml`:\n *\n * const app = await testWood('wood.config.yml')\n * .select({ main: { module: 'backend_service' }, pages: ['settings.backend-service'] })\n * .classes([[BackendServiceLifecycleCoordinator, LifecycleStub]])\n * .methods([[LifecycleStub, { getStatus: test.once(test.returns(status)) }]])\n * .build();\n *\n * `select` decides what exists (main router, renderer pages, DOM); the\n * `@noego/testing` vocabulary (`use/classes/functions/values/methods`, exact\n * tokens) decides what is replaced — controllers included, since the router\n * resolves them through the same root. Controllers, middleware and views are\n * auto-bound from the config; the app composes its backend through the\n * optional `main.testing.compose` hook and mounts pages through\n * `renderer.testing.host`.\n *\n * Every build creates fresh `@noego/ioc` roots (main, and renderer when\n * pages are selected) — nothing global is touched, so environments coexist\n * in one process.\n */\nimport path from 'node:path';\nimport type { IContainer } from '@noego/ioc';\nimport { ExecutionContext, flattenModule } from '@noego/ioc';\nimport { testIoc, TestIocBuilder, ENV_REGISTRY } from '@noego/testing';\nimport type { ConfigMap, MethodsConfig, TestEnvironment, UseInput } from '@noego/testing';\nimport { installWoodRuntime, WoodRouter } from '../runtime/runtime_module.js';\nimport type { OperationDef, ViewDef } from '../types/index.js';\nimport type { WoodConfig } from '../types/config.js';\nimport type { FrontendApplicationSurface } from '../frontend/frontend_surface.js';\nimport type { FrontendPage } from '../frontend/frontend_page.js';\nimport { Navigation } from '../navigation/index.js';\nimport type { RouterBridgeEvents } from './create_router_bridge.js';\nimport { TestWoodError, WOOD_FRONTEND_BRIDGE } from './test_wood.js';\nimport {\n defaultImporter,\n importDefaultExport,\n loadWoodConfigSource,\n type ModuleImporter,\n type WoodConfigSource,\n} from './wood_config_source.js';\nimport {\n resolveSelection,\n selectCatalog,\n selectOperations,\n type ResolvedWoodSelection,\n type WoodTestSelection,\n} from './wood_test_selection.js';\nimport {\n buildRendererTier,\n type RendererTier,\n type WoodDomDriver,\n type WoodRendererHooks,\n} from './wood_env_renderer.js';\nimport {\n buildRecordedEnvironment,\n cleanupAll,\n RegistrationOnlyRoot,\n TestWoodCleanupError,\n} from './registration_only_root.js';\n\n/** What the app's `main.testing.compose` default export receives. */\nexport interface WoodComposeContext {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n readonly selection: ResolvedWoodSelection;\n /** The selected operations (the router will serve exactly these). */\n readonly operations: ReadonlyMap<string, OperationDef>;\n readonly importer: ModuleImporter;\n /**\n * Dispatch through the environment's router. Available from `start()`\n * onward (the router is built after compose returns); calling it during\n * compose itself fails.\n */\n call<T = unknown>(operation: string, input?: unknown): Promise<T>;\n}\n\n/** What the compose hook may hand back; every member is optional. */\nexport interface WoodComposeResult extends WoodRendererHooks {\n /**\n * Runs AFTER the test's substitutions are installed on the root (compose\n * itself runs before them, so production registrations never shadow a\n * seam). Seed world state and warm caches here — through the seamed graph,\n * exactly as the test will observe it.\n */\n start?(): void | Promise<void>;\n /**\n * Quiesce BEFORE the main root disposes its instances: silence push\n * delivery, drain detached work — everything that still needs the live\n * graph (database, tracer) the services' own dispose hooks run against.\n */\n drain?(): void | Promise<void>;\n /** Release resources AFTER the roots are disposed (close files, remove dirs). */\n dispose?(): void | Promise<void>;\n}\n\n/**\n * The app's backend composition on the root the builder hands it. Register\n * lazily: nothing expensive should run until a test resolves it.\n */\nexport type WoodComposeMain = (\n root: IContainer,\n context: WoodComposeContext,\n) => WoodComposeResult | void | Promise<WoodComposeResult | void>;\n\ntype Params = Record<string, string>;\n\nexport interface TestWoodEnvironment {\n /** The main-process IoC root of this environment. */\n readonly root: IContainer;\n readonly router: WoodRouter;\n /** The selected operations. */\n readonly operations: ReadonlyMap<string, OperationDef>;\n /** Execute a selected operation through the real pipeline. */\n call<T = unknown>(operation: string, input?: unknown): Promise<T>;\n /** Resolve a controller class through a fresh main scope (root seams apply). */\n controller<T>(controller: new (...args: never[]) => T): Promise<T>;\n /** Resolve any dependency from the main root. */\n get<T>(token: unknown): Promise<T>;\n\n /** Renderer surface — present only when `pages` was selected. */\n readonly frontend: FrontendApplicationSurface;\n /** Backend push emitter feeding renderer subscriptions (pages only). */\n readonly events: RouterBridgeEvents;\n readonly rendererRoot: IContainer;\n open(page: string, params?: Params, query?: Params): Promise<FrontendPage>;\n navigate(page: string, params?: Params, query?: Params): Promise<FrontendPage>;\n current(): FrontendPage | null;\n settle(): Promise<void>;\n\n /** DOM driver — present only when `dom` was selected. */\n readonly target: Element;\n click(selector: string): Promise<void>;\n type(selector: string, text: string): Promise<void>;\n submit(selector: string): Promise<void>;\n text(selector: string): Promise<string>;\n exists(selector: string): Promise<boolean>;\n\n /** Finalize `test.*` expectations; fails on undrained renderer work. */\n verify(): Promise<void>;\n dispose(): Promise<void>;\n readonly [ENV_REGISTRY]: unknown;\n}\n\ntype IocMethod = 'use' | 'classes' | 'functions' | 'values' | 'methods';\ntype IocCall = { method: IocMethod; args: unknown[]; flat: boolean; order: number };\ntype ControllerClass = new (...args: never[]) => unknown;\n\n/** Recorder used by explicit .main() and .renderer() owner targeting. */\nexport interface WoodRootComposer {\n use(preset: UseInput): WoodRootComposer;\n classes(config: ConfigMap<new (...args: any[]) => unknown>): WoodRootComposer;\n functions(config: ConfigMap<(...args: any[]) => any>): WoodRootComposer;\n values(config: ConfigMap<unknown>): WoodRootComposer;\n methods(config: MethodsConfig): WoodRootComposer;\n}\n\ninterface BuilderState {\n readonly configPath: string;\n readonly selection: WoodTestSelection | null;\n readonly mainCalls: readonly IocCall[];\n readonly rendererCalls: readonly IocCall[];\n readonly importer: ModuleImporter;\n}\n\nconst CONTAINER_REGISTRY_SYMBOL = Symbol.for('@noego/wood.container.registry');\n\nfunction globalRegistrySize(): number {\n const registry = (globalThis as Record<symbol, unknown>)[CONTAINER_REGISTRY_SYMBOL];\n return registry instanceof Map ? registry.size : 0;\n}\n\nfunction tierMissing(tier: 'pages' | 'dom', member: string): never {\n throw new TestWoodError(\n `the ${member} is not available: this environment was built without { ${tier} } — add it to select()`,\n );\n}\n\nfunction recordComposer(compose: (root: WoodRootComposer) => unknown): Omit<IocCall, 'order'>[] {\n if (typeof compose !== 'function') throw new TestWoodError('owner composition expects a callback');\n const calls: Omit<IocCall, 'order'>[] = [];\n const root: WoodRootComposer = {\n use: (preset) => { calls.push({ method: 'use', args: [preset], flat: false }); return root; },\n classes: (config) => { calls.push({ method: 'classes', args: [config], flat: false }); return root; },\n functions: (config) => { calls.push({ method: 'functions', args: [config], flat: false }); return root; },\n values: (config) => { calls.push({ method: 'values', args: [config], flat: false }); return root; },\n methods: (config) => { calls.push({ method: 'methods', args: [config], flat: false }); return root; },\n };\n compose(root);\n return calls;\n}\n\nfunction applyCalls(builder: TestIocBuilder, calls: readonly IocCall[]): TestIocBuilder {\n let next = builder;\n for (const call of [...calls].sort((a, b) => a.order - b.order)) {\n next = (next as unknown as Record<IocMethod, (...args: unknown[]) => TestIocBuilder>)[call.method](...call.args);\n }\n return next;\n}\n\n/** Discover ownership from declarations only; constructors/factories never run. */\nfunction ownerTokens(builder: TestIocBuilder): Set<unknown> {\n const tokens = new Set<unknown>();\n const visit = (token: unknown): void => {\n if (tokens.has(token)) return;\n tokens.add(token);\n if (typeof token !== 'function') return;\n const injected = (Reflect as any).getMetadata?.(Symbol.for('ioc:inject:tokens'), token) as Map<number, unknown> | undefined;\n const design = (Reflect as any).getMetadata?.('design:paramtypes', token) ?? [];\n const indices = new Set<number>([...design.keys(), ...(injected?.keys() ?? [])]);\n for (const index of indices) visit(injected?.has(index) ? injected.get(index) : design[index]);\n };\n for (const write of builder.writes) {\n if (write.op === 'use') {\n for (const registration of flattenModule(write.module)) {\n visit(registration.token);\n if (registration.kind === 'class') visit(registration.implementation);\n for (const dependency of registration.dependencies) visit(dependency);\n }\n } else if (write.op !== 'methods') {\n visit(write.key);\n if (write.op === 'classes') visit(write.implementation);\n }\n }\n return tokens;\n}\n\nfunction tokenLabel(token: unknown): string {\n if (typeof token === 'function' && token.name) return token.name;\n return String(token);\n}\n\nfunction callEntries(call: IocCall): Array<[unknown, unknown]> {\n // Calling the shared builder performs canonical tuple/Map/object validation\n // and descriptor normalization; this helper only splits owner routing.\n applyCalls(testIoc(), [{ ...call, order: 0 }]);\n const config = call.args[0] as any;\n if (Array.isArray(config) || config instanceof Map) return [...config];\n return Reflect.ownKeys(config).map((key) => [key, config[key]]);\n}\n\nfunction resolveOwnerToken(\n key: unknown,\n exact: boolean,\n main: Set<unknown>,\n renderer: Set<unknown>,\n method: IocMethod,\n): { token: unknown; owner: 'main' | 'renderer' } {\n let token = key;\n if (!exact && typeof key === 'string') {\n const matches = [...new Set([...main, ...renderer])].filter(\n (candidate) => candidate === key || (typeof candidate === 'function' && candidate.name === key),\n );\n if (matches.length > 1) {\n throw new TestWoodError(`Ambiguous ${method} replacement name \"${key}\"; use an exact token`);\n }\n token = matches[0] ?? key;\n }\n const ownsMain = main.has(token);\n const ownsRenderer = renderer.has(token);\n if (!ownsMain && !ownsRenderer) {\n throw new TestWoodError(\n `Unknown ${method} replacement ${tokenLabel(token)} in the selected roots; ` +\n 'register it with .main() or .renderer()',\n );\n }\n if (ownsMain && ownsRenderer) {\n throw new TestWoodError(\n `Ambiguous ${method} replacement ${tokenLabel(token)} belongs to main and renderer; ` +\n 'target .main() or .renderer() explicitly',\n );\n }\n return { token, owner: ownsMain ? 'main' : 'renderer' };\n}\n\nfunction routeCalls(\n calls: readonly IocCall[],\n main: Set<unknown>,\n renderer: Set<unknown>,\n target: 'main' | 'renderer',\n): IocCall[] {\n // Replacement implementations become registrations too. Predeclare their\n // owner so .methods([[Replacement, ...]]) is order-independent.\n for (const call of calls) {\n if (!call.flat || call.method !== 'classes') continue;\n const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;\n for (const [key, implementation] of callEntries(call)) {\n const routed = resolveOwnerToken(key, exact || typeof key !== 'string', main, renderer, call.method);\n (routed.owner === 'main' ? main : renderer).add(implementation);\n }\n }\n\n const result: IocCall[] = [];\n for (const call of calls) {\n if (!call.flat) {\n if (target === 'main') result.push(call);\n continue;\n }\n const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;\n for (const [key, value] of callEntries(call)) {\n const routed = resolveOwnerToken(key, exact || typeof key !== 'string', main, renderer, call.method);\n if (routed.owner === target) {\n result.push({ ...call, flat: false, args: [[[routed.token, value]]] });\n }\n }\n }\n return result;\n}\n\nasync function loadController(\n source: WoodConfigSource,\n importer: ModuleImporter,\n reference: string,\n owner: 'main' | 'renderer',\n): Promise<ControllerClass> {\n const file = owner === 'main'\n ? source.mainControllerFile(reference)\n : source.rendererControllerFile(reference);\n const controller = await importDefaultExport<ControllerClass>(\n importer,\n file,\n `${owner} controller \"${reference}\"`,\n );\n if (typeof controller !== 'function') {\n throw new TestWoodError(`${owner} controller \"${reference}\" must default-export a class (${file})`);\n }\n return controller;\n}\n\nasync function loadMainControllers(\n source: WoodConfigSource,\n operations: ReadonlyMap<string, OperationDef>,\n importer: ModuleImporter,\n): Promise<Map<string, ControllerClass>> {\n const controllers = new Map<string, ControllerClass>();\n for (const operation of operations.values()) {\n if (!controllers.has(operation.controller)) {\n controllers.set(operation.controller, await loadController(source, importer, operation.controller, 'main'));\n }\n }\n return controllers;\n}\n\nasync function loadRendererControllers(\n source: WoodConfigSource,\n views: readonly ViewDef[],\n importer: ModuleImporter,\n): Promise<Map<string, ControllerClass>> {\n const controllers = new Map<string, ControllerClass>();\n const references = new Set<string>();\n for (const view of views) {\n if (view.controller) references.add(view.controller);\n for (const controller of view.layoutControllers ?? []) if (controller) references.add(controller);\n }\n for (const reference of references) {\n controllers.set(reference, await loadController(source, importer, reference, 'renderer'));\n }\n return controllers;\n}\n\nasync function runCompose(\n root: IContainer,\n source: WoodConfigSource,\n context: WoodComposeContext,\n): Promise<WoodComposeResult | null> {\n const composePath = source.hookModule(source.config.main.testing?.compose);\n if (!composePath) return null;\n const compose = await importDefaultExport<WoodComposeMain>(context.importer, composePath, 'main.testing.compose');\n if (typeof compose !== 'function') {\n throw new TestWoodError(`main.testing.compose default export must be a function (${composePath})`);\n }\n return (await ExecutionContext.run(root as never, () => compose(root, context))) ?? null;\n}\n\n/**\n * Dispose the main root under its own execution context (service teardown\n * hooks resolve collaborators such as tracers exactly like any other call),\n * naming every instance whose dispose hook failed.\n */\nasync function disposeRoot(env: TestEnvironment): Promise<void> {\n try {\n await ExecutionContext.run(env.root as never, () => env.dispose());\n } catch (error) {\n const failures = (error as { failures?: Array<{ token: string; error: unknown }> }).failures;\n if (!Array.isArray(failures)) throw error;\n const detail = failures\n .map((failure) => `${failure.token}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`)\n .join('\\n ');\n throw new TestWoodError(`main root disposal failed for ${failures.length} instance(s):\\n ${detail}`);\n }\n}\n\nfunction assertNoGlobalContainerLeak(before: number, source: WoodConfigSource): void {\n if (!process.env.VITEST) return;\n const after = globalRegistrySize();\n if (after > before) {\n throw new TestWoodError(\n `${source.config.main.testing?.compose ?? 'the build'} registered ${after - before} process-global ` +\n 'Wood container(s) (getContainer). Compose on the root the builder hands you so environments stay isolated.',\n );\n }\n}\n\ntype SelectedPageCatalog = ReturnType<typeof selectCatalog>;\ntype ControllerScope = { dispose(): Promise<void> };\n\ninterface AssemblyLiveState {\n composed: WoodComposeResult | null;\n env: TestEnvironment | null;\n renderer: RendererTier | null;\n router: WoodRouter | null;\n}\n\ninterface AssemblyInputs {\n readonly state: BuilderState;\n readonly selection: ResolvedWoodSelection;\n readonly source: WoodConfigSource;\n readonly operations: Map<string, OperationDef>;\n readonly catalog: SelectedPageCatalog | null;\n readonly registryBefore: number;\n readonly mainRegistrations: RegistrationOnlyRoot;\n readonly rendererRegistrations: RegistrationOnlyRoot | null;\n readonly controllerScopes: ControllerScope[];\n readonly live: AssemblyLiveState;\n readonly context: WoodComposeContext;\n readonly dispose: () => Promise<void>;\n}\n\nfunction createComposeContext(\n source: WoodConfigSource,\n state: BuilderState,\n selection: ResolvedWoodSelection,\n operations: Map<string, OperationDef>,\n live: AssemblyLiveState,\n): WoodComposeContext {\n return {\n configPath: source.configPath,\n configDir: source.configDir,\n config: source.config,\n selection,\n operations,\n importer: state.importer,\n call: async <T>(operation: string, input?: unknown): Promise<T> => {\n if (!live.router || !live.env) {\n throw new TestWoodError('compose context call() is available from start() onward, not during compose');\n }\n const router = live.router;\n return (await ExecutionContext.run(\n live.env.root as never,\n () => router.dispatch(operation, input),\n )) as T;\n },\n };\n}\n\nfunction createAssemblyDisposer(\n live: AssemblyLiveState,\n mainRegistrations: RegistrationOnlyRoot,\n rendererRegistrations: RegistrationOnlyRoot | null,\n controllerScopes: ControllerScope[],\n): () => Promise<void> {\n let closing: Promise<void> | null = null;\n return (): Promise<void> => closing ??= cleanupAll('testWood cleanup failed', [\n ['renderer', async () => { if (live.renderer) await live.renderer.dispose(); }],\n ...[...controllerScopes].reverse().map(\n (scope, index) => [`controller scope ${controllerScopes.length - index}`, () => scope.dispose()] as const,\n ),\n ['compose drain', async () => { await live.composed?.drain?.(); }],\n ['main root', async () => {\n if (live.env) await disposeRoot(live.env);\n else await mainRegistrations.actualRoot.dispose();\n }],\n ['unbuilt renderer root', async () => {\n if (!live.renderer && rendererRegistrations) await rendererRegistrations.actualRoot.dispose();\n }],\n ['compose external dispose', async () => { await live.composed?.dispose?.(); }],\n ]);\n}\n\nasync function loadAssemblyControllers(\n inputs: AssemblyInputs,\n): Promise<{\n main: Map<string, ControllerClass>;\n renderer: Map<string, ControllerClass>;\n}> {\n const main = await loadMainControllers(inputs.source, inputs.operations, inputs.state.importer);\n const renderer = inputs.catalog\n ? await loadRendererControllers(inputs.source, inputs.catalog.all(), inputs.state.importer)\n : new Map<string, ControllerClass>();\n return { main, renderer };\n}\n\nfunction installAssemblyRuntime(\n inputs: AssemblyInputs,\n mainControllers: ReadonlyMap<string, ControllerClass>,\n): void {\n installWoodRuntime(inputs.mainRegistrations.root, {\n controllersDir: inputs.source.mainControllersDir,\n middlewareDir: inputs.source.mainMiddlewareDir,\n mode: 'dev',\n registerRuntime: (configuredRouter) => {\n for (const [identity, controller] of mainControllers) {\n configuredRouter.registerControllerLoader(identity, () => Promise.resolve({ default: controller }));\n }\n configuredRouter.loadOperations(inputs.operations);\n },\n });\n}\n\nasync function configureRendererRegistrations(inputs: AssemblyInputs): Promise<void> {\n const registrations = inputs.rendererRegistrations;\n if (!registrations) return;\n registrations.declare(Navigation);\n registrations.declare(WOOD_FRONTEND_BRIDGE);\n await ExecutionContext.run(\n registrations.root as never,\n () => inputs.live.composed?.configureRendererRoot?.(registrations.root),\n );\n}\n\nfunction registerControllersBeforeOverlays(\n inputs: AssemblyInputs,\n mainControllers: ReadonlyMap<string, ControllerClass>,\n rendererControllers: ReadonlyMap<string, ControllerClass>,\n): void {\n for (const controller of mainControllers.values()) {\n inputs.mainRegistrations.declare(controller);\n if (!inputs.mainRegistrations.actualRoot.isRegistered(controller)) {\n inputs.mainRegistrations.root.registerClass(controller);\n }\n }\n for (const controller of rendererControllers.values()) {\n inputs.rendererRegistrations?.declare(controller);\n }\n}\n\nfunction buildAssemblyIoc(\n inputs: AssemblyInputs,\n): { main: TestIocBuilder; renderer: TestIocBuilder } {\n const explicitMain = inputs.state.mainCalls.filter((call) => !call.flat);\n const mainOwners = new Set([\n ...inputs.mainRegistrations.owners,\n ...ownerTokens(applyCalls(testIoc(), explicitMain)),\n ]);\n const rendererOwners = new Set([\n ...(inputs.rendererRegistrations?.owners ?? []),\n ...ownerTokens(applyCalls(testIoc(), inputs.state.rendererCalls)),\n ]);\n const routedMain = routeCalls(inputs.state.mainCalls, mainOwners, rendererOwners, 'main');\n const routedRenderer = routeCalls(inputs.state.mainCalls, mainOwners, rendererOwners, 'renderer');\n return {\n main: applyCalls(testIoc(), routedMain),\n renderer: applyCalls(testIoc(), [...inputs.state.rendererCalls, ...routedRenderer]),\n };\n}\n\nasync function materializeEnvironment(inputs: AssemblyInputs): Promise<TestWoodEnvironment> {\n const controllers = await loadAssemblyControllers(inputs);\n inputs.live.composed = await runCompose(inputs.mainRegistrations.root, inputs.source, inputs.context);\n installAssemblyRuntime(inputs, controllers.main);\n await configureRendererRegistrations(inputs);\n registerControllersBeforeOverlays(inputs, controllers.main, controllers.renderer);\n const ioc = buildAssemblyIoc(inputs);\n\n inputs.live.env = await buildRecordedEnvironment(ioc.main, inputs.mainRegistrations, 'main');\n const root = inputs.live.env.root;\n inputs.live.router = await Promise.resolve(\n ExecutionContext.run(root as never, () => root.get(WoodRouter)),\n ) as WoodRouter;\n await ExecutionContext.run(root as never, () => inputs.live.composed?.start?.());\n assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);\n\n if (inputs.catalog && inputs.rendererRegistrations) {\n inputs.live.renderer = await buildRendererTier({\n source: inputs.source,\n router: inputs.live.router,\n catalog: inputs.catalog,\n operations: inputs.operations,\n importer: inputs.state.importer,\n hooks: inputs.live.composed,\n dom: inputs.selection.dom,\n ioc: ioc.renderer,\n registrations: inputs.rendererRegistrations,\n controllerClasses: controllers.renderer,\n });\n }\n return environmentSurface({\n root,\n env: inputs.live.env,\n router: inputs.live.router,\n operations: inputs.operations,\n renderer: inputs.live.renderer,\n controllerScopes: inputs.controllerScopes,\n dispose: inputs.dispose,\n });\n}\n\nasync function rollbackEnvironment(error: unknown, dispose: () => Promise<void>): Promise<never> {\n try {\n await dispose();\n } catch (cleanupError) {\n const failures = [\n ...(error instanceof TestWoodCleanupError ? error.failures : []),\n ...(cleanupError instanceof TestWoodCleanupError\n ? cleanupError.failures\n : [{ owner: 'build rollback', error: cleanupError }]),\n ];\n throw new TestWoodCleanupError('testWood build failed and rollback failed', failures, error);\n }\n throw error;\n}\n\n/** Build the environment from an already-resolved selection (split from the builder for budget). */\nasync function assembleEnvironment(\n state: BuilderState,\n selection: ResolvedWoodSelection,\n): Promise<TestWoodEnvironment> {\n const source = loadWoodConfigSource(state.configPath);\n const operations = selectOperations(source.operations, selection.main);\n const catalog = selection.pages ? selectCatalog(source.views(), selection.pages) : null;\n if (!catalog && state.rendererCalls.length) {\n throw new TestWoodError('.renderer() composition requires a selected pages tier');\n }\n\n const registryBefore = globalRegistrySize();\n const mainRegistrations = new RegistrationOnlyRoot('main.testing.compose');\n const rendererRegistrations = catalog\n ? new RegistrationOnlyRoot('configureRendererRoot')\n : null;\n const controllerScopes: ControllerScope[] = [];\n const live: AssemblyLiveState = {\n composed: null,\n env: null,\n renderer: null,\n router: null,\n };\n const context = createComposeContext(source, state, selection, operations, live);\n const dispose = createAssemblyDisposer(\n live,\n mainRegistrations,\n rendererRegistrations,\n controllerScopes,\n );\n const inputs: AssemblyInputs = {\n state,\n selection,\n source,\n operations,\n catalog,\n registryBefore,\n mainRegistrations,\n rendererRegistrations,\n controllerScopes,\n live,\n context,\n dispose,\n };\n\n try {\n return await materializeEnvironment(inputs);\n } catch (error) {\n return rollbackEnvironment(error, dispose);\n }\n}\n\ninterface SurfaceParts {\n root: IContainer;\n env: TestEnvironment;\n router: WoodRouter;\n operations: Map<string, OperationDef>;\n renderer: RendererTier | null;\n controllerScopes: Array<{ dispose(): Promise<void> }>;\n dispose(): Promise<void>;\n}\n\nfunction environmentSurface(parts: SurfaceParts): TestWoodEnvironment {\n const { root, env, router, operations, renderer, controllerScopes, dispose } = parts;\n const dom = (): WoodDomDriver => renderer?.dom ?? tierMissing('dom', 'dom driver');\n const pages = (): RendererTier => renderer ?? tierMissing('pages', 'renderer');\n\n return {\n root,\n router,\n operations,\n call: async <T>(operation: string, input?: unknown): Promise<T> => {\n if (!operations.has(operation)) {\n throw new TestWoodError(\n `operation \"${operation}\" is not in the selected aperture; available operations:\\n ${[...operations.keys()].join('\\n ')}`,\n );\n }\n return (await ExecutionContext.run(root as never, () => router.dispatch(operation, input))) as T;\n },\n controller: async <T>(controller: new (...args: never[]) => T): Promise<T> => {\n const scope = root.extend();\n const ownsController = !root.isRegistered(controller);\n let instance: T | undefined;\n controllerScopes.push({ dispose: () => ExecutionContext.run(scope as never, () => cleanupAll('controller cleanup failed', [\n ['controller', async () => { if (ownsController) await disposeControllerLifecycle(instance); }],\n ['scope', () => scope.dispose()],\n ])) as Promise<void> });\n if (!scope.isRegistered(controller)) scope.registerClass(controller);\n instance = await ExecutionContext.run(scope as never, () => scope.get(controller)) as T;\n guardControllerLifecycleOnce(instance);\n return instance;\n },\n get: async <T>(token: unknown): Promise<T> => (await root.get(token)) as T,\n\n get frontend() { return pages().frontend; },\n get events() { return pages().events; },\n get rendererRoot() { return pages().rendererRoot; },\n open: async (page, params, query) => pages().open(page, params, query),\n navigate: async (page, params, query) => pages().navigate(page, params, query),\n current: () => pages().current(),\n settle: async () => pages().settle(),\n\n get target() { return dom().target; },\n click: async (selector) => dom().click(selector),\n type: async (selector, text) => dom().type(selector, text),\n submit: async (selector) => dom().submit(selector),\n text: async (selector) => dom().text(selector),\n exists: async (selector) => dom().exists(selector),\n\n // Method expectations only: `settle()` is the completion contract for\n // owned renderer work, and a test may legitimately verify while a\n // deliberately deferred stream is still in flight.\n verify: async () => {\n const failures: Array<{ owner: string; error: unknown }> = [];\n await env.verify().catch((error) => failures.push({ owner: 'main', error }));\n if (renderer) {\n await renderer.verify().catch((error) => failures.push({ owner: 'renderer', error }));\n }\n if (failures.length === 1) throw failures[0]!.error;\n if (failures.length) {\n throw new TestWoodError(\n 'testWood verification failed in both roots:\\n ' +\n failures.map(({ owner, error }) =>\n `${owner}: ${error instanceof Error ? error.message : String(error)}`).join('\\n '),\n );\n }\n },\n dispose,\n [ENV_REGISTRY]: (env as unknown as Record<symbol, unknown>)[ENV_REGISTRY],\n };\n}\n\nexport class TestWoodBuilder {\n private constructor(private readonly state: BuilderState) {}\n\n static create(configPath: string): TestWoodBuilder {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n return new TestWoodBuilder({\n configPath: path.resolve(configPath),\n selection: null,\n mainCalls: [],\n rendererCalls: [],\n importer: defaultImporter,\n });\n }\n\n /** Declare which tiers exist: `main`, `pages`, `dom`. Replaces any earlier selection. */\n select(selection: WoodTestSelection): TestWoodBuilder {\n resolveSelection(selection);\n return this.derive({ selection });\n }\n\n /** Route hook/controller imports through a caller-owned importer (escape hatch). */\n importer(importer: ModuleImporter): TestWoodBuilder {\n if (typeof importer !== 'function') throw new TestWoodError('importer() expects a function');\n return this.derive({ importer });\n }\n\n /** Explicitly compose or replace dependencies on the main-process root. */\n main(compose: (root: WoodRootComposer) => unknown): TestWoodBuilder {\n return this.append('main', recordComposer(compose));\n }\n\n /** Explicitly compose or replace dependencies on the renderer root. */\n renderer(compose: (root: WoodRootComposer) => unknown): TestWoodBuilder {\n return this.append('renderer', recordComposer(compose));\n }\n\n /** Reusable presets retain the historical main-root default. */\n use(preset: UseInput): TestWoodBuilder {\n return this.append('main', [{ method: 'use', args: [preset], flat: false }]);\n }\n\n /** Exact-token replacement routed to the sole declared selected owner. */\n classes(config: ConfigMap<new (...args: any[]) => unknown>): TestWoodBuilder {\n return this.append('main', [{ method: 'classes', args: [config], flat: true }]);\n }\n\n /** Exact-token replacement routed to the sole declared selected owner. */\n functions(config: ConfigMap<(...args: any[]) => any>): TestWoodBuilder {\n return this.append('main', [{ method: 'functions', args: [config], flat: true }]);\n }\n\n /** Exact-token replacement routed to the sole declared selected owner. */\n values(config: ConfigMap<unknown>): TestWoodBuilder {\n return this.append('main', [{ method: 'values', args: [config], flat: true }]);\n }\n\n /** Exact-token method seams routed to the sole declared selected owner. */\n methods(config: MethodsConfig): TestWoodBuilder {\n return this.append('main', [{ method: 'methods', args: [config], flat: true }]);\n }\n\n async build(): Promise<TestWoodEnvironment> {\n if (!this.state.selection) {\n throw new TestWoodError('select() a tier before build(): { main }, { pages }, or both');\n }\n return assembleEnvironment(this.state, resolveSelection(this.state.selection));\n }\n\n private append(\n owner: 'main' | 'renderer',\n calls: ReadonlyArray<Omit<IocCall, 'order'>>,\n ): TestWoodBuilder {\n const start = this.state.mainCalls.length + this.state.rendererCalls.length;\n const ordered = calls.map((call, index) => ({ ...call, order: start + index }));\n // Preserve @noego/testing's call-time validation and descriptor semantics.\n applyCalls(testIoc(), ordered);\n return owner === 'main'\n ? this.derive({ mainCalls: [...this.state.mainCalls, ...ordered] })\n : this.derive({ rendererCalls: [...this.state.rendererCalls, ...ordered] });\n }\n\n private derive(patch: Partial<BuilderState>): TestWoodBuilder {\n return new TestWoodBuilder({ ...this.state, ...patch });\n }\n}\n\nexport function testWood(configPath: string): TestWoodBuilder {\n return TestWoodBuilder.create(configPath);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var wood_dom_animation_exports = {};
20
+ __export(wood_dom_animation_exports, {
21
+ WoodTestAnimation: () => WoodTestAnimation,
22
+ animationLeaseCount: () => animationLeaseCount,
23
+ animationRealmOf: () => animationRealmOf,
24
+ hasNativeAnimate: () => hasNativeAnimate,
25
+ leaseDomAnimation: () => leaseDomAnimation
26
+ });
27
+ module.exports = __toCommonJS(wood_dom_animation_exports);
28
+ const PATCHES = /* @__PURE__ */ new WeakMap();
29
+ function defaultTimers() {
30
+ return {
31
+ setTimeout: (callback, ms) => globalThis.setTimeout(callback, ms),
32
+ clearTimeout: (handle) => globalThis.clearTimeout(handle),
33
+ now: () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now()
34
+ };
35
+ }
36
+ function toMs(value) {
37
+ const n = typeof value === "number" ? value : Number(value);
38
+ return Number.isFinite(n) && n > 0 ? n : 0;
39
+ }
40
+ function parseTiming(options) {
41
+ if (typeof options === "number") return { delay: 0, duration: toMs(options), fill: "none", easing: "linear" };
42
+ const o = options && typeof options === "object" ? options : {};
43
+ return {
44
+ delay: toMs(o.delay),
45
+ duration: toMs(o.duration),
46
+ fill: typeof o.fill === "string" ? o.fill : "none",
47
+ easing: typeof o.easing === "string" ? o.easing : "linear"
48
+ };
49
+ }
50
+ class WoodTestAnimation {
51
+ constructor(target, keyframes, timing, timers, onSettled) {
52
+ this.target = target;
53
+ this.keyframes = keyframes;
54
+ this.timing = timing;
55
+ this.timers = timers;
56
+ this.onSettled = onSettled;
57
+ this.onfinish = null;
58
+ this.oncancel = null;
59
+ this.playbackRate = 1;
60
+ this.pending = false;
61
+ this.id = "";
62
+ this.state = "running";
63
+ this.timer = null;
64
+ this.startedAt = timers.now();
65
+ this.finished = new Promise((resolve) => {
66
+ this.resolveFinished = resolve;
67
+ });
68
+ const total = timing.delay + timing.duration;
69
+ this.effect = {
70
+ target,
71
+ getKeyframes: () => keyframes,
72
+ getTiming: () => ({ delay: timing.delay, duration: timing.duration, fill: timing.fill, easing: timing.easing }),
73
+ getComputedTiming: () => ({
74
+ delay: timing.delay,
75
+ duration: timing.duration,
76
+ activeDuration: timing.duration,
77
+ endTime: total,
78
+ fill: timing.fill,
79
+ easing: timing.easing
80
+ })
81
+ };
82
+ this.timer = timers.setTimeout(() => {
83
+ this.timer = null;
84
+ this.complete();
85
+ }, total);
86
+ }
87
+ get playState() {
88
+ return this.state;
89
+ }
90
+ get startTime() {
91
+ return this.state === "idle" ? null : this.startedAt;
92
+ }
93
+ /** Elapsed time in the active interval (delay counts down from negative), clamped like a finished animation. */
94
+ get currentTime() {
95
+ if (this.state === "idle") return null;
96
+ if (this.state === "finished") return this.timing.duration;
97
+ const elapsed = this.timers.now() - this.startedAt - this.timing.delay;
98
+ return Math.min(Math.max(elapsed, 0), this.timing.duration);
99
+ }
100
+ set currentTime(value) {
101
+ if (value === null) return;
102
+ this.startedAt = this.timers.now() - this.timing.delay - toMs(value);
103
+ }
104
+ /** Cancels the animation: no completion, `playState` becomes `idle`, owned timer cleared. */
105
+ cancel() {
106
+ if (this.state === "idle") return;
107
+ const wasRunning = this.state === "running";
108
+ this.clearTimer();
109
+ this.state = "idle";
110
+ this.onSettled(this);
111
+ if (wasRunning) this.oncancel?.({ type: "cancel", target: this, currentTime: null });
112
+ }
113
+ /** Completes now (still notifies `onfinish` synchronously, as an explicit caller request). */
114
+ finish() {
115
+ if (this.state !== "running") return;
116
+ this.clearTimer();
117
+ this.complete();
118
+ }
119
+ play() {
120
+ if (this.state === "running") return;
121
+ this.startedAt = this.timers.now();
122
+ this.state = "running";
123
+ this.timer = this.timers.setTimeout(() => {
124
+ this.timer = null;
125
+ this.complete();
126
+ }, this.timing.delay + this.timing.duration);
127
+ }
128
+ pause() {
129
+ }
130
+ /** Called by the owning realm on release; never fires completion. */
131
+ dispose() {
132
+ this.clearTimer();
133
+ this.state = "idle";
134
+ }
135
+ clearTimer() {
136
+ if (this.timer !== null) {
137
+ this.timers.clearTimeout(this.timer);
138
+ this.timer = null;
139
+ }
140
+ }
141
+ complete() {
142
+ if (this.state !== "running") return;
143
+ this.state = "finished";
144
+ this.onSettled(this);
145
+ this.resolveFinished(this);
146
+ this.onfinish?.({ type: "finish", target: this, currentTime: this.timing.duration });
147
+ }
148
+ }
149
+ function animationRealmOf(target) {
150
+ const view = target?.ownerDocument?.defaultView;
151
+ const ctor = view?.Element ?? globalThis.Element;
152
+ const proto = ctor?.prototype;
153
+ return proto && typeof proto === "object" ? proto : null;
154
+ }
155
+ function hasNativeAnimate(proto) {
156
+ return typeof proto.animate === "function";
157
+ }
158
+ function animationLeaseCount(proto) {
159
+ return PATCHES.get(proto)?.leases ?? 0;
160
+ }
161
+ function install(proto, timers) {
162
+ const descriptor = Object.getOwnPropertyDescriptor(proto, "animate");
163
+ const active = /* @__PURE__ */ new Set();
164
+ const animate = function(keyframes, options) {
165
+ const animation = new WoodTestAnimation(this, keyframes, parseTiming(options), timers, (a) => active.delete(a));
166
+ active.add(animation);
167
+ return animation;
168
+ };
169
+ Object.defineProperty(proto, "animate", { value: animate, configurable: true, writable: true, enumerable: false });
170
+ const patch = { leases: 0, descriptor, animate, active, timers };
171
+ PATCHES.set(proto, patch);
172
+ return patch;
173
+ }
174
+ function uninstall(proto, patch) {
175
+ PATCHES.delete(proto);
176
+ for (const animation of [...patch.active]) animation.dispose();
177
+ patch.active.clear();
178
+ if (proto.animate !== patch.animate) return;
179
+ if (patch.descriptor) Object.defineProperty(proto, "animate", patch.descriptor);
180
+ else delete proto.animate;
181
+ }
182
+ function leaseDomAnimation(target, timers = defaultTimers()) {
183
+ const proto = animationRealmOf(target);
184
+ if (!proto) return { adapted: false, realm: null, release() {
185
+ } };
186
+ let patch = PATCHES.get(proto);
187
+ if (!patch) {
188
+ if (hasNativeAnimate(proto)) return { adapted: false, realm: proto, release() {
189
+ } };
190
+ patch = install(proto, timers);
191
+ }
192
+ patch.leases += 1;
193
+ let released = false;
194
+ return {
195
+ adapted: true,
196
+ realm: proto,
197
+ release() {
198
+ if (released) return;
199
+ released = true;
200
+ patch.leases -= 1;
201
+ if (patch.leases <= 0) uninstall(proto, patch);
202
+ }
203
+ };
204
+ }
205
+ // Annotate the CommonJS export names for ESM import in node:
206
+ 0 && (module.exports = {
207
+ WoodTestAnimation,
208
+ animationLeaseCount,
209
+ animationRealmOf,
210
+ hasNativeAnimate,
211
+ leaseDomAnimation
212
+ });
213
+ //# sourceMappingURL=wood_dom_animation.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/wood_dom_animation.ts"],"sourcesContent":["/**\n * Test-only Web Animations adapter for DOM environments that lack\n * `Element.prototype.animate` (happy-dom). Svelte 5 transitions call\n * `element.animate(keyframes, { duration, fill })` twice per transition — a\n * dummy animation lasting the transition's delay, then the real one — and rely\n * on `onfinish`, `cancel()`, `currentTime`, `playState` and `effect`.\n *\n * The adapter is timing-respecting: `onfinish` fires asynchronously after\n * `delay + duration` on the test's timers (real or fake), never immediately —\n * an immediately-finishing shim completes outros early and wedges real\n * round-trips. `cancel()` suppresses completion.\n *\n * Leases are reference-counted per realm (`Element.prototype` of the target's\n * `ownerDocument.defaultView`). The first lease installs the adapter only when\n * `animate` is absent (native/polyfilled `animate` is never touched); the last\n * release cancels every owned animation, clears its timers and restores the\n * original property descriptor. This module is part of the testing entry and\n * must never be reached by production or browser runtime code.\n */\n\nexport interface WoodAnimationTimers {\n setTimeout(callback: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n now(): number;\n}\n\nexport interface WoodAnimationLease {\n /** True when this lease installed or shares the adapter (false: native `animate` present or no realm). */\n readonly adapted: boolean;\n /** The `Element.prototype` this lease refers to (null when no realm could be resolved). */\n readonly realm: object | null;\n /** Release the lease; idempotent. */\n release(): void;\n}\n\nexport type WoodAnimationPlayState = 'idle' | 'running' | 'finished';\n\n/** Normalised effect timing derived from the `animate()` options. */\nexport interface WoodAnimationTiming {\n delay: number;\n duration: number;\n fill: string;\n easing: string;\n}\n\ninterface RealmPatch {\n leases: number;\n readonly descriptor: PropertyDescriptor | undefined;\n readonly animate: (this: unknown, keyframes: unknown, options?: unknown) => WoodTestAnimation;\n readonly active: Set<WoodTestAnimation>;\n readonly timers: WoodAnimationTimers;\n}\n\nconst PATCHES = new WeakMap<object, RealmPatch>();\n\nfunction defaultTimers(): WoodAnimationTimers {\n return {\n setTimeout: (callback, ms) => globalThis.setTimeout(callback, ms),\n clearTimeout: (handle) => globalThis.clearTimeout(handle as ReturnType<typeof setTimeout>),\n now: () => (typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now()),\n };\n}\n\nfunction toMs(value: unknown): number {\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) && n > 0 ? n : 0;\n}\n\nfunction parseTiming(options: unknown): WoodAnimationTiming {\n if (typeof options === 'number') return { delay: 0, duration: toMs(options), fill: 'none', easing: 'linear' };\n const o = (options && typeof options === 'object' ? options : {}) as Record<string, unknown>;\n return {\n delay: toMs(o.delay),\n duration: toMs(o.duration),\n fill: typeof o.fill === 'string' ? o.fill : 'none',\n easing: typeof o.easing === 'string' ? o.easing : 'linear',\n };\n}\n\n/** Minimal `Animation` stand-in honouring the members Svelte transitions use. */\nexport class WoodTestAnimation {\n onfinish: ((event: unknown) => void) | null = null;\n oncancel: ((event: unknown) => void) | null = null;\n effect: unknown;\n playbackRate = 1;\n pending = false;\n id = '';\n readonly finished: Promise<WoodTestAnimation>;\n\n private state: WoodAnimationPlayState = 'running';\n private startedAt: number;\n private timer: unknown = null;\n private resolveFinished!: (animation: WoodTestAnimation) => void;\n\n constructor(\n readonly target: unknown,\n readonly keyframes: unknown,\n readonly timing: WoodAnimationTiming,\n private readonly timers: WoodAnimationTimers,\n private readonly onSettled: (animation: WoodTestAnimation) => void,\n ) {\n this.startedAt = timers.now();\n this.finished = new Promise((resolve) => { this.resolveFinished = resolve; });\n const total = timing.delay + timing.duration;\n this.effect = {\n target,\n getKeyframes: () => keyframes,\n getTiming: () => ({ delay: timing.delay, duration: timing.duration, fill: timing.fill, easing: timing.easing }),\n getComputedTiming: () => ({\n delay: timing.delay,\n duration: timing.duration,\n activeDuration: timing.duration,\n endTime: total,\n fill: timing.fill,\n easing: timing.easing,\n }),\n };\n this.timer = timers.setTimeout(() => {\n this.timer = null;\n this.complete();\n }, total);\n }\n\n get playState(): WoodAnimationPlayState {\n return this.state;\n }\n\n get startTime(): number | null {\n return this.state === 'idle' ? null : this.startedAt;\n }\n\n /** Elapsed time in the active interval (delay counts down from negative), clamped like a finished animation. */\n get currentTime(): number | null {\n if (this.state === 'idle') return null;\n if (this.state === 'finished') return this.timing.duration;\n const elapsed = this.timers.now() - this.startedAt - this.timing.delay;\n return Math.min(Math.max(elapsed, 0), this.timing.duration);\n }\n\n set currentTime(value: number | null) {\n if (value === null) return;\n this.startedAt = this.timers.now() - this.timing.delay - toMs(value);\n }\n\n /** Cancels the animation: no completion, `playState` becomes `idle`, owned timer cleared. */\n cancel(): void {\n if (this.state === 'idle') return;\n const wasRunning = this.state === 'running';\n this.clearTimer();\n this.state = 'idle';\n this.onSettled(this);\n if (wasRunning) this.oncancel?.({ type: 'cancel', target: this, currentTime: null });\n }\n\n /** Completes now (still notifies `onfinish` synchronously, as an explicit caller request). */\n finish(): void {\n if (this.state !== 'running') return;\n this.clearTimer();\n this.complete();\n }\n\n play(): void {\n if (this.state === 'running') return;\n this.startedAt = this.timers.now();\n this.state = 'running';\n this.timer = this.timers.setTimeout(() => {\n this.timer = null;\n this.complete();\n }, this.timing.delay + this.timing.duration);\n }\n\n pause(): void {\n // Pausing is not modelled beyond keeping the timer; Svelte never pauses transitions.\n }\n\n /** Called by the owning realm on release; never fires completion. */\n dispose(): void {\n this.clearTimer();\n this.state = 'idle';\n }\n\n private clearTimer(): void {\n if (this.timer !== null) {\n this.timers.clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n private complete(): void {\n if (this.state !== 'running') return;\n this.state = 'finished';\n this.onSettled(this);\n this.resolveFinished(this);\n this.onfinish?.({ type: 'finish', target: this, currentTime: this.timing.duration });\n }\n}\n\n/** Resolves the `Element.prototype` of the realm owning `target` (its `ownerDocument.defaultView`), or null. */\nexport function animationRealmOf(target: unknown): object | null {\n const view = (target as { ownerDocument?: { defaultView?: unknown } } | null)?.ownerDocument?.defaultView;\n const ctor = (view as { Element?: { prototype?: object } } | undefined)?.Element\n ?? (globalThis as { Element?: { prototype?: object } }).Element;\n const proto = ctor?.prototype;\n return proto && typeof proto === 'object' ? proto : null;\n}\n\n/** True when `proto` (or its chain) already exposes a usable `animate`. */\nexport function hasNativeAnimate(proto: object): boolean {\n return typeof (proto as { animate?: unknown }).animate === 'function';\n}\n\n/** Number of live leases on a realm (0 when the adapter is not installed). */\nexport function animationLeaseCount(proto: object): number {\n return PATCHES.get(proto)?.leases ?? 0;\n}\n\nfunction install(proto: object, timers: WoodAnimationTimers): RealmPatch {\n const descriptor = Object.getOwnPropertyDescriptor(proto, 'animate');\n const active = new Set<WoodTestAnimation>();\n const animate = function (this: unknown, keyframes: unknown, options?: unknown): WoodTestAnimation {\n const animation = new WoodTestAnimation(this, keyframes, parseTiming(options), timers, (a) => active.delete(a));\n active.add(animation);\n return animation;\n };\n Object.defineProperty(proto, 'animate', { value: animate, configurable: true, writable: true, enumerable: false });\n const patch: RealmPatch = { leases: 0, descriptor, animate, active, timers };\n PATCHES.set(proto, patch);\n return patch;\n}\n\nfunction uninstall(proto: object, patch: RealmPatch): void {\n PATCHES.delete(proto);\n for (const animation of [...patch.active]) animation.dispose();\n patch.active.clear();\n if ((proto as { animate?: unknown }).animate !== patch.animate) return; // someone else replaced it; leave theirs\n if (patch.descriptor) Object.defineProperty(proto, 'animate', patch.descriptor);\n else delete (proto as { animate?: unknown }).animate;\n}\n\n/**\n * Acquire a lease on the realm owning `target`. Installs the adapter on first\n * lease when `animate` is absent; returns a no-op lease when a native\n * `animate` exists or no realm resolves.\n */\nexport function leaseDomAnimation(target: unknown, timers: WoodAnimationTimers = defaultTimers()): WoodAnimationLease {\n const proto = animationRealmOf(target);\n if (!proto) return { adapted: false, realm: null, release() {} };\n let patch = PATCHES.get(proto);\n if (!patch) {\n if (hasNativeAnimate(proto)) return { adapted: false, realm: proto, release() {} };\n patch = install(proto, timers);\n }\n patch.leases += 1;\n let released = false;\n return {\n adapted: true,\n realm: proto,\n release() {\n if (released) return;\n released = true;\n patch.leases -= 1;\n if (patch.leases <= 0) uninstall(proto, patch);\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Test-only Web Animations adapter for DOM environments that lack
3
+ * `Element.prototype.animate` (happy-dom). Svelte 5 transitions call
4
+ * `element.animate(keyframes, { duration, fill })` twice per transition — a
5
+ * dummy animation lasting the transition's delay, then the real one — and rely
6
+ * on `onfinish`, `cancel()`, `currentTime`, `playState` and `effect`.
7
+ *
8
+ * The adapter is timing-respecting: `onfinish` fires asynchronously after
9
+ * `delay + duration` on the test's timers (real or fake), never immediately —
10
+ * an immediately-finishing shim completes outros early and wedges real
11
+ * round-trips. `cancel()` suppresses completion.
12
+ *
13
+ * Leases are reference-counted per realm (`Element.prototype` of the target's
14
+ * `ownerDocument.defaultView`). The first lease installs the adapter only when
15
+ * `animate` is absent (native/polyfilled `animate` is never touched); the last
16
+ * release cancels every owned animation, clears its timers and restores the
17
+ * original property descriptor. This module is part of the testing entry and
18
+ * must never be reached by production or browser runtime code.
19
+ */
20
+ interface WoodAnimationTimers {
21
+ setTimeout(callback: () => void, ms: number): unknown;
22
+ clearTimeout(handle: unknown): void;
23
+ now(): number;
24
+ }
25
+ interface WoodAnimationLease {
26
+ /** True when this lease installed or shares the adapter (false: native `animate` present or no realm). */
27
+ readonly adapted: boolean;
28
+ /** The `Element.prototype` this lease refers to (null when no realm could be resolved). */
29
+ readonly realm: object | null;
30
+ /** Release the lease; idempotent. */
31
+ release(): void;
32
+ }
33
+ type WoodAnimationPlayState = 'idle' | 'running' | 'finished';
34
+ /** Normalised effect timing derived from the `animate()` options. */
35
+ interface WoodAnimationTiming {
36
+ delay: number;
37
+ duration: number;
38
+ fill: string;
39
+ easing: string;
40
+ }
41
+ /** Minimal `Animation` stand-in honouring the members Svelte transitions use. */
42
+ declare class WoodTestAnimation {
43
+ readonly target: unknown;
44
+ readonly keyframes: unknown;
45
+ readonly timing: WoodAnimationTiming;
46
+ private readonly timers;
47
+ private readonly onSettled;
48
+ onfinish: ((event: unknown) => void) | null;
49
+ oncancel: ((event: unknown) => void) | null;
50
+ effect: unknown;
51
+ playbackRate: number;
52
+ pending: boolean;
53
+ id: string;
54
+ readonly finished: Promise<WoodTestAnimation>;
55
+ private state;
56
+ private startedAt;
57
+ private timer;
58
+ private resolveFinished;
59
+ constructor(target: unknown, keyframes: unknown, timing: WoodAnimationTiming, timers: WoodAnimationTimers, onSettled: (animation: WoodTestAnimation) => void);
60
+ get playState(): WoodAnimationPlayState;
61
+ get startTime(): number | null;
62
+ /** Elapsed time in the active interval (delay counts down from negative), clamped like a finished animation. */
63
+ get currentTime(): number | null;
64
+ set currentTime(value: number | null);
65
+ /** Cancels the animation: no completion, `playState` becomes `idle`, owned timer cleared. */
66
+ cancel(): void;
67
+ /** Completes now (still notifies `onfinish` synchronously, as an explicit caller request). */
68
+ finish(): void;
69
+ play(): void;
70
+ pause(): void;
71
+ /** Called by the owning realm on release; never fires completion. */
72
+ dispose(): void;
73
+ private clearTimer;
74
+ private complete;
75
+ }
76
+ /** Resolves the `Element.prototype` of the realm owning `target` (its `ownerDocument.defaultView`), or null. */
77
+ declare function animationRealmOf(target: unknown): object | null;
78
+ /** True when `proto` (or its chain) already exposes a usable `animate`. */
79
+ declare function hasNativeAnimate(proto: object): boolean;
80
+ /** Number of live leases on a realm (0 when the adapter is not installed). */
81
+ declare function animationLeaseCount(proto: object): number;
82
+ /**
83
+ * Acquire a lease on the realm owning `target`. Installs the adapter on first
84
+ * lease when `animate` is absent; returns a no-op lease when a native
85
+ * `animate` exists or no realm resolves.
86
+ */
87
+ declare function leaseDomAnimation(target: unknown, timers?: WoodAnimationTimers): WoodAnimationLease;
88
+
89
+ export { type WoodAnimationLease, type WoodAnimationPlayState, type WoodAnimationTimers, type WoodAnimationTiming, WoodTestAnimation, animationLeaseCount, animationRealmOf, hasNativeAnimate, leaseDomAnimation };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Test-only Web Animations adapter for DOM environments that lack
3
+ * `Element.prototype.animate` (happy-dom). Svelte 5 transitions call
4
+ * `element.animate(keyframes, { duration, fill })` twice per transition — a
5
+ * dummy animation lasting the transition's delay, then the real one — and rely
6
+ * on `onfinish`, `cancel()`, `currentTime`, `playState` and `effect`.
7
+ *
8
+ * The adapter is timing-respecting: `onfinish` fires asynchronously after
9
+ * `delay + duration` on the test's timers (real or fake), never immediately —
10
+ * an immediately-finishing shim completes outros early and wedges real
11
+ * round-trips. `cancel()` suppresses completion.
12
+ *
13
+ * Leases are reference-counted per realm (`Element.prototype` of the target's
14
+ * `ownerDocument.defaultView`). The first lease installs the adapter only when
15
+ * `animate` is absent (native/polyfilled `animate` is never touched); the last
16
+ * release cancels every owned animation, clears its timers and restores the
17
+ * original property descriptor. This module is part of the testing entry and
18
+ * must never be reached by production or browser runtime code.
19
+ */
20
+ interface WoodAnimationTimers {
21
+ setTimeout(callback: () => void, ms: number): unknown;
22
+ clearTimeout(handle: unknown): void;
23
+ now(): number;
24
+ }
25
+ interface WoodAnimationLease {
26
+ /** True when this lease installed or shares the adapter (false: native `animate` present or no realm). */
27
+ readonly adapted: boolean;
28
+ /** The `Element.prototype` this lease refers to (null when no realm could be resolved). */
29
+ readonly realm: object | null;
30
+ /** Release the lease; idempotent. */
31
+ release(): void;
32
+ }
33
+ type WoodAnimationPlayState = 'idle' | 'running' | 'finished';
34
+ /** Normalised effect timing derived from the `animate()` options. */
35
+ interface WoodAnimationTiming {
36
+ delay: number;
37
+ duration: number;
38
+ fill: string;
39
+ easing: string;
40
+ }
41
+ /** Minimal `Animation` stand-in honouring the members Svelte transitions use. */
42
+ declare class WoodTestAnimation {
43
+ readonly target: unknown;
44
+ readonly keyframes: unknown;
45
+ readonly timing: WoodAnimationTiming;
46
+ private readonly timers;
47
+ private readonly onSettled;
48
+ onfinish: ((event: unknown) => void) | null;
49
+ oncancel: ((event: unknown) => void) | null;
50
+ effect: unknown;
51
+ playbackRate: number;
52
+ pending: boolean;
53
+ id: string;
54
+ readonly finished: Promise<WoodTestAnimation>;
55
+ private state;
56
+ private startedAt;
57
+ private timer;
58
+ private resolveFinished;
59
+ constructor(target: unknown, keyframes: unknown, timing: WoodAnimationTiming, timers: WoodAnimationTimers, onSettled: (animation: WoodTestAnimation) => void);
60
+ get playState(): WoodAnimationPlayState;
61
+ get startTime(): number | null;
62
+ /** Elapsed time in the active interval (delay counts down from negative), clamped like a finished animation. */
63
+ get currentTime(): number | null;
64
+ set currentTime(value: number | null);
65
+ /** Cancels the animation: no completion, `playState` becomes `idle`, owned timer cleared. */
66
+ cancel(): void;
67
+ /** Completes now (still notifies `onfinish` synchronously, as an explicit caller request). */
68
+ finish(): void;
69
+ play(): void;
70
+ pause(): void;
71
+ /** Called by the owning realm on release; never fires completion. */
72
+ dispose(): void;
73
+ private clearTimer;
74
+ private complete;
75
+ }
76
+ /** Resolves the `Element.prototype` of the realm owning `target` (its `ownerDocument.defaultView`), or null. */
77
+ declare function animationRealmOf(target: unknown): object | null;
78
+ /** True when `proto` (or its chain) already exposes a usable `animate`. */
79
+ declare function hasNativeAnimate(proto: object): boolean;
80
+ /** Number of live leases on a realm (0 when the adapter is not installed). */
81
+ declare function animationLeaseCount(proto: object): number;
82
+ /**
83
+ * Acquire a lease on the realm owning `target`. Installs the adapter on first
84
+ * lease when `animate` is absent; returns a no-op lease when a native
85
+ * `animate` exists or no realm resolves.
86
+ */
87
+ declare function leaseDomAnimation(target: unknown, timers?: WoodAnimationTimers): WoodAnimationLease;
88
+
89
+ export { type WoodAnimationLease, type WoodAnimationPlayState, type WoodAnimationTimers, type WoodAnimationTiming, WoodTestAnimation, animationLeaseCount, animationRealmOf, hasNativeAnimate, leaseDomAnimation };