@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,9 +1,25 @@
|
|
|
1
|
+
import { FETCH_RUNTIME } from "@noego/runtime/fetch";
|
|
2
|
+
import { createTestFetch, defineFetchRoutes } from "@noego/testing/fetch";
|
|
1
3
|
import { disposeControllerLifecycle, guardControllerLifecycleOnce } from "../controller/controller_lifecycle.js";
|
|
2
4
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
+
import { LoadAs } from "@noego/ioc";
|
|
6
|
+
import { ExecutionContext } from "@noego/ioc";
|
|
7
|
+
import {
|
|
8
|
+
applyTestBindings,
|
|
9
|
+
createTestExecutables,
|
|
10
|
+
describeTestBindings,
|
|
11
|
+
ENV_REGISTRY,
|
|
12
|
+
resolveTestOverlays,
|
|
13
|
+
testIoc,
|
|
14
|
+
TestStub,
|
|
15
|
+
testStub,
|
|
16
|
+
stubEntries
|
|
17
|
+
} from "@noego/testing";
|
|
5
18
|
import { installWoodRuntime, WoodRouter } from "../runtime/runtime_module.js";
|
|
19
|
+
import { assertDataLoader } from "../loader/loader_orchestration.js";
|
|
20
|
+
import { MiddlewareResolver } from "../middleware/middleware_resolver.js";
|
|
6
21
|
import { Navigation } from "../navigation/index.js";
|
|
22
|
+
import { createOperationApi, RouterBridgeEvents } from "./create_router_bridge.js";
|
|
7
23
|
import { TestWoodError, WOOD_FRONTEND_BRIDGE } from "./test_wood.js";
|
|
8
24
|
import {
|
|
9
25
|
defaultImporter,
|
|
@@ -11,9 +27,10 @@ import {
|
|
|
11
27
|
loadWoodConfigSource
|
|
12
28
|
} from "./wood_config_source.js";
|
|
13
29
|
import {
|
|
30
|
+
DECLARED_OWNERS,
|
|
14
31
|
resolveSelection,
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
selectOperations,
|
|
33
|
+
selectRendererCatalog
|
|
17
34
|
} from "./wood_test_selection.js";
|
|
18
35
|
import {
|
|
19
36
|
buildRendererTier
|
|
@@ -24,6 +41,7 @@ import {
|
|
|
24
41
|
RegistrationOnlyRoot,
|
|
25
42
|
TestWoodCleanupError
|
|
26
43
|
} from "./registration_only_root.js";
|
|
44
|
+
import { LazyRuntimeStage, lazyMember } from "./wood_lazy_runtime.js";
|
|
27
45
|
const CONTAINER_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for("@noego/wood.container.registry");
|
|
28
46
|
function globalRegistrySize() {
|
|
29
47
|
const registry = globalThis[CONTAINER_REGISTRY_SYMBOL];
|
|
@@ -34,126 +52,6 @@ function tierMissing(tier, member) {
|
|
|
34
52
|
`the ${member} is not available: this environment was built without { ${tier} } \u2014 add it to select()`
|
|
35
53
|
);
|
|
36
54
|
}
|
|
37
|
-
function recordComposer(compose) {
|
|
38
|
-
if (typeof compose !== "function") throw new TestWoodError("owner composition expects a callback");
|
|
39
|
-
const calls = [];
|
|
40
|
-
const root = {
|
|
41
|
-
use: (preset) => {
|
|
42
|
-
calls.push({ method: "use", args: [preset], flat: false });
|
|
43
|
-
return root;
|
|
44
|
-
},
|
|
45
|
-
classes: (config) => {
|
|
46
|
-
calls.push({ method: "classes", args: [config], flat: false });
|
|
47
|
-
return root;
|
|
48
|
-
},
|
|
49
|
-
functions: (config) => {
|
|
50
|
-
calls.push({ method: "functions", args: [config], flat: false });
|
|
51
|
-
return root;
|
|
52
|
-
},
|
|
53
|
-
values: (config) => {
|
|
54
|
-
calls.push({ method: "values", args: [config], flat: false });
|
|
55
|
-
return root;
|
|
56
|
-
},
|
|
57
|
-
methods: (config) => {
|
|
58
|
-
calls.push({ method: "methods", args: [config], flat: false });
|
|
59
|
-
return root;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
compose(root);
|
|
63
|
-
return calls;
|
|
64
|
-
}
|
|
65
|
-
function applyCalls(builder, calls) {
|
|
66
|
-
let next = builder;
|
|
67
|
-
for (const call of [...calls].sort((a, b) => a.order - b.order)) {
|
|
68
|
-
next = next[call.method](...call.args);
|
|
69
|
-
}
|
|
70
|
-
return next;
|
|
71
|
-
}
|
|
72
|
-
function ownerTokens(builder) {
|
|
73
|
-
const tokens = /* @__PURE__ */ new Set();
|
|
74
|
-
const visit = (token) => {
|
|
75
|
-
if (tokens.has(token)) return;
|
|
76
|
-
tokens.add(token);
|
|
77
|
-
if (typeof token !== "function") return;
|
|
78
|
-
const injected = Reflect.getMetadata?.(/* @__PURE__ */ Symbol.for("ioc:inject:tokens"), token);
|
|
79
|
-
const design = Reflect.getMetadata?.("design:paramtypes", token) ?? [];
|
|
80
|
-
const indices = /* @__PURE__ */ new Set([...design.keys(), ...injected?.keys() ?? []]);
|
|
81
|
-
for (const index of indices) visit(injected?.has(index) ? injected.get(index) : design[index]);
|
|
82
|
-
};
|
|
83
|
-
for (const write of builder.writes) {
|
|
84
|
-
if (write.op === "use") {
|
|
85
|
-
for (const registration of flattenModule(write.module)) {
|
|
86
|
-
visit(registration.token);
|
|
87
|
-
if (registration.kind === "class") visit(registration.implementation);
|
|
88
|
-
for (const dependency of registration.dependencies) visit(dependency);
|
|
89
|
-
}
|
|
90
|
-
} else if (write.op !== "methods") {
|
|
91
|
-
visit(write.key);
|
|
92
|
-
if (write.op === "classes") visit(write.implementation);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return tokens;
|
|
96
|
-
}
|
|
97
|
-
function tokenLabel(token) {
|
|
98
|
-
if (typeof token === "function" && token.name) return token.name;
|
|
99
|
-
return String(token);
|
|
100
|
-
}
|
|
101
|
-
function callEntries(call) {
|
|
102
|
-
applyCalls(testIoc(), [{ ...call, order: 0 }]);
|
|
103
|
-
const config = call.args[0];
|
|
104
|
-
if (Array.isArray(config) || config instanceof Map) return [...config];
|
|
105
|
-
return Reflect.ownKeys(config).map((key) => [key, config[key]]);
|
|
106
|
-
}
|
|
107
|
-
function resolveOwnerToken(key, exact, main, renderer, method) {
|
|
108
|
-
let token = key;
|
|
109
|
-
if (!exact && typeof key === "string") {
|
|
110
|
-
const matches = [.../* @__PURE__ */ new Set([...main, ...renderer])].filter(
|
|
111
|
-
(candidate) => candidate === key || typeof candidate === "function" && candidate.name === key
|
|
112
|
-
);
|
|
113
|
-
if (matches.length > 1) {
|
|
114
|
-
throw new TestWoodError(`Ambiguous ${method} replacement name "${key}"; use an exact token`);
|
|
115
|
-
}
|
|
116
|
-
token = matches[0] ?? key;
|
|
117
|
-
}
|
|
118
|
-
const ownsMain = main.has(token);
|
|
119
|
-
const ownsRenderer = renderer.has(token);
|
|
120
|
-
if (!ownsMain && !ownsRenderer) {
|
|
121
|
-
throw new TestWoodError(
|
|
122
|
-
`Unknown ${method} replacement ${tokenLabel(token)} in the selected roots; register it with .main() or .renderer()`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (ownsMain && ownsRenderer) {
|
|
126
|
-
throw new TestWoodError(
|
|
127
|
-
`Ambiguous ${method} replacement ${tokenLabel(token)} belongs to main and renderer; target .main() or .renderer() explicitly`
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
return { token, owner: ownsMain ? "main" : "renderer" };
|
|
131
|
-
}
|
|
132
|
-
function routeCalls(calls, main, renderer, target) {
|
|
133
|
-
for (const call of calls) {
|
|
134
|
-
if (!call.flat || call.method !== "classes") continue;
|
|
135
|
-
const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;
|
|
136
|
-
for (const [key, implementation] of callEntries(call)) {
|
|
137
|
-
const routed = resolveOwnerToken(key, exact || typeof key !== "string", main, renderer, call.method);
|
|
138
|
-
(routed.owner === "main" ? main : renderer).add(implementation);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const result = [];
|
|
142
|
-
for (const call of calls) {
|
|
143
|
-
if (!call.flat) {
|
|
144
|
-
if (target === "main") result.push(call);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;
|
|
148
|
-
for (const [key, value] of callEntries(call)) {
|
|
149
|
-
const routed = resolveOwnerToken(key, exact || typeof key !== "string", main, renderer, call.method);
|
|
150
|
-
if (routed.owner === target) {
|
|
151
|
-
result.push({ ...call, flat: false, args: [[[routed.token, value]]] });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
55
|
async function loadController(source, importer, reference, owner) {
|
|
158
56
|
const file = owner === "main" ? source.mainControllerFile(reference) : source.rendererControllerFile(reference);
|
|
159
57
|
const controller = await importDefaultExport(
|
|
@@ -218,6 +116,7 @@ function assertNoGlobalContainerLeak(before, source) {
|
|
|
218
116
|
}
|
|
219
117
|
function createComposeContext(source, state, selection, operations, live) {
|
|
220
118
|
return {
|
|
119
|
+
events: new RouterBridgeEvents(),
|
|
221
120
|
configPath: source.configPath,
|
|
222
121
|
configDir: source.configDir,
|
|
223
122
|
config: source.config,
|
|
@@ -225,10 +124,10 @@ function createComposeContext(source, state, selection, operations, live) {
|
|
|
225
124
|
operations,
|
|
226
125
|
importer: state.importer,
|
|
227
126
|
call: async (operation, input) => {
|
|
228
|
-
if (!live.
|
|
127
|
+
if (!live.env || !live.router && !live.runtime) {
|
|
229
128
|
throw new TestWoodError("compose context call() is available from start() onward, not during compose");
|
|
230
129
|
}
|
|
231
|
-
const router = live.router;
|
|
130
|
+
const router = live.router ?? (await live.runtime.main.acquire()).router;
|
|
232
131
|
return await ExecutionContext.run(
|
|
233
132
|
live.env.root,
|
|
234
133
|
() => router.dispatch(operation, input)
|
|
@@ -236,34 +135,49 @@ function createComposeContext(source, state, selection, operations, live) {
|
|
|
236
135
|
}
|
|
237
136
|
};
|
|
238
137
|
}
|
|
239
|
-
function createAssemblyDisposer(live, mainRegistrations, rendererRegistrations, controllerScopes) {
|
|
138
|
+
function createAssemblyDisposer(live, mainRegistrations, rendererRegistrations, controllerScopes, network) {
|
|
240
139
|
let closing = null;
|
|
241
|
-
return () =>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
140
|
+
return () => {
|
|
141
|
+
live.disposed = true;
|
|
142
|
+
return closing ?? (closing = cleanupAll("testWood cleanup failed", [
|
|
143
|
+
// An initialization racing this dispose finishes first (it refuses to
|
|
144
|
+
// complete once `disposed` is set) so every resource it created late is
|
|
145
|
+
// released below. A rollback-triggered dispose skips this: its own
|
|
146
|
+
// stage has already finished and waiting would be a self-await.
|
|
147
|
+
["runtime settle", async () => {
|
|
148
|
+
if (live.rolledBack || !live.runtime) return;
|
|
149
|
+
await live.runtime.main.settled();
|
|
150
|
+
await live.runtime.renderer?.settled();
|
|
151
|
+
}],
|
|
152
|
+
["renderer", async () => {
|
|
153
|
+
if (live.renderer) await live.renderer.dispose();
|
|
154
|
+
}],
|
|
155
|
+
...[...controllerScopes].reverse().map(
|
|
156
|
+
(scope, index) => [`controller scope ${controllerScopes.length - index}`, () => scope.dispose()]
|
|
157
|
+
),
|
|
158
|
+
["compose drain", async () => {
|
|
159
|
+
const root = live.env?.root ?? mainRegistrations.actualRoot;
|
|
160
|
+
if (!root.isDisposed()) await ExecutionContext.run(root, () => live.composed?.drain?.());
|
|
161
|
+
}],
|
|
162
|
+
["main root", async () => {
|
|
163
|
+
if (live.env) await disposeRoot(live.env);
|
|
164
|
+
else await mainRegistrations.actualRoot.dispose();
|
|
165
|
+
}],
|
|
166
|
+
["unbuilt renderer root", async () => {
|
|
167
|
+
if (!live.renderer && rendererRegistrations) await rendererRegistrations.actualRoot.dispose();
|
|
168
|
+
}],
|
|
169
|
+
["compose external dispose", async () => {
|
|
170
|
+
await live.composed?.dispose?.();
|
|
171
|
+
}],
|
|
172
|
+
["external fetch", () => network.dispose()]
|
|
173
|
+
]));
|
|
174
|
+
};
|
|
262
175
|
}
|
|
263
176
|
async function loadAssemblyControllers(inputs) {
|
|
264
177
|
const main = await loadMainControllers(inputs.source, inputs.operations, inputs.state.importer);
|
|
265
|
-
const renderer = inputs.catalog ? await loadRendererControllers(inputs.source, inputs.catalog.all(), inputs.state.importer) : /* @__PURE__ */ new Map();
|
|
266
|
-
|
|
178
|
+
const renderer = inputs.catalog && !inputs.state.browserHost ? await loadRendererControllers(inputs.source, inputs.catalog.all(), inputs.state.importer) : /* @__PURE__ */ new Map();
|
|
179
|
+
const loaders = inputs.catalog && !inputs.state.browserHost ? await loadRendererLoaders(inputs.source, inputs.catalog.all(), inputs.state.importer) : /* @__PURE__ */ new Map();
|
|
180
|
+
return { main, renderer, loaders };
|
|
267
181
|
}
|
|
268
182
|
function installAssemblyRuntime(inputs, mainControllers) {
|
|
269
183
|
installWoodRuntime(inputs.mainRegistrations.root, {
|
|
@@ -275,14 +189,66 @@ function installAssemblyRuntime(inputs, mainControllers) {
|
|
|
275
189
|
configuredRouter.registerControllerLoader(identity, () => Promise.resolve({ default: controller }));
|
|
276
190
|
}
|
|
277
191
|
configuredRouter.loadOperations(inputs.operations);
|
|
192
|
+
registerMiddlewareSeams(inputs, configuredRouter);
|
|
278
193
|
}
|
|
279
194
|
});
|
|
280
195
|
}
|
|
196
|
+
function registerMiddlewareSeams(inputs, configuredRouter) {
|
|
197
|
+
const executables = inputs.live.executables;
|
|
198
|
+
if (!executables || !executables.middleware.size) return;
|
|
199
|
+
const sourceResolver = new MiddlewareResolver(inputs.source.mainMiddlewareDir);
|
|
200
|
+
for (const spec of executables.middleware) {
|
|
201
|
+
configuredRouter.registerMiddleware(spec, async (context) => {
|
|
202
|
+
const original = await sourceResolver.resolve(spec);
|
|
203
|
+
return executables.main.replace("middleware", spec, original)(context);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function selectedMiddleware(operations) {
|
|
208
|
+
const identities = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const operation of operations.values()) for (const spec of operation.middleware) identities.add(spec);
|
|
210
|
+
return [...identities];
|
|
211
|
+
}
|
|
212
|
+
async function loadRendererLoaders(source, views, importer) {
|
|
213
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const view of views) {
|
|
215
|
+
for (const viewPath of [...view.layouts, view.viewPath]) {
|
|
216
|
+
const reference = source.rendererLoaderReference(viewPath);
|
|
217
|
+
if (!reference || loaders.has(reference.identity)) continue;
|
|
218
|
+
const mod = await importer(reference.file);
|
|
219
|
+
if (mod.default === void 0) {
|
|
220
|
+
throw new TestWoodError(`data loader ${reference.identity} (${reference.file}) has no default export`);
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
loaders.set(reference.identity, assertDataLoader(mod.default, `data loader ${reference.identity}`));
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw new TestWoodError(error instanceof Error ? error.message : String(error));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return loaders;
|
|
230
|
+
}
|
|
231
|
+
function rendererLoaderReferences(loaders) {
|
|
232
|
+
return [...loaders.keys()].map((identity) => Object.freeze({ path: identity, export: "default" }));
|
|
233
|
+
}
|
|
281
234
|
async function configureRendererRegistrations(inputs) {
|
|
282
235
|
const registrations = inputs.rendererRegistrations;
|
|
283
236
|
if (!registrations) return;
|
|
284
|
-
registrations.
|
|
285
|
-
registrations.
|
|
237
|
+
registrations.root.registerFunction(FETCH_RUNTIME, () => inputs.network, { owned: false, loadAs: LoadAs.Singleton });
|
|
238
|
+
registrations.declareBinding(Navigation, "singleton");
|
|
239
|
+
registrations.declareBinding(WOOD_FRONTEND_BRIDGE, "singleton");
|
|
240
|
+
const composePath = inputs.source.hookModule(inputs.source.config.renderer.compose);
|
|
241
|
+
if (composePath) {
|
|
242
|
+
const composeRenderer = await importDefaultExport(
|
|
243
|
+
inputs.state.importer,
|
|
244
|
+
composePath,
|
|
245
|
+
"renderer.compose"
|
|
246
|
+
);
|
|
247
|
+
if (typeof composeRenderer !== "function") {
|
|
248
|
+
throw new TestWoodError(`renderer.compose default export must be a function (${composePath})`);
|
|
249
|
+
}
|
|
250
|
+
await ExecutionContext.run(registrations.root, () => composeRenderer(registrations.root));
|
|
251
|
+
}
|
|
286
252
|
await ExecutionContext.run(
|
|
287
253
|
registrations.root,
|
|
288
254
|
() => inputs.live.composed?.configureRendererRoot?.(registrations.root)
|
|
@@ -299,62 +265,149 @@ function registerControllersBeforeOverlays(inputs, mainControllers, rendererCont
|
|
|
299
265
|
inputs.rendererRegistrations?.declare(controller);
|
|
300
266
|
}
|
|
301
267
|
}
|
|
302
|
-
function buildAssemblyIoc(inputs) {
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
268
|
+
function buildAssemblyIoc(inputs, controllers) {
|
|
269
|
+
const catalogs = [{
|
|
270
|
+
owner: "main",
|
|
271
|
+
bindings: describeTestBindings(testIoc().use(inputs.mainRegistrations.module()), [...controllers.main.values()]),
|
|
272
|
+
middleware: selectedMiddleware(inputs.operations),
|
|
273
|
+
loaders: []
|
|
274
|
+
}];
|
|
275
|
+
if (inputs.rendererRegistrations && inputs.catalog) {
|
|
276
|
+
catalogs.push({
|
|
277
|
+
owner: "renderer",
|
|
278
|
+
bindings: describeTestBindings(testIoc().use(inputs.rendererRegistrations.module()), [...controllers.renderer.values()]),
|
|
279
|
+
middleware: [],
|
|
280
|
+
loaders: rendererLoaderReferences(controllers.loaders)
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const overlays = resolveTestOverlays(inputs.state.overlays, catalogs);
|
|
284
|
+
const mainPlan = applyTestBindings(testIoc(), overlays, "main");
|
|
285
|
+
const rendererPlan = applyTestBindings(testIoc(), overlays, "renderer");
|
|
314
286
|
return {
|
|
315
|
-
main:
|
|
316
|
-
|
|
287
|
+
main: mainPlan.builder,
|
|
288
|
+
mainBorrowed: mainPlan.buildOptions.borrowedTokens ?? [],
|
|
289
|
+
renderer: rendererPlan.builder,
|
|
290
|
+
rendererBorrowed: rendererPlan.buildOptions.borrowedTokens ?? [],
|
|
291
|
+
overlays,
|
|
292
|
+
executables: {
|
|
293
|
+
main: createTestExecutables(overlays, "main"),
|
|
294
|
+
renderer: createTestExecutables(overlays, "renderer"),
|
|
295
|
+
middleware: new Set(overlays.filter((overlay) => overlay.owner === "main" && overlay.entry.kind === "middleware").map((overlay) => overlay.entry.target))
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function createRuntimeRollback(inputs) {
|
|
300
|
+
return (error) => {
|
|
301
|
+
if (inputs.live.rolledBack || inputs.live.disposed) return Promise.reject(error);
|
|
302
|
+
inputs.live.rolledBack = true;
|
|
303
|
+
return rollbackEnvironment(error, inputs.dispose, "testWood runtime initialization failed and rollback failed");
|
|
317
304
|
};
|
|
318
305
|
}
|
|
306
|
+
function assertRuntimeMayStart(inputs, stage) {
|
|
307
|
+
if (inputs.live.disposed) {
|
|
308
|
+
throw new TestWoodError(`this testWood environment was disposed before its ${stage} runtime was used`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function assertRuntimeMayContinue(inputs, stage) {
|
|
312
|
+
if (inputs.live.disposed) {
|
|
313
|
+
throw new TestWoodError(`this testWood environment was disposed while its ${stage} runtime was initializing`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function initializeMainRuntime(inputs) {
|
|
317
|
+
assertRuntimeMayStart(inputs, "main");
|
|
318
|
+
const root = inputs.live.env.root;
|
|
319
|
+
const router = await Promise.resolve(
|
|
320
|
+
ExecutionContext.run(root, () => root.get(WoodRouter))
|
|
321
|
+
);
|
|
322
|
+
inputs.live.router = router;
|
|
323
|
+
assertRuntimeMayContinue(inputs, "main");
|
|
324
|
+
await ExecutionContext.run(root, () => inputs.live.composed?.start?.());
|
|
325
|
+
assertRuntimeMayContinue(inputs, "main");
|
|
326
|
+
assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);
|
|
327
|
+
return { router };
|
|
328
|
+
}
|
|
329
|
+
async function initializeRendererRuntime(inputs, main, renderer) {
|
|
330
|
+
const { router } = await main.acquire();
|
|
331
|
+
assertRuntimeMayStart(inputs, "renderer");
|
|
332
|
+
const composed = inputs.live.composed;
|
|
333
|
+
const configurePageScope = composed?.configurePageScope?.bind(composed);
|
|
334
|
+
const hooks = {
|
|
335
|
+
events: renderer.events,
|
|
336
|
+
...configurePageScope ? { configurePageScope } : {}
|
|
337
|
+
};
|
|
338
|
+
const tier = await buildRendererTier({
|
|
339
|
+
source: inputs.source,
|
|
340
|
+
router,
|
|
341
|
+
catalog: inputs.catalog,
|
|
342
|
+
operations: inputs.operations,
|
|
343
|
+
importer: inputs.state.importer,
|
|
344
|
+
hooks,
|
|
345
|
+
dom: null,
|
|
346
|
+
ioc: renderer.ioc,
|
|
347
|
+
borrowedTokens: renderer.borrowedTokens,
|
|
348
|
+
registrations: inputs.rendererRegistrations,
|
|
349
|
+
controllerClasses: renderer.controllerClasses,
|
|
350
|
+
loaders: renderer.loaders,
|
|
351
|
+
loaderModules: renderer.loaderModules
|
|
352
|
+
});
|
|
353
|
+
inputs.live.renderer = tier;
|
|
354
|
+
assertRuntimeMayContinue(inputs, "renderer");
|
|
355
|
+
return tier;
|
|
356
|
+
}
|
|
357
|
+
function createAssemblyRuntime(inputs, renderer) {
|
|
358
|
+
const rollback = createRuntimeRollback(inputs);
|
|
359
|
+
const main = new LazyRuntimeStage(() => initializeMainRuntime(inputs), rollback);
|
|
360
|
+
const rendererStage = renderer ? new LazyRuntimeStage(() => initializeRendererRuntime(inputs, main, renderer), rollback) : null;
|
|
361
|
+
return { main, renderer: rendererStage };
|
|
362
|
+
}
|
|
319
363
|
async function materializeEnvironment(inputs) {
|
|
364
|
+
inputs.mainRegistrations.root.registerFunction(FETCH_RUNTIME, () => inputs.network, { owned: false, loadAs: LoadAs.Singleton });
|
|
320
365
|
const controllers = await loadAssemblyControllers(inputs);
|
|
321
366
|
inputs.live.composed = await runCompose(inputs.mainRegistrations.root, inputs.source, inputs.context);
|
|
367
|
+
assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);
|
|
322
368
|
installAssemblyRuntime(inputs, controllers.main);
|
|
323
369
|
await configureRendererRegistrations(inputs);
|
|
324
370
|
registerControllersBeforeOverlays(inputs, controllers.main, controllers.renderer);
|
|
325
|
-
const ioc = buildAssemblyIoc(inputs);
|
|
326
|
-
inputs.live.
|
|
371
|
+
const ioc = buildAssemblyIoc(inputs, controllers);
|
|
372
|
+
inputs.live.executables = ioc.executables;
|
|
373
|
+
inputs.live.env = await buildRecordedEnvironment(ioc.main, inputs.mainRegistrations, "main", ioc.mainBorrowed);
|
|
327
374
|
const root = inputs.live.env.root;
|
|
328
|
-
inputs.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
importer: inputs.state.importer,
|
|
340
|
-
hooks: inputs.live.composed,
|
|
341
|
-
dom: inputs.selection.dom,
|
|
342
|
-
ioc: ioc.renderer,
|
|
343
|
-
registrations: inputs.rendererRegistrations,
|
|
344
|
-
controllerClasses: controllers.renderer
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return environmentSurface({
|
|
375
|
+
const renderer = inputs.catalog && inputs.rendererRegistrations ? {
|
|
376
|
+
ioc: ioc.renderer,
|
|
377
|
+
borrowedTokens: ioc.rendererBorrowed,
|
|
378
|
+
controllerClasses: controllers.renderer,
|
|
379
|
+
events: inputs.live.composed?.events ?? inputs.context.events,
|
|
380
|
+
loaders: ioc.executables.renderer,
|
|
381
|
+
loaderModules: controllers.loaders
|
|
382
|
+
} : null;
|
|
383
|
+
const runtime = createAssemblyRuntime(inputs, renderer);
|
|
384
|
+
inputs.live.runtime = runtime;
|
|
385
|
+
const environment = environmentSurface({
|
|
348
386
|
root,
|
|
387
|
+
network: inputs.network,
|
|
349
388
|
env: inputs.live.env,
|
|
350
|
-
router: inputs.live.router,
|
|
351
389
|
operations: inputs.operations,
|
|
352
|
-
|
|
390
|
+
runtime,
|
|
391
|
+
allOperations: inputs.source.operations,
|
|
392
|
+
events: renderer?.events ?? inputs.live.composed?.events ?? inputs.context.events,
|
|
393
|
+
dom: false,
|
|
353
394
|
controllerScopes: inputs.controllerScopes,
|
|
395
|
+
executables: ioc.executables,
|
|
354
396
|
dispose: inputs.dispose
|
|
355
397
|
});
|
|
398
|
+
if (inputs.state.browserHost) {
|
|
399
|
+
const { createNativeBrowserEnvironment } = await import("./native_browser/native_browser_environment.js");
|
|
400
|
+
return createNativeBrowserEnvironment({
|
|
401
|
+
source: inputs.source,
|
|
402
|
+
views: inputs.catalog.all(),
|
|
403
|
+
environment,
|
|
404
|
+
network: inputs.network,
|
|
405
|
+
events: () => inputs.live.composed?.events ?? inputs.context.events
|
|
406
|
+
}, inputs.state.browserHost);
|
|
407
|
+
}
|
|
408
|
+
return environment;
|
|
356
409
|
}
|
|
357
|
-
async function rollbackEnvironment(error, dispose) {
|
|
410
|
+
async function rollbackEnvironment(error, dispose, message = "testWood build failed and rollback failed") {
|
|
358
411
|
try {
|
|
359
412
|
await dispose();
|
|
360
413
|
} catch (cleanupError) {
|
|
@@ -362,35 +415,48 @@ async function rollbackEnvironment(error, dispose) {
|
|
|
362
415
|
...error instanceof TestWoodCleanupError ? error.failures : [],
|
|
363
416
|
...cleanupError instanceof TestWoodCleanupError ? cleanupError.failures : [{ owner: "build rollback", error: cleanupError }]
|
|
364
417
|
];
|
|
365
|
-
throw new TestWoodCleanupError(
|
|
418
|
+
throw new TestWoodCleanupError(message, failures, error);
|
|
366
419
|
}
|
|
367
420
|
throw error;
|
|
368
421
|
}
|
|
369
|
-
async function assembleEnvironment(state,
|
|
422
|
+
async function assembleEnvironment(state, selected) {
|
|
370
423
|
const source = loadWoodConfigSource(state.configPath);
|
|
424
|
+
let selection = selected ?? resolveSelection(
|
|
425
|
+
Object.fromEntries(DECLARED_OWNERS.filter((owner) => source.config[owner]).map((owner) => [owner, {}]))
|
|
426
|
+
);
|
|
371
427
|
const operations = selectOperations(source.operations, selection.main);
|
|
372
|
-
const catalog =
|
|
373
|
-
if (
|
|
374
|
-
throw new TestWoodError("
|
|
428
|
+
const catalog = selectRendererCatalog(source.views, selection);
|
|
429
|
+
if (state.browserHost && !catalog) {
|
|
430
|
+
throw new TestWoodError("browser() requires a selected renderer owner; add renderer to select()");
|
|
375
431
|
}
|
|
432
|
+
if (state.browserHost && stubEntries(state.overlays).some((entry) => entry.kind === "load" || entry.owner === "renderer")) {
|
|
433
|
+
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.");
|
|
434
|
+
}
|
|
435
|
+
const network = createTestFetch(state.fetchConfigurations.flatMap(defineFetchRoutes));
|
|
376
436
|
const registryBefore = globalRegistrySize();
|
|
377
|
-
const mainRegistrations = new RegistrationOnlyRoot("main.testing.compose");
|
|
378
|
-
const rendererRegistrations = catalog ? new RegistrationOnlyRoot("configureRendererRoot") : null;
|
|
437
|
+
const mainRegistrations = new RegistrationOnlyRoot("main.testing.compose", source.configPath);
|
|
438
|
+
const rendererRegistrations = catalog && !state.browserHost ? new RegistrationOnlyRoot("configureRendererRoot", source.configPath) : null;
|
|
379
439
|
const controllerScopes = [];
|
|
380
440
|
const live = {
|
|
381
441
|
composed: null,
|
|
382
442
|
env: null,
|
|
383
443
|
renderer: null,
|
|
384
|
-
router: null
|
|
444
|
+
router: null,
|
|
445
|
+
executables: null,
|
|
446
|
+
runtime: null,
|
|
447
|
+
disposed: false,
|
|
448
|
+
rolledBack: false
|
|
385
449
|
};
|
|
386
450
|
const context = createComposeContext(source, state, selection, operations, live);
|
|
387
451
|
const dispose = createAssemblyDisposer(
|
|
388
452
|
live,
|
|
389
453
|
mainRegistrations,
|
|
390
454
|
rendererRegistrations,
|
|
391
|
-
controllerScopes
|
|
455
|
+
controllerScopes,
|
|
456
|
+
network
|
|
392
457
|
);
|
|
393
458
|
const inputs = {
|
|
459
|
+
network,
|
|
394
460
|
state,
|
|
395
461
|
selection,
|
|
396
462
|
source,
|
|
@@ -410,24 +476,52 @@ async function assembleEnvironment(state, selection) {
|
|
|
410
476
|
return rollbackEnvironment(error, dispose);
|
|
411
477
|
}
|
|
412
478
|
}
|
|
479
|
+
const OPEN_FIRST = "await open(page) (or another renderer call) first";
|
|
413
480
|
function environmentSurface(parts) {
|
|
414
|
-
const { root, env,
|
|
415
|
-
const
|
|
416
|
-
const
|
|
481
|
+
const { root, env, operations, allOperations, runtime, events, dom: domSelected, controllerScopes, executables, dispose } = parts;
|
|
482
|
+
const pages = () => runtime.renderer ?? tierMissing("pages", "renderer");
|
|
483
|
+
const dom = async () => {
|
|
484
|
+
if (!domSelected) tierMissing("dom", "dom driver");
|
|
485
|
+
return (await pages().acquire()).dom;
|
|
486
|
+
};
|
|
487
|
+
const router = lazyMember(
|
|
488
|
+
"router",
|
|
489
|
+
runtime.main.map((main) => main.router),
|
|
490
|
+
["dispatch"],
|
|
491
|
+
"await call()/get()/controller() first"
|
|
492
|
+
);
|
|
493
|
+
const frontend = runtime.renderer ? lazyMember(
|
|
494
|
+
"frontend",
|
|
495
|
+
runtime.renderer.map((tier) => tier.frontend),
|
|
496
|
+
["open", "act", "settle", "dispose"],
|
|
497
|
+
OPEN_FIRST
|
|
498
|
+
) : null;
|
|
499
|
+
const rendererRoot = runtime.renderer ? lazyMember(
|
|
500
|
+
"rendererRoot",
|
|
501
|
+
runtime.renderer.map((tier) => tier.rendererRoot),
|
|
502
|
+
["get", "dispose"],
|
|
503
|
+
OPEN_FIRST
|
|
504
|
+
) : null;
|
|
505
|
+
const dispatchOperation = async (operation, input) => {
|
|
506
|
+
if (!operations.has(operation)) {
|
|
507
|
+
throw new TestWoodError(
|
|
508
|
+
`operation "${operation}" is not in the selected aperture; available operations:
|
|
509
|
+
${[...operations.keys()].join("\n ")}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const { router: live } = await runtime.main.acquire();
|
|
513
|
+
return await ExecutionContext.run(root, () => live.dispatch(operation, input));
|
|
514
|
+
};
|
|
515
|
+
const api = createOperationApi(allOperations.keys(), dispatchOperation);
|
|
417
516
|
return {
|
|
418
517
|
root,
|
|
419
518
|
router,
|
|
420
519
|
operations,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
`operation "${operation}" is not in the selected aperture; available operations:
|
|
425
|
-
${[...operations.keys()].join("\n ")}`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
return await ExecutionContext.run(root, () => router.dispatch(operation, input));
|
|
429
|
-
},
|
|
520
|
+
api,
|
|
521
|
+
rawApi: api,
|
|
522
|
+
call: dispatchOperation,
|
|
430
523
|
controller: async (controller) => {
|
|
524
|
+
await runtime.main.acquire();
|
|
431
525
|
const scope = root.extend();
|
|
432
526
|
const ownsController = !root.isRegistered(controller);
|
|
433
527
|
let instance;
|
|
@@ -442,41 +536,54 @@ function environmentSurface(parts) {
|
|
|
442
536
|
guardControllerLifecycleOnce(instance);
|
|
443
537
|
return instance;
|
|
444
538
|
},
|
|
445
|
-
get: async (token) =>
|
|
539
|
+
get: async (token) => {
|
|
540
|
+
await runtime.main.acquire();
|
|
541
|
+
return await ExecutionContext.run(root, () => root.get(token));
|
|
542
|
+
},
|
|
446
543
|
get frontend() {
|
|
447
|
-
return pages
|
|
544
|
+
return frontend ?? tierMissing("pages", "renderer");
|
|
448
545
|
},
|
|
449
546
|
get events() {
|
|
450
|
-
return pages
|
|
547
|
+
return events ?? tierMissing("pages", "renderer");
|
|
451
548
|
},
|
|
452
549
|
get rendererRoot() {
|
|
453
|
-
return pages
|
|
550
|
+
return rendererRoot ?? tierMissing("pages", "renderer");
|
|
551
|
+
},
|
|
552
|
+
open: async (page, params, query) => (await pages().acquire()).open(page, params, query),
|
|
553
|
+
navigate: async (page, params, query) => (await pages().acquire()).navigate(page, params, query),
|
|
554
|
+
current: () => pages().current?.current() ?? null,
|
|
555
|
+
settle: async () => {
|
|
556
|
+
await parts.network.settle();
|
|
557
|
+
if (runtime.main.requested) await (await runtime.main.acquire()).router.drain();
|
|
558
|
+
if (runtime.renderer?.requested) await (await runtime.renderer.acquire()).settle();
|
|
454
559
|
},
|
|
455
|
-
open: async (page, params, query) => pages().open(page, params, query),
|
|
456
|
-
navigate: async (page, params, query) => pages().navigate(page, params, query),
|
|
457
|
-
current: () => pages().current(),
|
|
458
|
-
settle: async () => pages().settle(),
|
|
459
560
|
get target() {
|
|
460
|
-
|
|
561
|
+
if (!domSelected) tierMissing("dom", "dom driver");
|
|
562
|
+
const tier = pages().current;
|
|
563
|
+
if (!tier) throw new TestWoodError("no page is mounted; open(page) first");
|
|
564
|
+
return tier.dom.target;
|
|
461
565
|
},
|
|
462
|
-
click: async (selector) => dom().click(selector),
|
|
463
|
-
type: async (selector, text) => dom().type(selector, text),
|
|
464
|
-
submit: async (selector) => dom().submit(selector),
|
|
465
|
-
text: async (selector) => dom().text(selector),
|
|
466
|
-
exists: async (selector) => dom().exists(selector),
|
|
566
|
+
click: async (selector) => (await dom()).click(selector),
|
|
567
|
+
type: async (selector, text) => (await dom()).type(selector, text),
|
|
568
|
+
submit: async (selector) => (await dom()).submit(selector),
|
|
569
|
+
text: async (selector) => (await dom()).text(selector),
|
|
570
|
+
exists: async (selector) => (await dom()).exists(selector),
|
|
467
571
|
// Method expectations only: `settle()` is the completion contract for
|
|
468
572
|
// owned renderer work, and a test may legitimately verify while a
|
|
469
573
|
// deliberately deferred stream is still in flight.
|
|
470
574
|
verify: async () => {
|
|
471
575
|
const failures = [];
|
|
472
576
|
await env.verify().catch((error) => failures.push({ owner: "main", error }));
|
|
577
|
+
await executables.main.verify().catch((error) => failures.push({ owner: "main middleware", error }));
|
|
578
|
+
const renderer = runtime.renderer?.current ?? null;
|
|
473
579
|
if (renderer) {
|
|
474
580
|
await renderer.verify().catch((error) => failures.push({ owner: "renderer", error }));
|
|
475
581
|
}
|
|
582
|
+
await executables.renderer.verify().catch((error) => failures.push({ owner: "renderer loaders", error }));
|
|
476
583
|
if (failures.length === 1) throw failures[0].error;
|
|
477
584
|
if (failures.length) {
|
|
478
585
|
throw new TestWoodError(
|
|
479
|
-
"testWood verification failed in
|
|
586
|
+
"testWood verification failed in several owners:\n " + failures.map(({ owner, error }) => `${owner}: ${error instanceof Error ? error.message : String(error)}`).join("\n ")
|
|
480
587
|
);
|
|
481
588
|
}
|
|
482
589
|
},
|
|
@@ -495,60 +602,79 @@ class TestWoodBuilder {
|
|
|
495
602
|
return new TestWoodBuilder({
|
|
496
603
|
configPath: path.resolve(configPath),
|
|
497
604
|
selection: null,
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
importer: defaultImporter
|
|
605
|
+
browserHost: null,
|
|
606
|
+
fetchConfigurations: [],
|
|
607
|
+
importer: defaultImporter,
|
|
608
|
+
overlays: testStub()
|
|
501
609
|
});
|
|
502
610
|
}
|
|
503
|
-
|
|
611
|
+
fetch(configure) {
|
|
612
|
+
if (typeof configure !== "function") throw new TypeError("fetch() requires a route configuration callback");
|
|
613
|
+
return this.derive({ fetchConfigurations: Object.freeze([...this.state.fetchConfigurations, configure]) });
|
|
614
|
+
}
|
|
615
|
+
browser(host = "playwright") {
|
|
616
|
+
if (host !== "playwright" && host !== "happy_dom") throw new TestWoodError("browser() accepts playwright or happy_dom");
|
|
617
|
+
return new TestWoodBuilder({ ...this.state, browserHost: host });
|
|
618
|
+
}
|
|
619
|
+
/** Declare selected tiers, snapshotting the source selector at call time. */
|
|
504
620
|
select(selection) {
|
|
505
|
-
resolveSelection(selection);
|
|
506
|
-
return this.derive({ selection
|
|
621
|
+
const resolved = resolveSelection(selection);
|
|
622
|
+
return this.derive({ selection: Object.freeze({
|
|
623
|
+
...resolved.main !== null ? { main: resolved.main } : {},
|
|
624
|
+
...resolved.renderer !== null ? { renderer: resolved.renderer } : {}
|
|
625
|
+
}) });
|
|
507
626
|
}
|
|
508
627
|
/** Route hook/controller imports through a caller-owned importer (escape hatch). */
|
|
509
628
|
importer(importer) {
|
|
510
629
|
if (typeof importer !== "function") throw new TestWoodError("importer() expects a function");
|
|
511
630
|
return this.derive({ importer });
|
|
512
631
|
}
|
|
513
|
-
/**
|
|
514
|
-
main(compose) {
|
|
515
|
-
return this.append("main", recordComposer(compose));
|
|
516
|
-
}
|
|
517
|
-
/** Explicitly compose or replace dependencies on the renderer root. */
|
|
518
|
-
renderer(compose) {
|
|
519
|
-
return this.append("renderer", recordComposer(compose));
|
|
520
|
-
}
|
|
521
|
-
/** Reusable presets retain the historical main-root default. */
|
|
632
|
+
/** Compose a reusable canonical testStub() replacement description. */
|
|
522
633
|
use(preset) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
634
|
+
if (!(preset instanceof TestStub)) throw new TestWoodError("use() requires testStub()");
|
|
635
|
+
return this.derive({ overlays: this.state.overlays.use(preset) });
|
|
636
|
+
}
|
|
637
|
+
/* -- canonical singular replacements (shared @noego/testing engine) ------ */
|
|
638
|
+
/** Replace the class bound to an exact declared token; the declared lifetime is kept. */
|
|
639
|
+
class(target, replacement, options) {
|
|
640
|
+
return this.derive({ overlays: this.state.overlays.class(target, replacement, options) });
|
|
641
|
+
}
|
|
642
|
+
/** Replace a declared factory/provider (dependencies and lifetime kept) or a callable value. */
|
|
643
|
+
function(target, replacement, options) {
|
|
644
|
+
return this.derive({ overlays: this.state.overlays.function(target, replacement, options) });
|
|
645
|
+
}
|
|
646
|
+
/** A test.* method seam on the FINAL binding of an exact declared token. */
|
|
647
|
+
method(target, method, replacement, options) {
|
|
648
|
+
return this.derive({ overlays: this.state.overlays.method(target, method, replacement, options) });
|
|
649
|
+
}
|
|
650
|
+
/** Provide a caller-owned value for an exact declared token (never disposed by the app). */
|
|
651
|
+
value(target, replacement, options) {
|
|
652
|
+
return this.derive({ overlays: this.state.overlays.value(target, replacement, options) });
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Replace the executable satisfying a production middleware identity
|
|
656
|
+
* (`file.path:selector` as written in the operations source). Membership,
|
|
657
|
+
* order and inheritance always come from the source.
|
|
658
|
+
*/
|
|
659
|
+
middleware(identity, replacement, options) {
|
|
660
|
+
return this.derive({ overlays: this.state.overlays.middleware(identity, replacement, options) });
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Replace the DATA LOADER behind a selected page/layout: the companion
|
|
664
|
+
* `.load` source of a view (`pages/chat.svelte` → `pages/chat.load.ts`).
|
|
665
|
+
* The identity is the config-relative source path (`ui/pages/chat.load.ts`),
|
|
666
|
+
* export `default`; the executable receives the loader `RequestData`
|
|
667
|
+
* (`url` = page identity, `params`, `query`) and yields the data the
|
|
668
|
+
* controller's `initialize({ params, query, data })` receives. A source
|
|
669
|
+
* with no companion loader is not a declared identity (build error).
|
|
670
|
+
* Controllers are replaced with `.class`, never here.
|
|
671
|
+
*/
|
|
672
|
+
load(identity, replacement, options) {
|
|
673
|
+
return this.derive({ overlays: this.state.overlays.load(identity, replacement, options) });
|
|
674
|
+
}
|
|
675
|
+
/** Build; without `select()`, every owner the config declares is selected whole. */
|
|
541
676
|
async build() {
|
|
542
|
-
|
|
543
|
-
throw new TestWoodError("select() a tier before build(): { main }, { pages }, or both");
|
|
544
|
-
}
|
|
545
|
-
return assembleEnvironment(this.state, resolveSelection(this.state.selection));
|
|
546
|
-
}
|
|
547
|
-
append(owner, calls) {
|
|
548
|
-
const start = this.state.mainCalls.length + this.state.rendererCalls.length;
|
|
549
|
-
const ordered = calls.map((call, index) => ({ ...call, order: start + index }));
|
|
550
|
-
applyCalls(testIoc(), ordered);
|
|
551
|
-
return owner === "main" ? this.derive({ mainCalls: [...this.state.mainCalls, ...ordered] }) : this.derive({ rendererCalls: [...this.state.rendererCalls, ...ordered] });
|
|
677
|
+
return await assembleEnvironment(this.state, this.state.selection ? resolveSelection(this.state.selection) : null);
|
|
552
678
|
}
|
|
553
679
|
derive(patch) {
|
|
554
680
|
return new TestWoodBuilder({ ...this.state, ...patch });
|