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