@noego/wood 0.6.2 → 0.7.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.
- package/README.md +7 -0
- package/dist/browser-test-setup.cjs +29 -0
- package/dist/browser-test-setup.cjs.map +1 -0
- package/dist/browser-test-setup.d.cts +8 -0
- package/dist/browser-test-setup.d.ts +8 -0
- package/dist/browser-test-setup.js +9 -0
- package/dist/browser-test-setup.js.map +1 -0
- package/dist/client/global_polyfill.d.cts +1 -0
- package/dist/client/global_polyfill.d.ts +1 -0
- package/dist/client/index.cjs +7 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +7 -4
- package/dist/client/index.d.ts +7 -4
- package/dist/client/index.js +6 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +97 -24
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +97 -24
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +9 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +9 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +10 -0
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.js +10 -0
- package/dist/config/config_parser.js.map +1 -1
- package/dist/fetch.cjs +38 -0
- package/dist/fetch.cjs.map +1 -0
- package/dist/fetch.d.cts +7 -0
- package/dist/fetch.d.ts +7 -0
- package/dist/fetch.js +12 -0
- package/dist/fetch.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +3 -3
- package/dist/frontend/frontend_page.cjs.map +1 -1
- package/dist/frontend/frontend_page.d.cts +1 -1
- package/dist/frontend/frontend_page.d.ts +1 -1
- package/dist/frontend/frontend_page.js +3 -3
- package/dist/frontend/frontend_page.js.map +1 -1
- package/dist/frontend/frontend_surface.cjs +31 -17
- package/dist/frontend/frontend_surface.cjs.map +1 -1
- package/dist/frontend/frontend_surface.d.cts +12 -2
- package/dist/frontend/frontend_surface.d.ts +12 -2
- package/dist/frontend/frontend_surface.js +37 -17
- package/dist/frontend/frontend_surface.js.map +1 -1
- package/dist/frontend/index.d.cts +2 -0
- package/dist/frontend/index.d.ts +2 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader/index.cjs +19 -2
- package/dist/loader/index.cjs.map +1 -1
- package/dist/loader/index.d.cts +1 -0
- package/dist/loader/index.d.ts +1 -0
- package/dist/loader/index.js +19 -1
- package/dist/loader/index.js.map +1 -1
- package/dist/loader/loader_orchestration.cjs +82 -0
- package/dist/loader/loader_orchestration.cjs.map +1 -0
- package/dist/loader/loader_orchestration.d.cts +66 -0
- package/dist/loader/loader_orchestration.d.ts +66 -0
- package/dist/loader/loader_orchestration.js +51 -0
- package/dist/loader/loader_orchestration.js.map +1 -0
- package/dist/loader/loader_runner.cjs +46 -41
- package/dist/loader/loader_runner.cjs.map +1 -1
- package/dist/loader/loader_runner.d.cts +12 -6
- package/dist/loader/loader_runner.d.ts +12 -6
- package/dist/loader/loader_runner.js +50 -41
- package/dist/loader/loader_runner.js.map +1 -1
- package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
- package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
- package/dist/navigation/index.cjs +15 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +8 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/runtime/get_container.cjs +2 -0
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.js +2 -0
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +5 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime_capabilities.cjs +35 -0
- package/dist/runtime/runtime_capabilities.cjs.map +1 -0
- package/dist/runtime/runtime_capabilities.d.cts +6 -0
- package/dist/runtime/runtime_capabilities.d.ts +6 -0
- package/dist/runtime/runtime_capabilities.js +11 -0
- package/dist/runtime/runtime_capabilities.js.map +1 -0
- package/dist/runtime/runtime_module.cjs +2 -0
- package/dist/runtime/runtime_module.cjs.map +1 -1
- package/dist/runtime/runtime_module.js +2 -0
- package/dist/runtime/runtime_module.js.map +1 -1
- package/dist/test-wood.cjs +150 -0
- package/dist/test-wood.cjs.map +1 -0
- package/dist/test-wood.d.cts +62 -0
- package/dist/test-wood.d.ts +62 -0
- package/dist/test-wood.js +115 -0
- package/dist/test-wood.js.map +1 -0
- package/dist/testing/browser.cjs +92 -163
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +14 -13
- package/dist/testing/browser.d.ts +14 -13
- package/dist/testing/browser.js +88 -162
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +2 -1
- package/dist/testing/create_controller_harness.d.ts +2 -1
- package/dist/testing/create_router_bridge.cjs +19 -15
- package/dist/testing/create_router_bridge.cjs.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +8 -1
- package/dist/testing/create_router_bridge.d.ts +8 -1
- package/dist/testing/create_router_bridge.js +18 -15
- package/dist/testing/create_router_bridge.js.map +1 -1
- package/dist/testing/index.cjs +12 -4
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +11 -5
- package/dist/testing/index.d.ts +11 -5
- package/dist/testing/index.js +9 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/native_browser/browser_pool.cjs +31 -0
- package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
- package/dist/testing/native_browser/browser_pool.d.cts +1 -0
- package/dist/testing/native_browser/browser_pool.d.ts +1 -0
- package/dist/testing/native_browser/browser_pool.js +6 -0
- package/dist/testing/native_browser/browser_pool.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.js +73 -0
- package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
- package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
- package/dist/testing/native_browser/host_transport.cjs +89 -0
- package/dist/testing/native_browser/host_transport.cjs.map +1 -0
- package/dist/testing/native_browser/host_transport.d.cts +44 -0
- package/dist/testing/native_browser/host_transport.d.ts +44 -0
- package/dist/testing/native_browser/host_transport.js +65 -0
- package/dist/testing/native_browser/host_transport.js.map +1 -0
- package/dist/testing/native_browser/native_browser.types.cjs +17 -0
- package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
- package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
- package/dist/testing/native_browser/native_browser.types.js +1 -0
- package/dist/testing/native_browser/native_browser.types.js.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
- package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
- package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
- package/dist/testing/native_browser/native_browser_environment.js +109 -0
- package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
- package/dist/testing/native_browser/playwright_tab.cjs +124 -0
- package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
- package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
- package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
- package/dist/testing/native_browser/playwright_tab.js +100 -0
- package/dist/testing/native_browser/playwright_tab.js.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
- package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
- package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
- package/dist/testing/native_browser/renderer_compilation.js +115 -0
- package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
- package/dist/testing/native_browser/renderer_entry.cjs +148 -0
- package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
- package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
- package/dist/testing/native_browser/renderer_entry.js +114 -0
- package/dist/testing/native_browser/renderer_entry.js.map +1 -0
- package/dist/testing/registration_only_root.cjs +61 -5
- package/dist/testing/registration_only_root.cjs.map +1 -1
- package/dist/testing/registration_only_root.d.cts +25 -4
- package/dist/testing/registration_only_root.d.ts +25 -4
- package/dist/testing/registration_only_root.js +60 -5
- package/dist/testing/registration_only_root.js.map +1 -1
- package/dist/testing/runner_importer.cjs +75 -0
- package/dist/testing/runner_importer.cjs.map +1 -0
- package/dist/testing/runner_importer.d.cts +8 -0
- package/dist/testing/runner_importer.d.ts +8 -0
- package/dist/testing/runner_importer.js +41 -0
- package/dist/testing/runner_importer.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +387 -272
- package/dist/testing/test_wood_config.cjs.map +1 -1
- package/dist/testing/test_wood_config.d.cts +18 -123
- package/dist/testing/test_wood_config.d.ts +18 -123
- package/dist/testing/test_wood_config.js +396 -270
- package/dist/testing/test_wood_config.js.map +1 -1
- package/dist/testing/wood_config_source.cjs +12 -0
- package/dist/testing/wood_config_source.cjs.map +1 -1
- package/dist/testing/wood_config_source.d.cts +10 -0
- package/dist/testing/wood_config_source.d.ts +10 -0
- package/dist/testing/wood_config_source.js +12 -0
- package/dist/testing/wood_config_source.js.map +1 -1
- package/dist/testing/wood_env_renderer.cjs +28 -3
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +12 -1
- package/dist/testing/wood_env_renderer.d.ts +12 -1
- package/dist/testing/wood_env_renderer.js +28 -3
- package/dist/testing/wood_env_renderer.js.map +1 -1
- package/dist/testing/wood_lazy_runtime.cjs +109 -0
- package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
- package/dist/testing/wood_lazy_runtime.d.cts +36 -0
- package/dist/testing/wood_lazy_runtime.d.ts +36 -0
- package/dist/testing/wood_lazy_runtime.js +84 -0
- package/dist/testing/wood_lazy_runtime.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +34 -55
- package/dist/testing/wood_test_selection.cjs.map +1 -1
- package/dist/testing/wood_test_selection.d.cts +18 -24
- package/dist/testing/wood_test_selection.d.ts +18 -24
- package/dist/testing/wood_test_selection.js +30 -54
- package/dist/testing/wood_test_selection.js.map +1 -1
- package/dist/testing-api.cjs +17 -0
- package/dist/testing-api.cjs.map +1 -0
- package/dist/testing-api.d.cts +18 -0
- package/dist/testing-api.d.ts +18 -0
- package/dist/testing-api.js +1 -0
- package/dist/testing-api.js.map +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +4 -0
- package/dist/types/config.d.ts +4 -0
- package/docs/native-browser-testing.md +164 -0
- package/package.json +31 -5
- package/src/components/NavigationShell.browser.test.ts +10 -10
- package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
- package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
- package/src/components/NavigationShell.svelte +50 -24
|
@@ -1 +1 @@
|
|
|
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":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/test_wood_config.ts"],"sourcesContent":["import { FETCH_RUNTIME } from '@noego/runtime/fetch';\nimport { createTestFetch, defineFetchRoutes, type ConfigureFetch, type TestFetchRuntime } from '@noego/testing/fetch';\nimport type { WoodBrowserHost, WoodTestEnvironmentFor, NativeWoodTestEnvironment } from './native_browser/native_browser.types.js';\nimport { disposeControllerLifecycle, guardControllerLifecycleOnce } from '../controller/controller_lifecycle.js';\nimport type { WoodTestApi, WoodTestRawApi } from '../testing-api.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' }, renderer: { path: ['settings.backend-service'] } })\n * .class(BackendServiceLifecycleCoordinator, LifecycleStub)\n * .method(BackendServiceLifecycleCoordinator, 'getStatus', test.once(test.returns(status)))\n * .middleware('auth.session:require_session', test.original())\n * .build();\n *\n * `select` decides what exists (main router, renderer pages, DOM); the\n * canonical singular replacements (`use(testStub())`, `class/function/method/\n * value` on exact declared tokens, `middleware` on source identities, `load`\n * on page/layout companion data-loader sources) decide what is replaced — resolved\n * by the shared `@noego/testing` overlay engine against each owner's declared\n * binding catalog. Without `select()`, every declared owner is selected whole.\n * The legacy plural vocabulary (`classes/functions/values/methods`,\n * `.main()/.renderer()`, `main: true`, `{ operation }`, `pages`) is retained\n * here for existing suites. Controllers, middleware and views are auto-bound\n * from the config; the app composes its backend through the optional\n * `main.testing.compose` hook and mounts pages through `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 { LoadAs, type IContainer } from '@noego/ioc';\nimport { ExecutionContext } from '@noego/ioc';\nimport {\n applyTestBindings,\n createTestExecutables,\n describeTestBindings,\n ENV_REGISTRY,\n resolveTestOverlays,\n testIoc,\n TestIocBuilder,\n TestStub,\n testStub,\n stubEntries,\n} from '@noego/testing';\nimport type {\n ExecutableReplacement,\n LoaderIdentity,\n LoaderReference,\n MethodDescriptor,\n ClassToken, ReplacementClass,\n ReplacementFunction,\n ReplacementOptions,\n ResolvedTestOverlay,\n TestEnvironment,\n TestExecutableOverlays,\n TestOwnerCatalog,\n} from '@noego/testing';\nimport { installWoodRuntime, WoodRouter } from '../runtime/runtime_module.js';\nimport { assertDataLoader, type DataLoader } from '../loader/loader_orchestration.js';\nimport { MiddlewareResolver } from '../middleware/middleware_resolver.js';\nimport type { WoodMiddlewareContext } from '../types/index.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 { createOperationApi, 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 DECLARED_OWNERS,\n resolveSelection,\n selectOperations,\n selectRendererCatalog,\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';\nimport { LazyRuntimeStage, lazyMember } from './wood_lazy_runtime.js';\n\n/** What the app's `main.testing.compose` default export receives. */\nexport interface WoodComposeContext {\n /** Standard per-environment backend push source; hosts subscribe automatically. */\n readonly events: RouterBridgeEvents;\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 /** Typed nested operation API backed by the real selected WoodRouter. */\n readonly api: WoodTestApi;\n /** Object-shaped untyped payload surface for deliberate runtime-validation probes. */\n readonly rawApi: WoodTestRawApi;\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; class tokens retain their instance type. */\n get<T>(token: { readonly prototype: T }): Promise<T>;\n get<T = unknown>(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 ControllerClass = new (...args: never[]) => unknown;\n\ninterface BuilderState {\n readonly configPath: string;\n readonly selection: WoodTestSelection | null;\n readonly browserHost: WoodBrowserHost | null;\n readonly fetchConfigurations: readonly ConfigureFetch[];\n readonly importer: ModuleImporter;\n readonly overlays: TestStub;\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: 'renderer' | '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\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 = NonNullable<ReturnType<typeof selectRendererCatalog>>;\ntype ControllerScope = { dispose(): Promise<void> };\n\n/** Per-build executable seams (`.middleware` on main, `.load` on renderer). */\ninterface AssemblyExecutables {\n readonly main: TestExecutableOverlays;\n readonly renderer: TestExecutableOverlays;\n /** Middleware identities the test replaced (only these get a registry executable). */\n readonly middleware: ReadonlySet<string>;\n}\n\ninterface AssemblyLiveState {\n composed: WoodComposeResult | null;\n env: TestEnvironment | null;\n renderer: RendererTier | null;\n router: WoodRouter | null;\n /** Set once overlays are resolved (before the router is constructed on first use). */\n executables: AssemblyExecutables | null;\n /** Set once the environment's runtime stages exist (after build). */\n runtime: AssemblyRuntime | null;\n /** dispose() was requested; a runtime that has not started must not start. */\n disposed: boolean;\n /** A runtime stage already rolled the environment back; never roll back twice. */\n rolledBack: boolean;\n}\n\n/** The managed main runtime: constructed router + `compose.start()` done. */\ninterface MainRuntime {\n readonly router: WoodRouter;\n}\n\n/**\n * Lazily initialized runtime stages. `main` constructs the managed router\n * and runs `compose.start()`; `renderer` (pages only) builds the renderer\n * tier over the main router. Each initializes exactly once on first use.\n */\ninterface AssemblyRuntime {\n readonly main: LazyRuntimeStage<MainRuntime>;\n readonly renderer: LazyRuntimeStage<RendererTier> | null;\n}\n\ninterface AssemblyInputs {\n readonly network: TestFetchRuntime;\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 events: new RouterBridgeEvents(),\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.env || (!live.router && !live.runtime)) {\n throw new TestWoodError('compose context call() is available from start() onward, not during compose');\n }\n // Inside start() the router already exists (it is constructed before\n // start runs); after build, the first call initializes the runtime.\n const router = live.router ?? (await live.runtime!.main.acquire()).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 network: TestFetchRuntime,\n): () => Promise<void> {\n let closing: Promise<void> | null = null;\n return (): Promise<void> => {\n live.disposed = true;\n return closing ??= cleanupAll('testWood cleanup failed', [\n // An initialization racing this dispose finishes first (it refuses to\n // complete once `disposed` is set) so every resource it created late is\n // released below. A rollback-triggered dispose skips this: its own\n // stage has already finished and waiting would be a self-await.\n ['runtime settle', async () => {\n if (live.rolledBack || !live.runtime) return;\n await live.runtime.main.settled();\n await live.runtime.renderer?.settled();\n }],\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 () => {\n const root = live.env?.root ?? mainRegistrations.actualRoot;\n if (!root.isDisposed()) await ExecutionContext.run(root as never, () => live.composed?.drain?.());\n }],\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 ['external fetch', () => network.dispose()],\n ]);\n };\n}\n\nasync function loadAssemblyControllers(\n inputs: AssemblyInputs,\n): Promise<{\n main: Map<string, ControllerClass>;\n renderer: Map<string, ControllerClass>;\n loaders: Map<string, DataLoader>;\n}> {\n const main = await loadMainControllers(inputs.source, inputs.operations, inputs.state.importer);\n const renderer = inputs.catalog && !inputs.state.browserHost\n ? await loadRendererControllers(inputs.source, inputs.catalog.all(), inputs.state.importer)\n : new Map<string, ControllerClass>();\n const loaders = inputs.catalog && !inputs.state.browserHost\n ? await loadRendererLoaders(inputs.source, inputs.catalog.all(), inputs.state.importer)\n : new Map<string, DataLoader>();\n return { main, renderer, loaders };\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 registerMiddlewareSeams(inputs, configuredRouter);\n },\n });\n}\n\n/**\n * `.middleware(identity, …)`: the production identity keeps its membership,\n * order and inheritance (they come from the operations source); only the\n * executable satisfying it is replaced, through the router's registry seam.\n * The source executable is resolved lazily from `main.middleware`, exactly\n * as production would, and handed to the shared test.* runtime once.\n */\nfunction registerMiddlewareSeams(inputs: AssemblyInputs, configuredRouter: WoodRouter): void {\n const executables = inputs.live.executables;\n if (!executables || !executables.middleware.size) return;\n const sourceResolver = new MiddlewareResolver(inputs.source.mainMiddlewareDir);\n for (const spec of executables.middleware) {\n configuredRouter.registerMiddleware(spec, async (context: WoodMiddlewareContext) => {\n const original = await sourceResolver.resolve(spec);\n return executables.main.replace('middleware', spec, original as ReplacementFunction)(context);\n });\n }\n}\n\n/** Production middleware identities the selected operations run, in source order. */\nfunction selectedMiddleware(operations: ReadonlyMap<string, OperationDef>): string[] {\n const identities = new Set<string>();\n for (const operation of operations.values()) for (const spec of operation.middleware) identities.add(spec);\n return [...identities];\n}\n\n/**\n * Discover and import every companion data loader (`pages/chat.svelte` →\n * `pages/chat.load.ts`) of the selected pages/layouts. Discovery and import\n * happen at build (a missing default loader function fails the build); no\n * loader RUNS until a page opens. The identity is the config-relative\n * source path with export `default` — the `.load` seam identity.\n */\nasync function loadRendererLoaders(\n source: WoodConfigSource,\n views: readonly ViewDef[],\n importer: ModuleImporter,\n): Promise<Map<string, DataLoader>> {\n const loaders = new Map<string, DataLoader>();\n for (const view of views) {\n for (const viewPath of [...view.layouts, view.viewPath]) {\n const reference = source.rendererLoaderReference(viewPath);\n if (!reference || loaders.has(reference.identity)) continue;\n const mod = (await importer(reference.file)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new TestWoodError(`data loader ${reference.identity} (${reference.file}) has no default export`);\n }\n try {\n loaders.set(reference.identity, assertDataLoader(mod.default, `data loader ${reference.identity}`));\n } catch (error) {\n throw new TestWoodError(error instanceof Error ? error.message : String(error));\n }\n }\n }\n return loaders;\n}\n\n/** The native renderer loader identities: every discovered companion loader source. */\nfunction rendererLoaderReferences(loaders: ReadonlyMap<string, DataLoader>): LoaderReference[] {\n return [...loaders.keys()].map((identity) => Object.freeze({ path: identity, export: 'default' }));\n}\n\nasync function configureRendererRegistrations(inputs: AssemblyInputs): Promise<void> {\n const registrations = inputs.rendererRegistrations;\n if (!registrations) return;\n // Bound by the renderer tier on first use (see buildRendererTier); declared\n // here so the shared overlay catalog can resolve replacements for them.\n registrations.root.registerFunction(FETCH_RUNTIME, () => inputs.network, {owned: false, loadAs: LoadAs.Singleton});\n registrations.declareBinding(Navigation, 'singleton');\n registrations.declareBinding(WOOD_FRONTEND_BRIDGE, 'singleton');\n const composePath = inputs.source.hookModule(inputs.source.config.renderer.compose);\n if (composePath) {\n const composeRenderer = await importDefaultExport<(root: IContainer) => void | Promise<void>>(\n inputs.state.importer,\n composePath,\n 'renderer.compose',\n );\n if (typeof composeRenderer !== 'function') {\n throw new TestWoodError(`renderer.compose default export must be a function (${composePath})`);\n }\n await ExecutionContext.run(registrations.root as never, () => composeRenderer(registrations.root));\n }\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\ninterface AssemblyIoc {\n readonly main: TestIocBuilder;\n readonly mainBorrowed: readonly unknown[];\n readonly renderer: TestIocBuilder;\n readonly rendererBorrowed: readonly unknown[];\n readonly overlays: readonly ResolvedTestOverlay[];\n readonly executables: AssemblyExecutables;\n}\n\n/**\n * Legacy plural calls are routed by owner token sets (unchanged); the\n * canonical singular replacements are resolved by the shared overlay engine\n * against each owner's DECLARED binding catalog — production registrations\n * recorded on the root, legacy explicit composition, auto-bound controllers,\n * the selected middleware identities and the page/layout loader identities.\n */\nfunction buildAssemblyIoc(\n inputs: AssemblyInputs,\n controllers: {\n main: ReadonlyMap<string, ControllerClass>;\n renderer: ReadonlyMap<string, ControllerClass>;\n loaders: ReadonlyMap<string, DataLoader>;\n },\n): AssemblyIoc {\n const catalogs: TestOwnerCatalog[] = [{\n owner: 'main',\n bindings: describeTestBindings(testIoc().use(inputs.mainRegistrations.module()), [...controllers.main.values()]),\n middleware: selectedMiddleware(inputs.operations),\n loaders: [],\n }];\n if (inputs.rendererRegistrations && inputs.catalog) {\n catalogs.push({\n owner: 'renderer',\n bindings: describeTestBindings(testIoc().use(inputs.rendererRegistrations.module()), [...controllers.renderer.values()]),\n middleware: [],\n loaders: rendererLoaderReferences(controllers.loaders),\n });\n }\n const overlays = resolveTestOverlays(inputs.state.overlays, catalogs);\n const mainPlan = applyTestBindings(testIoc(), overlays, 'main');\n const rendererPlan = applyTestBindings(testIoc(), overlays, 'renderer');\n return {\n main: mainPlan.builder,\n mainBorrowed: mainPlan.buildOptions.borrowedTokens ?? [],\n renderer: rendererPlan.builder,\n rendererBorrowed: rendererPlan.buildOptions.borrowedTokens ?? [],\n overlays,\n executables: {\n main: createTestExecutables(overlays, 'main'),\n renderer: createTestExecutables(overlays, 'renderer'),\n middleware: new Set(overlays\n .filter((overlay) => overlay.owner === 'main' && overlay.entry.kind === 'middleware')\n .map((overlay) => overlay.entry.target as string)),\n },\n };\n}\n\n/**\n * Roll the whole environment back after a failed first use: every resource a\n * partially initialized runtime created is released, and both the primary\n * error and any cleanup failures are preserved. Runs at most once.\n */\nfunction createRuntimeRollback(inputs: AssemblyInputs): (error: unknown) => Promise<never> {\n return (error) => {\n // Already rolled back, or dispose() is in flight / done: that disposer\n // owns the cleanup (it waits for this attempt), so never re-enter it here.\n if (inputs.live.rolledBack || inputs.live.disposed) return Promise.reject(error);\n inputs.live.rolledBack = true;\n return rollbackEnvironment(error, inputs.dispose, 'testWood runtime initialization failed and rollback failed');\n };\n}\n\nfunction assertRuntimeMayStart(inputs: AssemblyInputs, stage: string): void {\n if (inputs.live.disposed) {\n throw new TestWoodError(`this testWood environment was disposed before its ${stage} runtime was used`);\n }\n}\n\n/**\n * Re-checked after every await of a first initialization: dispose() may have\n * been requested meanwhile. The disposer waits for this attempt, so failing\n * here hands every late resource to it instead of exposing a disposed root.\n */\nfunction assertRuntimeMayContinue(inputs: AssemblyInputs, stage: string): void {\n if (inputs.live.disposed) {\n throw new TestWoodError(`this testWood environment was disposed while its ${stage} runtime was initializing`);\n }\n}\n\n/** First use: construct the managed router, then run `compose.start()` on the seamed graph. */\nasync function initializeMainRuntime(inputs: AssemblyInputs): Promise<MainRuntime> {\n assertRuntimeMayStart(inputs, 'main');\n const root = inputs.live.env!.root;\n const router = await Promise.resolve(\n ExecutionContext.run(root as never, () => root.get(WoodRouter)),\n ) as WoodRouter;\n inputs.live.router = router;\n assertRuntimeMayContinue(inputs, 'main');\n await ExecutionContext.run(root as never, () => inputs.live.composed?.start?.());\n assertRuntimeMayContinue(inputs, 'main');\n assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);\n return { router };\n}\n\ninterface RendererRuntimeInputs {\n readonly ioc: TestIocBuilder;\n readonly borrowedTokens: readonly unknown[];\n readonly controllerClasses: ReadonlyMap<string, ControllerClass>;\n readonly events: RouterBridgeEvents;\n readonly loaders: TestExecutableOverlays;\n /** Imported companion data loaders by `.load` identity (none has run). */\n readonly loaderModules: ReadonlyMap<string, DataLoader>;\n}\n\n/** First renderer use: the main runtime, then the renderer tier over its router. */\nasync function initializeRendererRuntime(\n inputs: AssemblyInputs,\n main: LazyRuntimeStage<MainRuntime>,\n renderer: RendererRuntimeInputs,\n): Promise<RendererTier> {\n const { router } = await main.acquire();\n assertRuntimeMayStart(inputs, 'renderer');\n const composed = inputs.live.composed;\n const configurePageScope = composed?.configurePageScope?.bind(composed);\n const hooks: WoodRendererHooks = {\n events: renderer.events,\n ...(configurePageScope ? { configurePageScope } : {}),\n };\n const tier = await buildRendererTier({\n source: inputs.source,\n router,\n catalog: inputs.catalog!,\n operations: inputs.operations,\n importer: inputs.state.importer,\n hooks,\n dom: null,\n ioc: renderer.ioc,\n borrowedTokens: renderer.borrowedTokens,\n registrations: inputs.rendererRegistrations!,\n controllerClasses: renderer.controllerClasses,\n loaders: renderer.loaders,\n loaderModules: renderer.loaderModules,\n });\n // Hand the tier to the disposer before refusing: a dispose() that raced\n // this build releases it through the regular 'renderer' cleanup step.\n inputs.live.renderer = tier;\n assertRuntimeMayContinue(inputs, 'renderer');\n return tier;\n}\n\nfunction createAssemblyRuntime(\n inputs: AssemblyInputs,\n renderer: RendererRuntimeInputs | null,\n): AssemblyRuntime {\n const rollback = createRuntimeRollback(inputs);\n const main = new LazyRuntimeStage<MainRuntime>(() => initializeMainRuntime(inputs), rollback);\n const rendererStage = renderer\n ? new LazyRuntimeStage<RendererTier>(() => initializeRendererRuntime(inputs, main, renderer), rollback)\n : null;\n return { main, renderer: rendererStage };\n}\n\n/**\n * Build: read the source, record registrations (compose, runtime module,\n * renderer hooks, controllers) and install the test overlays on the main root.\n * Nothing is resolved or started here — the managed router, `compose.start()`\n * and the renderer tier initialize on first explicit use.\n */\nasync function materializeEnvironment(inputs: AssemblyInputs): Promise<TestWoodEnvironment | NativeWoodTestEnvironment<WoodBrowserHost>> {\n inputs.mainRegistrations.root.registerFunction(FETCH_RUNTIME, () => inputs.network, {owned: false, loadAs: LoadAs.Singleton});\n const controllers = await loadAssemblyControllers(inputs);\n inputs.live.composed = await runCompose(inputs.mainRegistrations.root, inputs.source, inputs.context);\n assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);\n installAssemblyRuntime(inputs, controllers.main);\n await configureRendererRegistrations(inputs);\n registerControllersBeforeOverlays(inputs, controllers.main, controllers.renderer);\n const ioc = buildAssemblyIoc(inputs, controllers);\n inputs.live.executables = ioc.executables;\n\n inputs.live.env = await buildRecordedEnvironment(ioc.main, inputs.mainRegistrations, 'main', ioc.mainBorrowed);\n const root = inputs.live.env.root;\n\n const renderer: RendererRuntimeInputs | null = inputs.catalog && inputs.rendererRegistrations\n ? {\n ioc: ioc.renderer,\n borrowedTokens: ioc.rendererBorrowed,\n controllerClasses: controllers.renderer,\n events: inputs.live.composed?.events ?? inputs.context.events,\n loaders: ioc.executables.renderer,\n loaderModules: controllers.loaders,\n }\n : null;\n const runtime = createAssemblyRuntime(inputs, renderer);\n inputs.live.runtime = runtime;\n\n const environment = environmentSurface({\n root,\n network: inputs.network,\n env: inputs.live.env,\n operations: inputs.operations,\n runtime,\n allOperations: inputs.source.operations,\n events: renderer?.events ?? inputs.live.composed?.events ?? inputs.context.events,\n dom: false,\n controllerScopes: inputs.controllerScopes,\n executables: ioc.executables,\n dispose: inputs.dispose,\n });\n if (inputs.state.browserHost) {\n const { createNativeBrowserEnvironment } = await import('./native_browser/native_browser_environment.js');\n return createNativeBrowserEnvironment({\n source: inputs.source,\n views: inputs.catalog!.all(),\n environment,\n network: inputs.network,\n events: () => inputs.live.composed?.events ?? inputs.context.events,\n }, inputs.state.browserHost);\n }\n return environment;\n}\n\nasync function rollbackEnvironment(\n error: unknown,\n dispose: () => Promise<void>,\n message = 'testWood build failed and rollback failed',\n): 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(message, 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 selected: ResolvedWoodSelection | null,\n): Promise<TestWoodEnvironment | NativeWoodTestEnvironment<WoodBrowserHost>> {\n const source = loadWoodConfigSource(state.configPath);\n // No selection: every owner the config declares, whole ({}). A Wood\n // config declares both `main` and `renderer` sections.\n let selection = selected ?? resolveSelection(\n Object.fromEntries(DECLARED_OWNERS.filter((owner) => source.config[owner]).map((owner) => [owner, {}])),\n );\n // An omitted main owner exposes no operations; a selected renderer still gets\n // a main root for dependencies but does not widen the main operation aperture.\n const operations = selectOperations(source.operations, selection.main);\n const catalog = selectRendererCatalog(source.views, selection);\n if (state.browserHost && !catalog) {\n throw new TestWoodError('browser() requires a selected renderer owner; add renderer to select()');\n }\n if (state.browserHost && stubEntries(state.overlays).some(entry => entry.kind === 'load' || entry.owner === 'renderer')) {\n throw new TestWoodError('Native browser renderer replacements are not supported yet: renderer code executes in its own realm. Keep main-process seams, or use the existing in-process renderer test environment for renderer .class/.method/.value/.load seams.');\n }\n const network = createTestFetch(state.fetchConfigurations.flatMap(defineFetchRoutes));\n const registryBefore = globalRegistrySize();\n const mainRegistrations = new RegistrationOnlyRoot('main.testing.compose', source.configPath);\n const rendererRegistrations = catalog && !state.browserHost\n ? new RegistrationOnlyRoot('configureRendererRoot', source.configPath)\n : null;\n const controllerScopes: ControllerScope[] = [];\n const live: AssemblyLiveState = {\n composed: null,\n env: null,\n renderer: null,\n router: null,\n executables: null,\n runtime: null,\n disposed: false,\n rolledBack: false,\n };\n const context = createComposeContext(source, state, selection, operations, live);\n const dispose = createAssemblyDisposer(\n live,\n mainRegistrations,\n rendererRegistrations,\n controllerScopes,\n network,\n );\n const inputs: AssemblyInputs = {\n network,\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 network: TestFetchRuntime;\n root: IContainer;\n env: TestEnvironment;\n operations: Map<string, OperationDef>;\n runtime: AssemblyRuntime;\n /** Every production operation, used to keep `api` object shape stable across selections. */\n allOperations: ReadonlyMap<string, OperationDef>;\n /** Backend push emitter (pages only); available from build, before any renderer use. */\n events: RouterBridgeEvents | null;\n dom: boolean;\n controllerScopes: Array<{ dispose(): Promise<void> }>;\n executables: AssemblyExecutables;\n dispose(): Promise<void>;\n}\n\nconst OPEN_FIRST = 'await open(page) (or another renderer call) first';\n\nfunction environmentSurface(parts: SurfaceParts): TestWoodEnvironment {\n const { root, env, operations, allOperations, runtime, events, dom: domSelected, controllerScopes, executables, dispose } = parts;\n const pages = (): LazyRuntimeStage<RendererTier> => runtime.renderer ?? tierMissing('pages', 'renderer');\n const dom = async (): Promise<WoodDomDriver> => {\n if (!domSelected) tierMissing('dom', 'dom driver');\n return (await pages().acquire()).dom!;\n };\n\n // Stable lazy members: async use initializes the runtime; synchronous\n // members are served once it is live and fail with guidance before that.\n const router = lazyMember<WoodRouter>(\n 'router',\n runtime.main.map((main) => main.router),\n ['dispatch'],\n 'await call()/get()/controller() first',\n );\n const frontend = runtime.renderer\n ? lazyMember<FrontendApplicationSurface>(\n 'frontend',\n runtime.renderer.map((tier) => tier.frontend),\n ['open', 'act', 'settle', 'dispose'],\n OPEN_FIRST,\n )\n : null;\n const rendererRoot = runtime.renderer\n ? lazyMember<IContainer>(\n 'rendererRoot',\n runtime.renderer.map((tier) => tier.rendererRoot),\n ['get', 'dispose'],\n OPEN_FIRST,\n )\n : null;\n\n const dispatchOperation = async (operation: string, input?: unknown): Promise<unknown> => {\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 const { router: live } = await runtime.main.acquire();\n return await ExecutionContext.run(root as never, () => live.dispatch(operation, input));\n };\n const api = createOperationApi(allOperations.keys(), dispatchOperation) as WoodTestApi;\n\n return {\n root,\n router,\n operations,\n api,\n rawApi: api as WoodTestRawApi,\n call: dispatchOperation as TestWoodEnvironment['call'],\n controller: async <T>(controller: new (...args: never[]) => T): Promise<T> => {\n await runtime.main.acquire();\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> => {\n await runtime.main.acquire();\n return (await ExecutionContext.run(root as never, () => root.get(token))) as T;\n },\n\n get frontend() { return frontend ?? tierMissing('pages', 'renderer'); },\n get events() { return events ?? tierMissing('pages', 'renderer'); },\n get rendererRoot() { return rendererRoot ?? tierMissing('pages', 'renderer'); },\n open: async (page, params, query) => (await pages().acquire()).open(page, params, query),\n navigate: async (page, params, query) => (await pages().acquire()).navigate(page, params, query),\n current: () => pages().current?.current() ?? null,\n settle: async () => {\n await parts.network.settle();\n // Finalization applies to main-only roots too. Waiting must never start\n // an unused product or renderer merely to discover that it is idle.\n if (runtime.main.requested) await (await runtime.main.acquire()).router.drain();\n if (runtime.renderer?.requested) await (await runtime.renderer.acquire()).settle();\n },\n\n get target() {\n if (!domSelected) tierMissing('dom', 'dom driver');\n const tier = pages().current;\n if (!tier) throw new TestWoodError('no page is mounted; open(page) first');\n return tier.dom!.target;\n },\n click: async (selector) => (await dom()).click(selector),\n type: async (selector, text) => (await dom()).type(selector, text),\n submit: async (selector) => (await dom()).submit(selector),\n text: async (selector) => (await dom()).text(selector),\n exists: async (selector) => (await 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 await executables.main.verify().catch((error) => failures.push({ owner: 'main middleware', error }));\n const renderer = runtime.renderer?.current ?? null;\n if (renderer) {\n await renderer.verify().catch((error) => failures.push({ owner: 'renderer', error }));\n }\n await executables.renderer.verify().catch((error) => failures.push({ owner: 'renderer loaders', error }));\n if (failures.length === 1) throw failures[0]!.error;\n if (failures.length) {\n throw new TestWoodError(\n 'testWood verification failed in several owners:\\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<H extends WoodBrowserHost | null = null> {\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 browserHost: null,\n fetchConfigurations: [],\n importer: defaultImporter,\n overlays: testStub(),\n });\n }\n\n fetch(configure: ConfigureFetch): TestWoodBuilder<H> {\n if (typeof configure !== 'function') throw new TypeError('fetch() requires a route configuration callback');\n return this.derive({fetchConfigurations: Object.freeze([...this.state.fetchConfigurations, configure])});\n }\n\n browser(): TestWoodBuilder<'playwright'>;\n browser(host: 'playwright'): TestWoodBuilder<'playwright'>;\n browser(host: 'happy_dom'): TestWoodBuilder<'happy_dom'>;\n browser(host: WoodBrowserHost = 'playwright'): TestWoodBuilder<WoodBrowserHost> {\n if (host !== 'playwright' && host !== 'happy_dom') throw new TestWoodError('browser() accepts playwright or happy_dom');\n return new TestWoodBuilder<WoodBrowserHost>({...this.state, browserHost: host});\n }\n\n /** Declare selected tiers, snapshotting the source selector at call time. */\n select(selection: WoodTestSelection): TestWoodBuilder<H> {\n const resolved = resolveSelection(selection);\n return this.derive({ selection: Object.freeze({\n ...(resolved.main !== null ? { main: resolved.main } : {}),\n ...(resolved.renderer !== null ? { renderer: resolved.renderer } : {}),\n }) });\n }\n\n /** Route hook/controller imports through a caller-owned importer (escape hatch). */\n importer(importer: ModuleImporter): TestWoodBuilder<H> {\n if (typeof importer !== 'function') throw new TestWoodError('importer() expects a function');\n return this.derive({ importer });\n }\n\n /** Compose a reusable canonical testStub() replacement description. */\n use(preset: TestStub): TestWoodBuilder<H> {\n if (!(preset instanceof TestStub)) throw new TestWoodError('use() requires testStub()');\n return this.derive({ overlays: this.state.overlays.use(preset) });\n }\n\n /* -- canonical singular replacements (shared @noego/testing engine) ------ */\n\n /** Replace the class bound to an exact declared token; the declared lifetime is kept. */\n class<T>(target: ClassToken<T>, replacement: ReplacementClass<T>, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.class(target, replacement, options) });\n }\n\n /** Replace a declared factory/provider (dependencies and lifetime kept) or a callable value. */\n function(target: unknown, replacement: ReplacementFunction, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.function(target, replacement, options) });\n }\n\n /** A test.* method seam on the FINAL binding of an exact declared token. */\n method(target: unknown, method: string, replacement: MethodDescriptor, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.method(target, method, replacement, options) });\n }\n\n /** Provide a caller-owned value for an exact declared token (never disposed by the app). */\n value(target: unknown, replacement: unknown, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.value(target, replacement, options) });\n }\n\n /**\n * Replace the executable satisfying a production middleware identity\n * (`file.path:selector` as written in the operations source). Membership,\n * order and inheritance always come from the source.\n */\n middleware(identity: string, replacement: ExecutableReplacement, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.middleware(identity, replacement, options) });\n }\n\n /**\n * Replace the DATA LOADER behind a selected page/layout: the companion\n * `.load` source of a view (`pages/chat.svelte` → `pages/chat.load.ts`).\n * The identity is the config-relative source path (`ui/pages/chat.load.ts`),\n * export `default`; the executable receives the loader `RequestData`\n * (`url` = page identity, `params`, `query`) and yields the data the\n * controller's `initialize({ params, query, data })` receives. A source\n * with no companion loader is not a declared identity (build error).\n * Controllers are replaced with `.class`, never here.\n */\n load(identity: LoaderIdentity, replacement: ExecutableReplacement, options?: ReplacementOptions): TestWoodBuilder<H> {\n return this.derive({ overlays: this.state.overlays.load(identity, replacement, options) });\n }\n\n /** Build; without `select()`, every owner the config declares is selected whole. */\n async build(): Promise<WoodTestEnvironmentFor<H>> {\n return await assembleEnvironment(this.state, this.state.selection ? resolveSelection(this.state.selection) : null) as WoodTestEnvironmentFor<H>;\n }\n\n private derive(patch: Partial<BuilderState>): TestWoodBuilder<H> {\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":[]}
|
|
@@ -43,6 +43,7 @@ var import_config_parser = require("../config/config_parser.cjs");
|
|
|
43
43
|
var import_operations_parser = require("../parser/operations_parser.cjs");
|
|
44
44
|
var import_views_parser = require("../parser/views_parser.cjs");
|
|
45
45
|
var import_controller_registration_generator = require("../codegen/controller_registration_generator.cjs");
|
|
46
|
+
var import_loader_orchestration = require("../loader/loader_orchestration.cjs");
|
|
46
47
|
var import_test_wood = require("./test_wood.cjs");
|
|
47
48
|
const defaultImporter = (absolutePath) => import(
|
|
48
49
|
/* @vite-ignore */
|
|
@@ -126,6 +127,17 @@ function loadWoodConfigSource(configPath) {
|
|
|
126
127
|
if (import_node_fs.default.existsSync(file)) return file;
|
|
127
128
|
throw new import_test_wood.TestWoodError(`renderer controller "${reference}" not found at ${file}`);
|
|
128
129
|
},
|
|
130
|
+
rendererLoaderReference: (viewPath) => {
|
|
131
|
+
const present = (0, import_loader_orchestration.loaderCompanionCandidates)(viewPath).map((candidate) => import_node_path.default.resolve(rendererComponentDir, candidate)).filter((file2) => import_node_fs.default.existsSync(file2));
|
|
132
|
+
if (present.length > 1) {
|
|
133
|
+
throw new import_test_wood.TestWoodError(
|
|
134
|
+
`ambiguous data loader for "${viewPath}": ${present.join(", ")}; keep exactly one companion`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!present.length) return null;
|
|
138
|
+
const file = present[0];
|
|
139
|
+
return { identity: import_node_path.default.relative(configDir, file).split(import_node_path.default.sep).join("/"), file };
|
|
140
|
+
},
|
|
129
141
|
hookModule: (relative) => relative ? fromConfig(relative) : null
|
|
130
142
|
};
|
|
131
143
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/wood_config_source.ts"],"sourcesContent":["/**\n * Config-driven test source for `testWood(configPath)`.\n *\n * Everything a test environment binds is read from the production\n * `wood.config.yml`: operations from `main.openapi` (a single document or a\n * stitch manifest), views from `renderer.openapi`, controller files from\n * `main.controllers` (same candidate rules as codegen) and renderer\n * controllers relative to `renderer.componentDir`. Tests name nothing by\n * hand — a config path is the whole source.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport yaml from 'js-yaml';\nimport { globSync } from 'glob';\nimport { parseConfig } from '../config/config_parser.cjs';\nimport type { WoodConfig } from '../types/config.cjs';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { parseViews } from '../parser/views_parser.cjs';\nimport type { OperationDef, ViewDef } from '../types/index.cjs';\nimport {\n controllerFileCandidates,\n resolveControllerFile,\n} from '../codegen/controller_registration_generator.cjs';\nimport { TestWoodError } from './test_wood.cjs';\n\n/** Imports a module by absolute path; the default keeps the caller's module registry. */\nexport type ModuleImporter = (absolutePath: string) => Promise<unknown>;\n\nexport const defaultImporter: ModuleImporter = (absolutePath) =>\n import(/* @vite-ignore */ pathToFileURL(absolutePath).href);\n\nexport interface WoodConfigSource {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n /** Every production operation, stitch order, first declaration wins. */\n readonly operations: Map<string, OperationDef>;\n readonly mainControllersDir: string;\n readonly mainMiddlewareDir: string;\n readonly rendererComponentDir: string;\n /** Every production view (parsed on first use — main-only builds never read them). */\n views(): ViewDef[];\n /** Absolute file for a main controller identity, or a build error naming the candidates. */\n mainControllerFile(identity: string): string;\n /** Absolute file for a renderer controller reference (views `controller:`). */\n rendererControllerFile(reference: string): string;\n /** Absolute path of an optional config-relative hook module, if declared. */\n hookModule(relative: string | undefined): string | null;\n}\n\ninterface StitchManifest {\n stitch?: unknown;\n}\n\nconst GLOB_MAGIC = /[*?[\\]{}]/;\n\n/**\n * Expand an openapi reference into the ordered list of YAML files it names:\n * a stitch manifest (`stitch: [...]`, entries relative to the manifest, globs\n * allowed) or a single document.\n */\nexport function expandOpenapiFiles(absolutePath: string): string[] {\n const doc = yaml.load(fs.readFileSync(absolutePath, 'utf-8')) as StitchManifest | null;\n if (!doc || typeof doc !== 'object' || !('stitch' in doc)) return [absolutePath];\n if (!Array.isArray(doc.stitch)) {\n throw new TestWoodError(`${absolutePath}: \"stitch\" must be a list of YAML files or globs`);\n }\n const manifestDir = path.dirname(absolutePath);\n const files: string[] = [];\n for (const entry of doc.stitch) {\n if (typeof entry !== 'string' || !entry.length) {\n throw new TestWoodError(`${absolutePath}: every \"stitch\" entry must be a non-empty string`);\n }\n files.push(...expandStitchEntry(entry, manifestDir));\n }\n return files;\n}\n\nfunction expandStitchEntry(entry: string, manifestDir: string): string[] {\n if (!GLOB_MAGIC.test(entry)) return [path.resolve(manifestDir, entry)];\n return globSync(entry, { cwd: manifestDir, absolute: true, nodir: true }).sort();\n}\n\nfunction loadOperations(files: string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n for (const file of files) {\n for (const [channel, operation] of parseOperations(fs.readFileSync(file, 'utf-8'))) {\n if (!merged.has(channel)) merged.set(channel, operation);\n }\n }\n return merged;\n}\n\n/** Views documents only; a stitched directory may hold other YAML kinds. */\nfunction loadViews(files: string[]): ViewDef[] {\n const views: ViewDef[] = [];\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf-8');\n if (!/^view:/m.test(content)) continue;\n views.push(...parseViews(content));\n }\n return views;\n}\n\nexport function loadWoodConfigSource(configPath: string): WoodConfigSource {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n const resolvedConfigPath = path.resolve(configPath);\n if (!fs.existsSync(resolvedConfigPath)) {\n throw new TestWoodError(`testWood(configPath): config file not found: ${resolvedConfigPath}`);\n }\n const config = parseConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n const fromConfig = (relative: string): string => path.resolve(configDir, relative);\n\n const mainControllersDir = fromConfig(config.main.controllers);\n const rendererComponentDir = fromConfig(config.renderer.componentDir);\n let views: ViewDef[] | null = null;\n\n return {\n configPath: resolvedConfigPath,\n configDir,\n config,\n operations: loadOperations(expandOpenapiFiles(fromConfig(config.main.openapi))),\n mainControllersDir,\n mainMiddlewareDir: fromConfig(config.main.middleware),\n rendererComponentDir,\n views: () => {\n views ??= loadViews(expandOpenapiFiles(fromConfig(config.renderer.openapi)));\n return views;\n },\n mainControllerFile: (identity) => {\n const file = resolveControllerFile(identity, mainControllersDir);\n if (file) return file;\n throw new TestWoodError(\n `no controller file for identity \"${identity}\" under ${mainControllersDir}; looked for:\\n ` +\n controllerFileCandidates(identity, mainControllersDir).join('\\n '),\n );\n },\n rendererControllerFile: (reference) => {\n const file = path.resolve(rendererComponentDir, reference);\n if (fs.existsSync(file)) return file;\n throw new TestWoodError(`renderer controller \"${reference}\" not found at ${file}`);\n },\n hookModule: (relative) => (relative ? fromConfig(relative) : null),\n };\n}\n\n/** Import a hook module declared in the config and return its default export. */\nexport async function importDefaultExport<T>(\n importer: ModuleImporter,\n absolutePath: string,\n what: string,\n): Promise<T> {\n const mod = (await importer(absolutePath)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new TestWoodError(`${what} module ${absolutePath} has no default export`);\n }\n return mod.default as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,qBAAe;AACf,uBAAiB;AACjB,sBAA8B;AAC9B,qBAAiB;AACjB,kBAAyB;AACzB,2BAA4B;AAE5B,+BAAgC;AAChC,0BAA2B;AAE3B,+CAGO;AACP,uBAA8B;AAKvB,MAAM,kBAAkC,CAAC,iBAC9C;AAAA;AAAA,MAA0B,+BAAc,YAAY,EAAE;AAAA;AAyBxD,MAAM,aAAa;AAOZ,SAAS,mBAAmB,cAAgC;AACjE,QAAM,MAAM,eAAAA,QAAK,KAAK,eAAAC,QAAG,aAAa,cAAc,OAAO,CAAC;AAC5D,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,EAAE,YAAY,KAAM,QAAO,CAAC,YAAY;AAC/E,MAAI,CAAC,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC9B,UAAM,IAAI,+BAAc,GAAG,YAAY,kDAAkD;AAAA,EAC3F;AACA,QAAM,cAAc,iBAAAC,QAAK,QAAQ,YAAY;AAC7C,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,IAAI,QAAQ;AAC9B,QAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ;AAC9C,YAAM,IAAI,+BAAc,GAAG,YAAY,mDAAmD;AAAA,IAC5F;AACA,UAAM,KAAK,GAAG,kBAAkB,OAAO,WAAW,CAAC;AAAA,EACrD;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAe,aAA+B;AACvE,MAAI,CAAC,WAAW,KAAK,KAAK,EAAG,QAAO,CAAC,iBAAAA,QAAK,QAAQ,aAAa,KAAK,CAAC;AACrE,aAAO,sBAAS,OAAO,EAAE,KAAK,aAAa,UAAU,MAAM,OAAO,KAAK,CAAC,EAAE,KAAK;AACjF;AAEA,SAAS,eAAe,OAA4C;AAClE,QAAM,SAAS,oBAAI,IAA0B;AAC7C,aAAW,QAAQ,OAAO;AACxB,eAAW,CAAC,SAAS,SAAS,SAAK,0CAAgB,eAAAD,QAAG,aAAa,MAAM,OAAO,CAAC,GAAG;AAClF,UAAI,CAAC,OAAO,IAAI,OAAO,EAAG,QAAO,IAAI,SAAS,SAAS;AAAA,IACzD;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,UAAU,OAA4B;AAC7C,QAAM,QAAmB,CAAC;AAC1B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,eAAAA,QAAG,aAAa,MAAM,OAAO;AAC7C,QAAI,CAAC,UAAU,KAAK,OAAO,EAAG;AAC9B,UAAM,KAAK,OAAG,gCAAW,OAAO,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,YAAsC;AACzE,MAAI,OAAO,eAAe,YAAY,CAAC,WAAW,QAAQ;AACxD,UAAM,IAAI,+BAAc,6DAA6D;AAAA,EACvF;AACA,QAAM,qBAAqB,iBAAAC,QAAK,QAAQ,UAAU;AAClD,MAAI,CAAC,eAAAD,QAAG,WAAW,kBAAkB,GAAG;AACtC,UAAM,IAAI,+BAAc,gDAAgD,kBAAkB,EAAE;AAAA,EAC9F;AACA,QAAM,aAAS,kCAAY,kBAAkB;AAC7C,QAAM,YAAY,iBAAAC,QAAK,QAAQ,kBAAkB;AACjD,QAAM,aAAa,CAAC,aAA6B,iBAAAA,QAAK,QAAQ,WAAW,QAAQ;AAEjF,QAAM,qBAAqB,WAAW,OAAO,KAAK,WAAW;AAC7D,QAAM,uBAAuB,WAAW,OAAO,SAAS,YAAY;AACpE,MAAI,QAA0B;AAE9B,SAAO;AAAA,IACL,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY,eAAe,mBAAmB,WAAW,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA,IAC9E;AAAA,IACA,mBAAmB,WAAW,OAAO,KAAK,UAAU;AAAA,IACpD;AAAA,IACA,OAAO,MAAM;AACX,wBAAU,UAAU,mBAAmB,WAAW,OAAO,SAAS,OAAO,CAAC,CAAC;AAC3E,aAAO;AAAA,IACT;AAAA,IACA,oBAAoB,CAAC,aAAa;AAChC,YAAM,WAAO,gEAAsB,UAAU,kBAAkB;AAC/D,UAAI,KAAM,QAAO;AACjB,YAAM,IAAI;AAAA,QACR,oCAAoC,QAAQ,WAAW,kBAAkB;AAAA,UACzE,mEAAyB,UAAU,kBAAkB,EAAE,KAAK,MAAM;AAAA,MACpE;AAAA,IACF;AAAA,IACA,wBAAwB,CAAC,cAAc;AACrC,YAAM,OAAO,iBAAAA,QAAK,QAAQ,sBAAsB,SAAS;AACzD,UAAI,eAAAD,QAAG,WAAW,IAAI,EAAG,QAAO;AAChC,YAAM,IAAI,+BAAc,wBAAwB,SAAS,kBAAkB,IAAI,EAAE;AAAA,IACnF;AAAA,IACA,YAAY,CAAC,aAAc,WAAW,WAAW,QAAQ,IAAI;AAAA,EAC/D;AACF;AAGA,eAAsB,oBACpB,UACA,cACA,MACY;AACZ,QAAM,MAAO,MAAM,SAAS,YAAY;AACxC,MAAI,IAAI,YAAY,QAAW;AAC7B,UAAM,IAAI,+BAAc,GAAG,IAAI,WAAW,YAAY,wBAAwB;AAAA,EAChF;AACA,SAAO,IAAI;AACb;","names":["yaml","fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/wood_config_source.ts"],"sourcesContent":["/**\n * Config-driven test source for `testWood(configPath)`.\n *\n * Everything a test environment binds is read from the production\n * `wood.config.yml`: operations from `main.openapi` (a single document or a\n * stitch manifest), views from `renderer.openapi`, controller files from\n * `main.controllers` (same candidate rules as codegen) and renderer\n * controllers relative to `renderer.componentDir`. Tests name nothing by\n * hand — a config path is the whole source.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport yaml from 'js-yaml';\nimport { globSync } from 'glob';\nimport { parseConfig } from '../config/config_parser.cjs';\nimport type { WoodConfig } from '../types/config.cjs';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { parseViews } from '../parser/views_parser.cjs';\nimport type { OperationDef, ViewDef } from '../types/index.cjs';\nimport {\n controllerFileCandidates,\n resolveControllerFile,\n} from '../codegen/controller_registration_generator.cjs';\nimport { loaderCompanionCandidates } from '../loader/loader_orchestration.cjs';\nimport { TestWoodError } from './test_wood.cjs';\n\n/** Imports a module by absolute path; the default keeps the caller's module registry. */\nexport type ModuleImporter = (absolutePath: string) => Promise<unknown>;\n\nexport const defaultImporter: ModuleImporter = (absolutePath) =>\n import(/* @vite-ignore */ pathToFileURL(absolutePath).href);\n\nexport interface WoodConfigSource {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n /** Every production operation, stitch order, first declaration wins. */\n readonly operations: Map<string, OperationDef>;\n readonly mainControllersDir: string;\n readonly mainMiddlewareDir: string;\n readonly rendererComponentDir: string;\n /** Every production view (parsed on first use — main-only builds never read them). */\n views(): ViewDef[];\n /** Absolute file for a main controller identity, or a build error naming the candidates. */\n mainControllerFile(identity: string): string;\n /** Absolute file for a renderer controller reference (views `controller:`). */\n rendererControllerFile(reference: string): string;\n /**\n * The companion data loader of a view/layout path (`pages/chat.svelte` →\n * `pages/chat.load.ts`), or `null` when the source declares none. The\n * identity is the config-relative source path (`ui/pages/chat.load.ts`) —\n * the `.load` seam identity. Two companions for one view is a build error.\n */\n rendererLoaderReference(viewPath: string): { identity: string; file: string } | null;\n /** Absolute path of an optional config-relative hook module, if declared. */\n hookModule(relative: string | undefined): string | null;\n}\n\ninterface StitchManifest {\n stitch?: unknown;\n}\n\nconst GLOB_MAGIC = /[*?[\\]{}]/;\n\n/**\n * Expand an openapi reference into the ordered list of YAML files it names:\n * a stitch manifest (`stitch: [...]`, entries relative to the manifest, globs\n * allowed) or a single document.\n */\nexport function expandOpenapiFiles(absolutePath: string): string[] {\n const doc = yaml.load(fs.readFileSync(absolutePath, 'utf-8')) as StitchManifest | null;\n if (!doc || typeof doc !== 'object' || !('stitch' in doc)) return [absolutePath];\n if (!Array.isArray(doc.stitch)) {\n throw new TestWoodError(`${absolutePath}: \"stitch\" must be a list of YAML files or globs`);\n }\n const manifestDir = path.dirname(absolutePath);\n const files: string[] = [];\n for (const entry of doc.stitch) {\n if (typeof entry !== 'string' || !entry.length) {\n throw new TestWoodError(`${absolutePath}: every \"stitch\" entry must be a non-empty string`);\n }\n files.push(...expandStitchEntry(entry, manifestDir));\n }\n return files;\n}\n\nfunction expandStitchEntry(entry: string, manifestDir: string): string[] {\n if (!GLOB_MAGIC.test(entry)) return [path.resolve(manifestDir, entry)];\n return globSync(entry, { cwd: manifestDir, absolute: true, nodir: true }).sort();\n}\n\nfunction loadOperations(files: string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n for (const file of files) {\n for (const [channel, operation] of parseOperations(fs.readFileSync(file, 'utf-8'))) {\n if (!merged.has(channel)) merged.set(channel, operation);\n }\n }\n return merged;\n}\n\n/** Views documents only; a stitched directory may hold other YAML kinds. */\nfunction loadViews(files: string[]): ViewDef[] {\n const views: ViewDef[] = [];\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf-8');\n if (!/^view:/m.test(content)) continue;\n views.push(...parseViews(content));\n }\n return views;\n}\n\nexport function loadWoodConfigSource(configPath: string): WoodConfigSource {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n const resolvedConfigPath = path.resolve(configPath);\n if (!fs.existsSync(resolvedConfigPath)) {\n throw new TestWoodError(`testWood(configPath): config file not found: ${resolvedConfigPath}`);\n }\n const config = parseConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n const fromConfig = (relative: string): string => path.resolve(configDir, relative);\n\n const mainControllersDir = fromConfig(config.main.controllers);\n const rendererComponentDir = fromConfig(config.renderer.componentDir);\n let views: ViewDef[] | null = null;\n\n return {\n configPath: resolvedConfigPath,\n configDir,\n config,\n operations: loadOperations(expandOpenapiFiles(fromConfig(config.main.openapi))),\n mainControllersDir,\n mainMiddlewareDir: fromConfig(config.main.middleware),\n rendererComponentDir,\n views: () => {\n views ??= loadViews(expandOpenapiFiles(fromConfig(config.renderer.openapi)));\n return views;\n },\n mainControllerFile: (identity) => {\n const file = resolveControllerFile(identity, mainControllersDir);\n if (file) return file;\n throw new TestWoodError(\n `no controller file for identity \"${identity}\" under ${mainControllersDir}; looked for:\\n ` +\n controllerFileCandidates(identity, mainControllersDir).join('\\n '),\n );\n },\n rendererControllerFile: (reference) => {\n const file = path.resolve(rendererComponentDir, reference);\n if (fs.existsSync(file)) return file;\n throw new TestWoodError(`renderer controller \"${reference}\" not found at ${file}`);\n },\n rendererLoaderReference: (viewPath) => {\n const present = loaderCompanionCandidates(viewPath)\n .map((candidate) => path.resolve(rendererComponentDir, candidate))\n .filter((file) => fs.existsSync(file));\n if (present.length > 1) {\n throw new TestWoodError(\n `ambiguous data loader for \"${viewPath}\": ${present.join(', ')}; keep exactly one companion`,\n );\n }\n if (!present.length) return null;\n const file = present[0]!;\n return { identity: path.relative(configDir, file).split(path.sep).join('/'), file };\n },\n hookModule: (relative) => (relative ? fromConfig(relative) : null),\n };\n}\n\n/** Import a hook module declared in the config and return its default export. */\nexport async function importDefaultExport<T>(\n importer: ModuleImporter,\n absolutePath: string,\n what: string,\n): Promise<T> {\n const mod = (await importer(absolutePath)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new TestWoodError(`${what} module ${absolutePath} has no default export`);\n }\n return mod.default as T;\n}\n"],"mappings":"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","names":["yaml","fs","path","file"]}
|
|
@@ -20,6 +20,16 @@ interface WoodConfigSource {
|
|
|
20
20
|
mainControllerFile(identity: string): string;
|
|
21
21
|
/** Absolute file for a renderer controller reference (views `controller:`). */
|
|
22
22
|
rendererControllerFile(reference: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* The companion data loader of a view/layout path (`pages/chat.svelte` →
|
|
25
|
+
* `pages/chat.load.ts`), or `null` when the source declares none. The
|
|
26
|
+
* identity is the config-relative source path (`ui/pages/chat.load.ts`) —
|
|
27
|
+
* the `.load` seam identity. Two companions for one view is a build error.
|
|
28
|
+
*/
|
|
29
|
+
rendererLoaderReference(viewPath: string): {
|
|
30
|
+
identity: string;
|
|
31
|
+
file: string;
|
|
32
|
+
} | null;
|
|
23
33
|
/** Absolute path of an optional config-relative hook module, if declared. */
|
|
24
34
|
hookModule(relative: string | undefined): string | null;
|
|
25
35
|
}
|
|
@@ -20,6 +20,16 @@ interface WoodConfigSource {
|
|
|
20
20
|
mainControllerFile(identity: string): string;
|
|
21
21
|
/** Absolute file for a renderer controller reference (views `controller:`). */
|
|
22
22
|
rendererControllerFile(reference: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* The companion data loader of a view/layout path (`pages/chat.svelte` →
|
|
25
|
+
* `pages/chat.load.ts`), or `null` when the source declares none. The
|
|
26
|
+
* identity is the config-relative source path (`ui/pages/chat.load.ts`) —
|
|
27
|
+
* the `.load` seam identity. Two companions for one view is a build error.
|
|
28
|
+
*/
|
|
29
|
+
rendererLoaderReference(viewPath: string): {
|
|
30
|
+
identity: string;
|
|
31
|
+
file: string;
|
|
32
|
+
} | null;
|
|
23
33
|
/** Absolute path of an optional config-relative hook module, if declared. */
|
|
24
34
|
hookModule(relative: string | undefined): string | null;
|
|
25
35
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
controllerFileCandidates,
|
|
11
11
|
resolveControllerFile
|
|
12
12
|
} from "../codegen/controller_registration_generator.js";
|
|
13
|
+
import { loaderCompanionCandidates } from "../loader/loader_orchestration.js";
|
|
13
14
|
import { TestWoodError } from "./test_wood.js";
|
|
14
15
|
const defaultImporter = (absolutePath) => import(
|
|
15
16
|
/* @vite-ignore */
|
|
@@ -93,6 +94,17 @@ function loadWoodConfigSource(configPath) {
|
|
|
93
94
|
if (fs.existsSync(file)) return file;
|
|
94
95
|
throw new TestWoodError(`renderer controller "${reference}" not found at ${file}`);
|
|
95
96
|
},
|
|
97
|
+
rendererLoaderReference: (viewPath) => {
|
|
98
|
+
const present = loaderCompanionCandidates(viewPath).map((candidate) => path.resolve(rendererComponentDir, candidate)).filter((file2) => fs.existsSync(file2));
|
|
99
|
+
if (present.length > 1) {
|
|
100
|
+
throw new TestWoodError(
|
|
101
|
+
`ambiguous data loader for "${viewPath}": ${present.join(", ")}; keep exactly one companion`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!present.length) return null;
|
|
105
|
+
const file = present[0];
|
|
106
|
+
return { identity: path.relative(configDir, file).split(path.sep).join("/"), file };
|
|
107
|
+
},
|
|
96
108
|
hookModule: (relative) => relative ? fromConfig(relative) : null
|
|
97
109
|
};
|
|
98
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/wood_config_source.ts"],"sourcesContent":["/**\n * Config-driven test source for `testWood(configPath)`.\n *\n * Everything a test environment binds is read from the production\n * `wood.config.yml`: operations from `main.openapi` (a single document or a\n * stitch manifest), views from `renderer.openapi`, controller files from\n * `main.controllers` (same candidate rules as codegen) and renderer\n * controllers relative to `renderer.componentDir`. Tests name nothing by\n * hand — a config path is the whole source.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport yaml from 'js-yaml';\nimport { globSync } from 'glob';\nimport { parseConfig } from '../config/config_parser.js';\nimport type { WoodConfig } from '../types/config.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { parseViews } from '../parser/views_parser.js';\nimport type { OperationDef, ViewDef } from '../types/index.js';\nimport {\n controllerFileCandidates,\n resolveControllerFile,\n} from '../codegen/controller_registration_generator.js';\nimport { TestWoodError } from './test_wood.js';\n\n/** Imports a module by absolute path; the default keeps the caller's module registry. */\nexport type ModuleImporter = (absolutePath: string) => Promise<unknown>;\n\nexport const defaultImporter: ModuleImporter = (absolutePath) =>\n import(/* @vite-ignore */ pathToFileURL(absolutePath).href);\n\nexport interface WoodConfigSource {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n /** Every production operation, stitch order, first declaration wins. */\n readonly operations: Map<string, OperationDef>;\n readonly mainControllersDir: string;\n readonly mainMiddlewareDir: string;\n readonly rendererComponentDir: string;\n /** Every production view (parsed on first use — main-only builds never read them). */\n views(): ViewDef[];\n /** Absolute file for a main controller identity, or a build error naming the candidates. */\n mainControllerFile(identity: string): string;\n /** Absolute file for a renderer controller reference (views `controller:`). */\n rendererControllerFile(reference: string): string;\n /** Absolute path of an optional config-relative hook module, if declared. */\n hookModule(relative: string | undefined): string | null;\n}\n\ninterface StitchManifest {\n stitch?: unknown;\n}\n\nconst GLOB_MAGIC = /[*?[\\]{}]/;\n\n/**\n * Expand an openapi reference into the ordered list of YAML files it names:\n * a stitch manifest (`stitch: [...]`, entries relative to the manifest, globs\n * allowed) or a single document.\n */\nexport function expandOpenapiFiles(absolutePath: string): string[] {\n const doc = yaml.load(fs.readFileSync(absolutePath, 'utf-8')) as StitchManifest | null;\n if (!doc || typeof doc !== 'object' || !('stitch' in doc)) return [absolutePath];\n if (!Array.isArray(doc.stitch)) {\n throw new TestWoodError(`${absolutePath}: \"stitch\" must be a list of YAML files or globs`);\n }\n const manifestDir = path.dirname(absolutePath);\n const files: string[] = [];\n for (const entry of doc.stitch) {\n if (typeof entry !== 'string' || !entry.length) {\n throw new TestWoodError(`${absolutePath}: every \"stitch\" entry must be a non-empty string`);\n }\n files.push(...expandStitchEntry(entry, manifestDir));\n }\n return files;\n}\n\nfunction expandStitchEntry(entry: string, manifestDir: string): string[] {\n if (!GLOB_MAGIC.test(entry)) return [path.resolve(manifestDir, entry)];\n return globSync(entry, { cwd: manifestDir, absolute: true, nodir: true }).sort();\n}\n\nfunction loadOperations(files: string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n for (const file of files) {\n for (const [channel, operation] of parseOperations(fs.readFileSync(file, 'utf-8'))) {\n if (!merged.has(channel)) merged.set(channel, operation);\n }\n }\n return merged;\n}\n\n/** Views documents only; a stitched directory may hold other YAML kinds. */\nfunction loadViews(files: string[]): ViewDef[] {\n const views: ViewDef[] = [];\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf-8');\n if (!/^view:/m.test(content)) continue;\n views.push(...parseViews(content));\n }\n return views;\n}\n\nexport function loadWoodConfigSource(configPath: string): WoodConfigSource {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n const resolvedConfigPath = path.resolve(configPath);\n if (!fs.existsSync(resolvedConfigPath)) {\n throw new TestWoodError(`testWood(configPath): config file not found: ${resolvedConfigPath}`);\n }\n const config = parseConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n const fromConfig = (relative: string): string => path.resolve(configDir, relative);\n\n const mainControllersDir = fromConfig(config.main.controllers);\n const rendererComponentDir = fromConfig(config.renderer.componentDir);\n let views: ViewDef[] | null = null;\n\n return {\n configPath: resolvedConfigPath,\n configDir,\n config,\n operations: loadOperations(expandOpenapiFiles(fromConfig(config.main.openapi))),\n mainControllersDir,\n mainMiddlewareDir: fromConfig(config.main.middleware),\n rendererComponentDir,\n views: () => {\n views ??= loadViews(expandOpenapiFiles(fromConfig(config.renderer.openapi)));\n return views;\n },\n mainControllerFile: (identity) => {\n const file = resolveControllerFile(identity, mainControllersDir);\n if (file) return file;\n throw new TestWoodError(\n `no controller file for identity \"${identity}\" under ${mainControllersDir}; looked for:\\n ` +\n controllerFileCandidates(identity, mainControllersDir).join('\\n '),\n );\n },\n rendererControllerFile: (reference) => {\n const file = path.resolve(rendererComponentDir, reference);\n if (fs.existsSync(file)) return file;\n throw new TestWoodError(`renderer controller \"${reference}\" not found at ${file}`);\n },\n hookModule: (relative) => (relative ? fromConfig(relative) : null),\n };\n}\n\n/** Import a hook module declared in the config and return its default export. */\nexport async function importDefaultExport<T>(\n importer: ModuleImporter,\n absolutePath: string,\n what: string,\n): Promise<T> {\n const mod = (await importer(absolutePath)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new TestWoodError(`${what} module ${absolutePath} has no default export`);\n }\n return mod.default as T;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/wood_config_source.ts"],"sourcesContent":["/**\n * Config-driven test source for `testWood(configPath)`.\n *\n * Everything a test environment binds is read from the production\n * `wood.config.yml`: operations from `main.openapi` (a single document or a\n * stitch manifest), views from `renderer.openapi`, controller files from\n * `main.controllers` (same candidate rules as codegen) and renderer\n * controllers relative to `renderer.componentDir`. Tests name nothing by\n * hand — a config path is the whole source.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport yaml from 'js-yaml';\nimport { globSync } from 'glob';\nimport { parseConfig } from '../config/config_parser.js';\nimport type { WoodConfig } from '../types/config.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { parseViews } from '../parser/views_parser.js';\nimport type { OperationDef, ViewDef } from '../types/index.js';\nimport {\n controllerFileCandidates,\n resolveControllerFile,\n} from '../codegen/controller_registration_generator.js';\nimport { loaderCompanionCandidates } from '../loader/loader_orchestration.js';\nimport { TestWoodError } from './test_wood.js';\n\n/** Imports a module by absolute path; the default keeps the caller's module registry. */\nexport type ModuleImporter = (absolutePath: string) => Promise<unknown>;\n\nexport const defaultImporter: ModuleImporter = (absolutePath) =>\n import(/* @vite-ignore */ pathToFileURL(absolutePath).href);\n\nexport interface WoodConfigSource {\n readonly configPath: string;\n readonly configDir: string;\n readonly config: WoodConfig;\n /** Every production operation, stitch order, first declaration wins. */\n readonly operations: Map<string, OperationDef>;\n readonly mainControllersDir: string;\n readonly mainMiddlewareDir: string;\n readonly rendererComponentDir: string;\n /** Every production view (parsed on first use — main-only builds never read them). */\n views(): ViewDef[];\n /** Absolute file for a main controller identity, or a build error naming the candidates. */\n mainControllerFile(identity: string): string;\n /** Absolute file for a renderer controller reference (views `controller:`). */\n rendererControllerFile(reference: string): string;\n /**\n * The companion data loader of a view/layout path (`pages/chat.svelte` →\n * `pages/chat.load.ts`), or `null` when the source declares none. The\n * identity is the config-relative source path (`ui/pages/chat.load.ts`) —\n * the `.load` seam identity. Two companions for one view is a build error.\n */\n rendererLoaderReference(viewPath: string): { identity: string; file: string } | null;\n /** Absolute path of an optional config-relative hook module, if declared. */\n hookModule(relative: string | undefined): string | null;\n}\n\ninterface StitchManifest {\n stitch?: unknown;\n}\n\nconst GLOB_MAGIC = /[*?[\\]{}]/;\n\n/**\n * Expand an openapi reference into the ordered list of YAML files it names:\n * a stitch manifest (`stitch: [...]`, entries relative to the manifest, globs\n * allowed) or a single document.\n */\nexport function expandOpenapiFiles(absolutePath: string): string[] {\n const doc = yaml.load(fs.readFileSync(absolutePath, 'utf-8')) as StitchManifest | null;\n if (!doc || typeof doc !== 'object' || !('stitch' in doc)) return [absolutePath];\n if (!Array.isArray(doc.stitch)) {\n throw new TestWoodError(`${absolutePath}: \"stitch\" must be a list of YAML files or globs`);\n }\n const manifestDir = path.dirname(absolutePath);\n const files: string[] = [];\n for (const entry of doc.stitch) {\n if (typeof entry !== 'string' || !entry.length) {\n throw new TestWoodError(`${absolutePath}: every \"stitch\" entry must be a non-empty string`);\n }\n files.push(...expandStitchEntry(entry, manifestDir));\n }\n return files;\n}\n\nfunction expandStitchEntry(entry: string, manifestDir: string): string[] {\n if (!GLOB_MAGIC.test(entry)) return [path.resolve(manifestDir, entry)];\n return globSync(entry, { cwd: manifestDir, absolute: true, nodir: true }).sort();\n}\n\nfunction loadOperations(files: string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n for (const file of files) {\n for (const [channel, operation] of parseOperations(fs.readFileSync(file, 'utf-8'))) {\n if (!merged.has(channel)) merged.set(channel, operation);\n }\n }\n return merged;\n}\n\n/** Views documents only; a stitched directory may hold other YAML kinds. */\nfunction loadViews(files: string[]): ViewDef[] {\n const views: ViewDef[] = [];\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf-8');\n if (!/^view:/m.test(content)) continue;\n views.push(...parseViews(content));\n }\n return views;\n}\n\nexport function loadWoodConfigSource(configPath: string): WoodConfigSource {\n if (typeof configPath !== 'string' || !configPath.length) {\n throw new TestWoodError('testWood(configPath) requires the path to a wood.config.yml');\n }\n const resolvedConfigPath = path.resolve(configPath);\n if (!fs.existsSync(resolvedConfigPath)) {\n throw new TestWoodError(`testWood(configPath): config file not found: ${resolvedConfigPath}`);\n }\n const config = parseConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n const fromConfig = (relative: string): string => path.resolve(configDir, relative);\n\n const mainControllersDir = fromConfig(config.main.controllers);\n const rendererComponentDir = fromConfig(config.renderer.componentDir);\n let views: ViewDef[] | null = null;\n\n return {\n configPath: resolvedConfigPath,\n configDir,\n config,\n operations: loadOperations(expandOpenapiFiles(fromConfig(config.main.openapi))),\n mainControllersDir,\n mainMiddlewareDir: fromConfig(config.main.middleware),\n rendererComponentDir,\n views: () => {\n views ??= loadViews(expandOpenapiFiles(fromConfig(config.renderer.openapi)));\n return views;\n },\n mainControllerFile: (identity) => {\n const file = resolveControllerFile(identity, mainControllersDir);\n if (file) return file;\n throw new TestWoodError(\n `no controller file for identity \"${identity}\" under ${mainControllersDir}; looked for:\\n ` +\n controllerFileCandidates(identity, mainControllersDir).join('\\n '),\n );\n },\n rendererControllerFile: (reference) => {\n const file = path.resolve(rendererComponentDir, reference);\n if (fs.existsSync(file)) return file;\n throw new TestWoodError(`renderer controller \"${reference}\" not found at ${file}`);\n },\n rendererLoaderReference: (viewPath) => {\n const present = loaderCompanionCandidates(viewPath)\n .map((candidate) => path.resolve(rendererComponentDir, candidate))\n .filter((file) => fs.existsSync(file));\n if (present.length > 1) {\n throw new TestWoodError(\n `ambiguous data loader for \"${viewPath}\": ${present.join(', ')}; keep exactly one companion`,\n );\n }\n if (!present.length) return null;\n const file = present[0]!;\n return { identity: path.relative(configDir, file).split(path.sep).join('/'), file };\n },\n hookModule: (relative) => (relative ? fromConfig(relative) : null),\n };\n}\n\n/** Import a hook module declared in the config and return its default export. */\nexport async function importDefaultExport<T>(\n importer: ModuleImporter,\n absolutePath: string,\n what: string,\n): Promise<T> {\n const mod = (await importer(absolutePath)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new TestWoodError(`${what} module ${absolutePath} has no default export`);\n }\n return mod.default as T;\n}\n"],"mappings":"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","names":["file"]}
|
|
@@ -159,8 +159,11 @@ async function buildRendererTier(options) {
|
|
|
159
159
|
hooks,
|
|
160
160
|
dom,
|
|
161
161
|
ioc,
|
|
162
|
+
borrowedTokens,
|
|
162
163
|
registrations,
|
|
163
|
-
controllerClasses
|
|
164
|
+
controllerClasses,
|
|
165
|
+
loaders,
|
|
166
|
+
loaderModules
|
|
164
167
|
} = options;
|
|
165
168
|
const keeper = dom ? new MountKeeper(await loadPageHost(source, importer, dom)) : null;
|
|
166
169
|
const registry = new import_work_registry.WorkRegistry();
|
|
@@ -171,10 +174,31 @@ async function buildRendererTier(options) {
|
|
|
171
174
|
const { bridge } = (0, import_build_transport_bridge.buildTransportBridge)(operations, transport);
|
|
172
175
|
registrations.root.registerFunction(import_navigation.Navigation, () => defaultNavigation, { loadAs: import_ioc.LoadAs.Singleton });
|
|
173
176
|
registrations.root.registerFunction(import_test_wood.WOOD_FRONTEND_BRIDGE, () => bridge, { loadAs: import_ioc.LoadAs.Singleton });
|
|
174
|
-
const rendererEnv = await (0, import_registration_only_root.buildRecordedEnvironment)(ioc, registrations, "renderer");
|
|
177
|
+
const rendererEnv = await (0, import_registration_only_root.buildRecordedEnvironment)(ioc, registrations, "renderer", borrowedTokens);
|
|
175
178
|
const rendererRoot = rendererEnv.root;
|
|
176
179
|
let navigation;
|
|
177
180
|
const loadController = controllerLoader(source, importer, controllerClasses);
|
|
181
|
+
const resolveController = (identity) => loadController(identity);
|
|
182
|
+
const installedLoaders = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const [identity, original] of loaderModules ?? []) {
|
|
184
|
+
installedLoaders.set(
|
|
185
|
+
identity,
|
|
186
|
+
loaders ? loaders.replace("load", { path: identity, export: "default" }, original) : original
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const loaderFor = (viewPath) => {
|
|
190
|
+
const reference = source.rendererLoaderReference(viewPath);
|
|
191
|
+
if (!reference) return null;
|
|
192
|
+
const installed = installedLoaders.get(reference.identity);
|
|
193
|
+
if (!installed) {
|
|
194
|
+
throw new import_test_wood.TestWoodError(`data loader ${reference.identity} was not imported at build; rebuild the environment`);
|
|
195
|
+
}
|
|
196
|
+
return installed;
|
|
197
|
+
};
|
|
198
|
+
const resolveLoaders = (view) => ({
|
|
199
|
+
layouts: view.layouts.map((layoutPath) => loaderFor(layoutPath)),
|
|
200
|
+
view: loaderFor(view.viewPath)
|
|
201
|
+
});
|
|
178
202
|
let frontend;
|
|
179
203
|
try {
|
|
180
204
|
navigation = await import_ioc.ExecutionContext.run(rendererRoot, () => rendererRoot.get(import_navigation.Navigation));
|
|
@@ -183,7 +207,8 @@ async function buildRendererTier(options) {
|
|
|
183
207
|
rendererRoot,
|
|
184
208
|
registry,
|
|
185
209
|
navigation,
|
|
186
|
-
resolveController
|
|
210
|
+
resolveController,
|
|
211
|
+
resolveLoaders,
|
|
187
212
|
...hooks?.configurePageScope ? { configurePageScope: (scope, view, context) => hooks.configurePageScope(scope, view, context) } : {}
|
|
188
213
|
});
|
|
189
214
|
} catch (error) {
|