@noego/wood 0.6.2 → 0.7.1
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 +407 -273
- 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 +416 -271
- 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 +31 -4
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +14 -1
- package/dist/testing/wood_env_renderer.d.ts +14 -1
- package/dist/testing/wood_env_renderer.js +31 -4
- 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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
const logs = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function derive(builder, method, args) {
|
|
4
|
+
const state = logs.get(builder);
|
|
5
|
+
const copied = [...args];
|
|
6
|
+
const optionsIndex = method === "method" ? 3 : 2;
|
|
7
|
+
if (!["select", "use"].includes(method) && copied[optionsIndex] !== void 0) {
|
|
8
|
+
copied[optionsIndex] = Object.freeze({ ...copied[optionsIndex] });
|
|
9
|
+
}
|
|
10
|
+
if (method === "load" && typeof copied[0] !== "string") copied[0] = Object.freeze({ ...copied[0] });
|
|
11
|
+
const child = Object.create(WoodTestBuilder.prototype);
|
|
12
|
+
logs.set(child, {
|
|
13
|
+
config: state.config,
|
|
14
|
+
commands: Object.freeze([...state.commands, Object.freeze({ method, args: Object.freeze(copied) })])
|
|
15
|
+
});
|
|
16
|
+
return Object.freeze(child);
|
|
17
|
+
}
|
|
18
|
+
function plain(value) {
|
|
19
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
20
|
+
}
|
|
21
|
+
function copyOwner(owner, input) {
|
|
22
|
+
if (!plain(input) || Reflect.ownKeys(input).some((key) => key !== "module" && key !== "path") || Reflect.ownKeys(input).length > 1) {
|
|
23
|
+
throw new TypeError(`select({ ${owner} }) takes module OR path, or {}`);
|
|
24
|
+
}
|
|
25
|
+
const value = { ...input };
|
|
26
|
+
if (typeof value.module === "string") value.module = Object.freeze([value.module]);
|
|
27
|
+
else if (Array.isArray(value.module)) value.module = Object.freeze([...value.module]);
|
|
28
|
+
if (Array.isArray(value.path)) value.path = Object.freeze([...value.path]);
|
|
29
|
+
return Object.freeze(value);
|
|
30
|
+
}
|
|
31
|
+
function copySelection(selection) {
|
|
32
|
+
if (!plain(selection) || Reflect.ownKeys(selection).some((key) => key !== "main" && key !== "renderer")) {
|
|
33
|
+
throw new TypeError("select requires { main } and/or { renderer }");
|
|
34
|
+
}
|
|
35
|
+
const out = {};
|
|
36
|
+
if ("main" in selection) out.main = copyOwner("main", selection.main);
|
|
37
|
+
if ("renderer" in selection) out.renderer = copyOwner("renderer", selection.renderer);
|
|
38
|
+
if (!("main" in out) && !("renderer" in out)) throw new TypeError("select requires { main } and/or { renderer }");
|
|
39
|
+
return Object.freeze(out);
|
|
40
|
+
}
|
|
41
|
+
function identity(value, what) {
|
|
42
|
+
if (typeof value !== "string" || !value.trim()) throw new TypeError(`${what} requires a nonempty identity`);
|
|
43
|
+
}
|
|
44
|
+
class WoodTestBuilder {
|
|
45
|
+
constructor() {
|
|
46
|
+
}
|
|
47
|
+
fetch(configure) {
|
|
48
|
+
if (typeof configure !== "function") throw new TypeError("fetch() requires a route configuration callback");
|
|
49
|
+
return derive(this, "fetch", [configure]);
|
|
50
|
+
}
|
|
51
|
+
browser(host = "playwright") {
|
|
52
|
+
if (host !== "playwright" && host !== "happy_dom") throw new TypeError("browser() accepts playwright or happy_dom");
|
|
53
|
+
return derive(this, "browser", [host]);
|
|
54
|
+
}
|
|
55
|
+
select(selection) {
|
|
56
|
+
return derive(this, "select", [copySelection(selection)]);
|
|
57
|
+
}
|
|
58
|
+
use(stub) {
|
|
59
|
+
if (!stub || typeof stub !== "object") throw new TypeError("use() expects a testStub() description");
|
|
60
|
+
return derive(this, "use", [stub]);
|
|
61
|
+
}
|
|
62
|
+
class(target, replacement, options) {
|
|
63
|
+
return derive(this, "class", options === void 0 ? [target, replacement] : [target, replacement, options]);
|
|
64
|
+
}
|
|
65
|
+
function(target, replacement, options) {
|
|
66
|
+
return derive(this, "function", options === void 0 ? [target, replacement] : [target, replacement, options]);
|
|
67
|
+
}
|
|
68
|
+
method(target, method, replacement, options) {
|
|
69
|
+
identity(method, "method()");
|
|
70
|
+
return derive(this, "method", options === void 0 ? [target, method, replacement] : [target, method, replacement, options]);
|
|
71
|
+
}
|
|
72
|
+
value(target, replacement, options) {
|
|
73
|
+
return derive(this, "value", options === void 0 ? [target, replacement] : [target, replacement, options]);
|
|
74
|
+
}
|
|
75
|
+
middleware(identity_, replacement, options) {
|
|
76
|
+
identity(identity_, "middleware()");
|
|
77
|
+
return derive(this, "middleware", options === void 0 ? [identity_, replacement] : [identity_, replacement, options]);
|
|
78
|
+
}
|
|
79
|
+
load(identity_, replacement, options) {
|
|
80
|
+
return derive(this, "load", options === void 0 ? [identity_, replacement] : [identity_, replacement, options]);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Load the implementation and replay the recorded calls in order. Under
|
|
84
|
+
* Vitest the build binds to the project-local runner's module registry so
|
|
85
|
+
* production modules keep the identity the test file imported.
|
|
86
|
+
*/
|
|
87
|
+
async build() {
|
|
88
|
+
const state = logs.get(this);
|
|
89
|
+
const implementation = await import("./testing/index.js");
|
|
90
|
+
let builder = implementation.testWood(state.config);
|
|
91
|
+
if (process.env.VITEST) builder = builder.importer(await implementation.vitestModuleImporter(state.config));
|
|
92
|
+
for (const command of state.commands) {
|
|
93
|
+
if (command.method === "use") {
|
|
94
|
+
const { stubEntries } = await import("@noego/testing");
|
|
95
|
+
stubEntries(command.args[0]);
|
|
96
|
+
}
|
|
97
|
+
builder = builder[command.method].apply(builder, [...command.args]);
|
|
98
|
+
}
|
|
99
|
+
const environment = await builder.build();
|
|
100
|
+
const { registerTestResource } = await import("@noego/testing");
|
|
101
|
+
return await registerTestResource(environment, "testWood");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function testWood(configPath) {
|
|
105
|
+
const config = configPath instanceof URL ? fileURLToPath(configPath) : typeof configPath === "string" && configPath.startsWith("file:") ? fileURLToPath(configPath) : configPath;
|
|
106
|
+
if (typeof config !== "string" || !config.trim()) throw new TypeError("testWood requires an original wood.config.yml path or file URL");
|
|
107
|
+
const builder = Object.create(WoodTestBuilder.prototype);
|
|
108
|
+
logs.set(builder, { config, commands: Object.freeze([]) });
|
|
109
|
+
return Object.freeze(builder);
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
WoodTestBuilder,
|
|
113
|
+
testWood
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=test-wood.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/test-wood.ts"],"sourcesContent":["import type { ConfigureFetch } from '@noego/testing/fetch';\n/**\n * Production-safe Wood testing facade, exported from the `@noego/wood` root.\n *\n * Importing `@noego/wood` or declaring a test loads no testing, DOM,\n * Playwright or Electron modules: every call is recorded on an immutable\n * builder and replayed onto the real `@noego/wood/testing` implementation\n * only inside `build()`.\n *\n * The canonical surface is exactly:\n *\n * select({ main?, renderer? }) what exists (module | path | {} per owner)\n * use(testStub()) compose a shared replacement description\n * class / function / method / value exact declared tokens\n * middleware(identity, …) the executable behind a source middleware id\n * load(identity, …) the page/layout companion data loader\n * browser([happy_dom]) native Page (default) or happy-dom Window\n * build() no selection = every declared owner, whole\n *\n * Explicit browser() selects a native renderer host. Without it, renderer\n * selection stays in the fast in-process Node controller tier.\n */\nimport { fileURLToPath } from 'node:url';\nimport type {\n ExecutableReplacement,\n LoaderIdentity,\n MethodDescriptor,\n OwnerSelection,\n ClassToken, ReplacementClass,\n ReplacementFunction,\n ReplacementOptions,\n TestStub,\n} from '@noego/testing';\nimport type { TestWoodBuilder } from './testing/index.js';\nimport type { WoodBrowserHost, WoodTestEnvironmentFor } from './testing/native_browser/native_browser.types.js';\n\nexport interface WoodTestSelection {\n readonly main?: OwnerSelection;\n readonly renderer?: OwnerSelection;\n}\n\ntype Method = 'select' | 'use' | 'class' | 'function' | 'method' | 'value' | 'middleware' | 'load' | 'browser' | 'fetch';\ntype Command = Readonly<{ method: Method; args: readonly unknown[] }>;\nconst logs = new WeakMap<object, { config: string; commands: readonly Command[] }>();\n\nfunction derive<H extends WoodBrowserHost | null>(builder: WoodTestBuilder<H>, method: Method, args: readonly unknown[]): WoodTestBuilder<H> {\n const state = logs.get(builder)!;\n const copied = [...args];\n const optionsIndex = method === 'method' ? 3 : 2;\n if (!['select', 'use'].includes(method) && copied[optionsIndex] !== undefined) {\n copied[optionsIndex] = Object.freeze({ ...(copied[optionsIndex] as ReplacementOptions) });\n }\n if (method === 'load' && typeof copied[0] !== 'string') copied[0] = Object.freeze({ ...(copied[0] as object) });\n const child = Object.create(WoodTestBuilder.prototype) as WoodTestBuilder<H>;\n logs.set(child, {\n config: state.config,\n commands: Object.freeze([...state.commands, Object.freeze({ method, args: Object.freeze(copied) })]),\n });\n return Object.freeze(child);\n}\n\nfunction plain(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);\n}\nfunction copyOwner(owner: string, input: unknown): OwnerSelection {\n if (!plain(input)\n || Reflect.ownKeys(input).some(key => key !== 'module' && key !== 'path')\n || Reflect.ownKeys(input).length > 1) {\n throw new TypeError(`select({ ${owner} }) takes module OR path, or {}`);\n }\n const value = { ...(input as Record<string, unknown>) };\n if (typeof value.module === 'string') value.module = Object.freeze([value.module]);\n else if (Array.isArray(value.module)) value.module = Object.freeze([...value.module]);\n if (Array.isArray(value.path)) value.path = Object.freeze([...value.path]);\n return Object.freeze(value) as OwnerSelection;\n}\n\n/** Structural copy only; owner contents are validated by the shared contract at build. */\nfunction copySelection(selection: WoodTestSelection): WoodTestSelection {\n if (!plain(selection)\n || Reflect.ownKeys(selection).some(key => key !== 'main' && key !== 'renderer')) {\n throw new TypeError('select requires { main } and/or { renderer }');\n }\n const out: { main?: OwnerSelection; renderer?: OwnerSelection } = {};\n if ('main' in selection) out.main = copyOwner('main', selection.main);\n if ('renderer' in selection) out.renderer = copyOwner('renderer', selection.renderer);\n if (!('main' in out) && !('renderer' in out)) throw new TypeError('select requires { main } and/or { renderer }');\n return Object.freeze(out);\n}\n\nfunction identity(value: unknown, what: string): void {\n if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${what} requires a nonempty identity`);\n}\n\n/** The root-export builder: a recorder over the `@noego/wood/testing` builder's canonical surface. */\nexport class WoodTestBuilder<H extends WoodBrowserHost | null = null> {\n private constructor() {}\n fetch(configure: ConfigureFetch): WoodTestBuilder<H> {\n if (typeof configure !== 'function') throw new TypeError('fetch() requires a route configuration callback');\n return derive(this, 'fetch', [configure]);\n }\n /** No argument = Playwright/Chromium; happy_dom = a native happy-dom Window. */\n browser(): WoodTestBuilder<'playwright'>;\n browser(host: 'playwright'): WoodTestBuilder<'playwright'>;\n browser(host: 'happy_dom'): WoodTestBuilder<'happy_dom'>;\n browser(host: WoodBrowserHost = 'playwright'): WoodTestBuilder<WoodBrowserHost> {\n if (host !== 'playwright' && host !== 'happy_dom') throw new TypeError('browser() accepts playwright or happy_dom');\n return derive(this, 'browser', [host]) as WoodTestBuilder<WoodBrowserHost>;\n }\n select(selection: WoodTestSelection): WoodTestBuilder<H> { return derive(this, 'select', [copySelection(selection)]); }\n use(stub: TestStub): WoodTestBuilder<H> {\n if (!stub || typeof stub !== 'object') throw new TypeError('use() expects a testStub() description');\n return derive(this, 'use', [stub]);\n }\n class<T>(target: ClassToken<T>, replacement: ReplacementClass<T>, options?: ReplacementOptions): WoodTestBuilder<H> {\n return derive(this, 'class', options === undefined ? [target, replacement] : [target, replacement, options]);\n }\n function(target: unknown, replacement: ReplacementFunction, options?: ReplacementOptions): WoodTestBuilder<H> {\n return derive(this, 'function', options === undefined ? [target, replacement] : [target, replacement, options]);\n }\n method(target: unknown, method: string, replacement: MethodDescriptor, options?: ReplacementOptions): WoodTestBuilder<H> {\n identity(method, 'method()');\n return derive(this, 'method', options === undefined ? [target, method, replacement] : [target, method, replacement, options]);\n }\n value(target: unknown, replacement: unknown, options?: ReplacementOptions): WoodTestBuilder<H> {\n return derive(this, 'value', options === undefined ? [target, replacement] : [target, replacement, options]);\n }\n middleware(identity_: string, replacement: ExecutableReplacement, options?: ReplacementOptions): WoodTestBuilder<H> {\n identity(identity_, 'middleware()');\n return derive(this, 'middleware', options === undefined ? [identity_, replacement] : [identity_, replacement, options]);\n }\n load(identity_: LoaderIdentity, replacement: ExecutableReplacement, options?: ReplacementOptions): WoodTestBuilder<H> {\n return derive(this, 'load', options === undefined ? [identity_, replacement] : [identity_, replacement, options]);\n }\n /**\n * Load the implementation and replay the recorded calls in order. Under\n * Vitest the build binds to the project-local runner's module registry so\n * production modules keep the identity the test file imported.\n */\n async build(): Promise<WoodTestEnvironmentFor<H>> {\n const state = logs.get(this)!;\n const implementation = await import('./testing/index.js');\n let builder: TestWoodBuilder = implementation.testWood(state.config);\n if (process.env.VITEST) builder = builder.importer(await implementation.vitestModuleImporter(state.config));\n for (const command of state.commands) {\n if (command.method === 'use') {\n const { stubEntries } = await import('@noego/testing');\n stubEntries(command.args[0] as TestStub); // reject legacy IoC presets on the canonical root facade\n }\n builder = (builder[command.method] as (...args: unknown[]) => TestWoodBuilder).apply(builder, [...command.args]);\n }\n const environment = await builder.build();\n const { registerTestResource } = await import('@noego/testing');\n return await registerTestResource(environment, 'testWood') as WoodTestEnvironmentFor<H>;\n }\n}\n\nexport function testWood(configPath: string | URL): WoodTestBuilder {\n const config = configPath instanceof URL ? fileURLToPath(configPath)\n : typeof configPath === 'string' && configPath.startsWith('file:') ? fileURLToPath(configPath) : configPath;\n if (typeof config !== 'string' || !config.trim()) throw new TypeError('testWood requires an original wood.config.yml path or file URL');\n const builder = Object.create(WoodTestBuilder.prototype) as WoodTestBuilder;\n logs.set(builder, { config, commands: Object.freeze([]) });\n return Object.freeze(builder);\n}\n"],"mappings":"AAsBA,SAAS,qBAAqB;AAqB9B,MAAM,OAAO,oBAAI,QAAkE;AAEnF,SAAS,OAAyC,SAA6B,QAAgB,MAA8C;AAC3I,QAAM,QAAQ,KAAK,IAAI,OAAO;AAC9B,QAAM,SAAS,CAAC,GAAG,IAAI;AACvB,QAAM,eAAe,WAAW,WAAW,IAAI;AAC/C,MAAI,CAAC,CAAC,UAAU,KAAK,EAAE,SAAS,MAAM,KAAK,OAAO,YAAY,MAAM,QAAW;AAC7E,WAAO,YAAY,IAAI,OAAO,OAAO,EAAE,GAAI,OAAO,YAAY,EAAyB,CAAC;AAAA,EAC1F;AACA,MAAI,WAAW,UAAU,OAAO,OAAO,CAAC,MAAM,SAAU,QAAO,CAAC,IAAI,OAAO,OAAO,EAAE,GAAI,OAAO,CAAC,EAAa,CAAC;AAC9G,QAAM,QAAQ,OAAO,OAAO,gBAAgB,SAAS;AACrD,OAAK,IAAI,OAAO;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,UAAU,OAAO,OAAO,CAAC,GAAG,MAAM,UAAU,OAAO,OAAO,EAAE,QAAQ,MAAM,OAAO,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;AAAA,EACrG,CAAC;AACD,SAAO,OAAO,OAAO,KAAK;AAC5B;AAEA,SAAS,MAAM,OAAkD;AAC/D,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,MAC7D,OAAO,eAAe,KAAK,MAAM,OAAO,aAAa,OAAO,eAAe,KAAK,MAAM;AAC9F;AACA,SAAS,UAAU,OAAe,OAAgC;AAChE,MAAI,CAAC,MAAM,KAAK,KACX,QAAQ,QAAQ,KAAK,EAAE,KAAK,SAAO,QAAQ,YAAY,QAAQ,MAAM,KACrE,QAAQ,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,IAAI,UAAU,YAAY,KAAK,iCAAiC;AAAA,EACxE;AACA,QAAM,QAAQ,EAAE,GAAI,MAAkC;AACtD,MAAI,OAAO,MAAM,WAAW,SAAU,OAAM,SAAS,OAAO,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,WACxE,MAAM,QAAQ,MAAM,MAAM,EAAG,OAAM,SAAS,OAAO,OAAO,CAAC,GAAG,MAAM,MAAM,CAAC;AACpF,MAAI,MAAM,QAAQ,MAAM,IAAI,EAAG,OAAM,OAAO,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,CAAC;AACzE,SAAO,OAAO,OAAO,KAAK;AAC5B;AAGA,SAAS,cAAc,WAAiD;AACtE,MAAI,CAAC,MAAM,SAAS,KACf,QAAQ,QAAQ,SAAS,EAAE,KAAK,SAAO,QAAQ,UAAU,QAAQ,UAAU,GAAG;AACjF,UAAM,IAAI,UAAU,8CAA8C;AAAA,EACpE;AACA,QAAM,MAA4D,CAAC;AACnE,MAAI,UAAU,UAAW,KAAI,OAAO,UAAU,QAAQ,UAAU,IAAI;AACpE,MAAI,cAAc,UAAW,KAAI,WAAW,UAAU,YAAY,UAAU,QAAQ;AACpF,MAAI,EAAE,UAAU,QAAQ,EAAE,cAAc,KAAM,OAAM,IAAI,UAAU,8CAA8C;AAChH,SAAO,OAAO,OAAO,GAAG;AAC1B;AAEA,SAAS,SAAS,OAAgB,MAAoB;AACpD,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,EAAG,OAAM,IAAI,UAAU,GAAG,IAAI,+BAA+B;AAC5G;AAGO,MAAM,gBAAyD;AAAA,EAC5D,cAAc;AAAA,EAAC;AAAA,EACvB,MAAM,WAA+C;AACnD,QAAI,OAAO,cAAc,WAAY,OAAM,IAAI,UAAU,iDAAiD;AAC1G,WAAO,OAAO,MAAM,SAAS,CAAC,SAAS,CAAC;AAAA,EAC1C;AAAA,EAKA,QAAQ,OAAwB,cAAgD;AAC9E,QAAI,SAAS,gBAAgB,SAAS,YAAa,OAAM,IAAI,UAAU,2CAA2C;AAClH,WAAO,OAAO,MAAM,WAAW,CAAC,IAAI,CAAC;AAAA,EACvC;AAAA,EACA,OAAO,WAAkD;AAAE,WAAO,OAAO,MAAM,UAAU,CAAC,cAAc,SAAS,CAAC,CAAC;AAAA,EAAG;AAAA,EACtH,IAAI,MAAoC;AACtC,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,OAAM,IAAI,UAAU,wCAAwC;AACnG,WAAO,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;AAAA,EACnC;AAAA,EACA,MAAS,QAAuB,aAAkC,SAAkD;AAClH,WAAO,OAAO,MAAM,SAAS,YAAY,SAAY,CAAC,QAAQ,WAAW,IAAI,CAAC,QAAQ,aAAa,OAAO,CAAC;AAAA,EAC7G;AAAA,EACA,SAAS,QAAiB,aAAkC,SAAkD;AAC5G,WAAO,OAAO,MAAM,YAAY,YAAY,SAAY,CAAC,QAAQ,WAAW,IAAI,CAAC,QAAQ,aAAa,OAAO,CAAC;AAAA,EAChH;AAAA,EACA,OAAO,QAAiB,QAAgB,aAA+B,SAAkD;AACvH,aAAS,QAAQ,UAAU;AAC3B,WAAO,OAAO,MAAM,UAAU,YAAY,SAAY,CAAC,QAAQ,QAAQ,WAAW,IAAI,CAAC,QAAQ,QAAQ,aAAa,OAAO,CAAC;AAAA,EAC9H;AAAA,EACA,MAAM,QAAiB,aAAsB,SAAkD;AAC7F,WAAO,OAAO,MAAM,SAAS,YAAY,SAAY,CAAC,QAAQ,WAAW,IAAI,CAAC,QAAQ,aAAa,OAAO,CAAC;AAAA,EAC7G;AAAA,EACA,WAAW,WAAmB,aAAoC,SAAkD;AAClH,aAAS,WAAW,cAAc;AAClC,WAAO,OAAO,MAAM,cAAc,YAAY,SAAY,CAAC,WAAW,WAAW,IAAI,CAAC,WAAW,aAAa,OAAO,CAAC;AAAA,EACxH;AAAA,EACA,KAAK,WAA2B,aAAoC,SAAkD;AACpH,WAAO,OAAO,MAAM,QAAQ,YAAY,SAAY,CAAC,WAAW,WAAW,IAAI,CAAC,WAAW,aAAa,OAAO,CAAC;AAAA,EAClH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAA4C;AAChD,UAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B,UAAM,iBAAiB,MAAM,OAAO,oBAAoB;AACxD,QAAI,UAA2B,eAAe,SAAS,MAAM,MAAM;AACnE,QAAI,QAAQ,IAAI,OAAQ,WAAU,QAAQ,SAAS,MAAM,eAAe,qBAAqB,MAAM,MAAM,CAAC;AAC1G,eAAW,WAAW,MAAM,UAAU;AACpC,UAAI,QAAQ,WAAW,OAAO;AAC5B,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,gBAAgB;AACrD,oBAAY,QAAQ,KAAK,CAAC,CAAa;AAAA,MACzC;AACA,gBAAW,QAAQ,QAAQ,MAAM,EAA8C,MAAM,SAAS,CAAC,GAAG,QAAQ,IAAI,CAAC;AAAA,IACjH;AACA,UAAM,cAAc,MAAM,QAAQ,MAAM;AACxC,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,gBAAgB;AAC9D,WAAO,MAAM,qBAAqB,aAAa,UAAU;AAAA,EAC3D;AACF;AAEO,SAAS,SAAS,YAA2C;AAClE,QAAM,SAAS,sBAAsB,MAAM,cAAc,UAAU,IAC/D,OAAO,eAAe,YAAY,WAAW,WAAW,OAAO,IAAI,cAAc,UAAU,IAAI;AACnG,MAAI,OAAO,WAAW,YAAY,CAAC,OAAO,KAAK,EAAG,OAAM,IAAI,UAAU,gEAAgE;AACtI,QAAM,UAAU,OAAO,OAAO,gBAAgB,SAAS;AACvD,OAAK,IAAI,SAAS,EAAE,QAAQ,UAAU,OAAO,OAAO,CAAC,CAAC,EAAE,CAAC;AACzD,SAAO,OAAO,OAAO,OAAO;AAC9B;","names":[]}
|
package/dist/testing/browser.cjs
CHANGED
|
@@ -28,8 +28,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var browser_exports = {};
|
|
30
30
|
__export(browser_exports, {
|
|
31
|
-
|
|
31
|
+
browserAssertions: () => browserAssertions,
|
|
32
32
|
createBrowserBridgeFixture: () => createBrowserBridgeFixture,
|
|
33
|
+
createBrowserFixture: () => createBrowserFixture,
|
|
34
|
+
createWoodBrowserResolver: () => createWoodBrowserResolver,
|
|
35
|
+
loadSveltePlugin: () => loadSveltePlugin,
|
|
33
36
|
startSharedBrowserViteServer: () => startSharedBrowserViteServer
|
|
34
37
|
});
|
|
35
38
|
module.exports = __toCommonJS(browser_exports);
|
|
@@ -40,6 +43,7 @@ var import_node_path = __toESM(require("node:path"), 1);
|
|
|
40
43
|
var import_node_url = require("node:url");
|
|
41
44
|
var import_tracing = require("../tracing/index.js");
|
|
42
45
|
var import_trace_testing = require("../trace-testing/index.js");
|
|
46
|
+
var import_renderer_app_generator = require("../codegen/renderer_app_generator.cjs");
|
|
43
47
|
var import_geometry = require("./geometry.js");
|
|
44
48
|
const DEFAULT_VIEWPORT = {
|
|
45
49
|
width: 1280,
|
|
@@ -97,6 +101,16 @@ async function waitForBoundedClose(promise, timeoutMs = DEFAULT_RESOURCE_CLOSE_T
|
|
|
97
101
|
clearTimeout(timeout);
|
|
98
102
|
}
|
|
99
103
|
}
|
|
104
|
+
async function closeRequired(promise, owner) {
|
|
105
|
+
let timeout;
|
|
106
|
+
try {
|
|
107
|
+
await Promise.race([promise, new Promise((_, reject) => {
|
|
108
|
+
timeout = setTimeout(() => reject(new Error(`${owner} cleanup timed out`)), DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS);
|
|
109
|
+
})]);
|
|
110
|
+
} finally {
|
|
111
|
+
if (timeout) clearTimeout(timeout);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
100
114
|
function channelToManifestEntry(channel, handler) {
|
|
101
115
|
const pathSegments = channel.split(".").filter(Boolean);
|
|
102
116
|
const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;
|
|
@@ -256,6 +270,11 @@ function createWoodBrowserResolver(rootDir) {
|
|
|
256
270
|
name: "wood-browser-resolver",
|
|
257
271
|
enforce: "pre",
|
|
258
272
|
resolveId(source) {
|
|
273
|
+
if (source.startsWith("@noego/wood/")) {
|
|
274
|
+
const resolved = resolvePackageModule(rootDir, source);
|
|
275
|
+
const esm = resolved?.endsWith(".cjs") ? resolved.replace(/\.cjs$/, ".js") : resolved;
|
|
276
|
+
return esm && import_node_fs.default.existsSync(esm) ? esm : null;
|
|
277
|
+
}
|
|
259
278
|
if (source === "svelte") {
|
|
260
279
|
return svelteClientEntry;
|
|
261
280
|
}
|
|
@@ -339,14 +358,14 @@ function workerRuntimeKey(input) {
|
|
|
339
358
|
].join("::");
|
|
340
359
|
}
|
|
341
360
|
async function createBrowserWorkerRuntime(input) {
|
|
342
|
-
let
|
|
361
|
+
let browser = null;
|
|
343
362
|
let cacheRoot = null;
|
|
344
363
|
let ownedServer = null;
|
|
345
364
|
try {
|
|
346
365
|
const playwrightBrowser = await loadPlaywright(input.browserName, input.rootDir);
|
|
347
366
|
if (input.wsEndpoint) {
|
|
348
367
|
try {
|
|
349
|
-
|
|
368
|
+
browser = await playwrightBrowser.connect(input.wsEndpoint);
|
|
350
369
|
} catch (error) {
|
|
351
370
|
if (!browserConnectWarnings.has(input.wsEndpoint)) {
|
|
352
371
|
browserConnectWarnings.add(input.wsEndpoint);
|
|
@@ -357,8 +376,8 @@ async function createBrowserWorkerRuntime(input) {
|
|
|
357
376
|
}
|
|
358
377
|
}
|
|
359
378
|
}
|
|
360
|
-
if (!
|
|
361
|
-
|
|
379
|
+
if (!browser) {
|
|
380
|
+
browser = await playwrightBrowser.launch({ headless: input.headless });
|
|
362
381
|
}
|
|
363
382
|
let vite;
|
|
364
383
|
if (input.viteUrl) {
|
|
@@ -378,7 +397,7 @@ async function createBrowserWorkerRuntime(input) {
|
|
|
378
397
|
};
|
|
379
398
|
}
|
|
380
399
|
return {
|
|
381
|
-
browser
|
|
400
|
+
browser,
|
|
382
401
|
vite,
|
|
383
402
|
rootDir: input.rootDir,
|
|
384
403
|
key: input.key,
|
|
@@ -388,7 +407,7 @@ async function createBrowserWorkerRuntime(input) {
|
|
|
388
407
|
};
|
|
389
408
|
} catch (error) {
|
|
390
409
|
await Promise.all([
|
|
391
|
-
|
|
410
|
+
browser?.close().catch(() => void 0),
|
|
392
411
|
ownedServer?.close().catch(() => void 0)
|
|
393
412
|
]);
|
|
394
413
|
if (cacheRoot) {
|
|
@@ -499,7 +518,19 @@ function assertFileExists(filePath, label) {
|
|
|
499
518
|
function writeEntry(tempDir, name, source) {
|
|
500
519
|
import_node_fs.default.mkdirSync(tempDir, { recursive: true });
|
|
501
520
|
const entryPath = import_node_path.default.join(tempDir, `${name}.ts`);
|
|
502
|
-
import_node_fs.default.writeFileSync(entryPath,
|
|
521
|
+
import_node_fs.default.writeFileSync(entryPath, `import { unmount as __woodHarnessUnmount } from 'svelte';
|
|
522
|
+
` + source + `
|
|
523
|
+
let __woodHarnessClosing;
|
|
524
|
+
window.__WOOD_BROWSER_UNMOUNT__ = () => __woodHarnessClosing ??= Promise.resolve().then(async () => {
|
|
525
|
+
const app = window.__WOOD_BROWSER_APP__;
|
|
526
|
+
if (!app) return;
|
|
527
|
+
try { await app.disposeNavigation?.(); }
|
|
528
|
+
finally {
|
|
529
|
+
await __woodHarnessUnmount(app);
|
|
530
|
+
window.__WOOD_BROWSER_APP__ = undefined;
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
`);
|
|
503
534
|
return entryPath;
|
|
504
535
|
}
|
|
505
536
|
function writeHtml(tempDir, name, entryPath, cssFiles) {
|
|
@@ -532,103 +563,6 @@ function findView(views, routeKey) {
|
|
|
532
563
|
}
|
|
533
564
|
return found;
|
|
534
565
|
}
|
|
535
|
-
function resolveComponentPath(rootDir, componentDir, filePath) {
|
|
536
|
-
if (import_node_path.default.isAbsolute(filePath)) return filePath;
|
|
537
|
-
const base = componentDir ? resolveFromRoot(rootDir, componentDir) : rootDir;
|
|
538
|
-
const componentDirName = componentDir ? import_node_path.default.basename(componentDir) : "";
|
|
539
|
-
if (componentDirName && filePath.replace(/\\/g, "/").startsWith(`${componentDirName}/`)) {
|
|
540
|
-
return import_node_path.default.resolve(base, filePath.slice(componentDirName.length + 1));
|
|
541
|
-
}
|
|
542
|
-
return import_node_path.default.resolve(base, filePath);
|
|
543
|
-
}
|
|
544
|
-
function buildRouteEntrySource(input) {
|
|
545
|
-
const { view, rootDir, componentDir, routeKey, route } = input;
|
|
546
|
-
const viewPath = resolveComponentPath(rootDir, componentDir, view.viewPath);
|
|
547
|
-
const layoutPaths = view.layouts.map((layout) => resolveComponentPath(rootDir, componentDir, layout));
|
|
548
|
-
const controllerPath = view.controller ? resolveComponentPath(rootDir, componentDir, view.controller) : null;
|
|
549
|
-
const layoutControllerPaths = view.layoutControllers?.map(
|
|
550
|
-
(controller) => controller ? resolveComponentPath(rootDir, componentDir, controller) : null
|
|
551
|
-
) ?? [];
|
|
552
|
-
const imports = [
|
|
553
|
-
`import { mount } from 'svelte';`,
|
|
554
|
-
`import NavigationShell from '@noego/wood/navigation-shell';`,
|
|
555
|
-
`import { getContainer } from '@noego/wood/client';`,
|
|
556
|
-
`import { getNavigation } from '@noego/wood/navigation';`,
|
|
557
|
-
`import ViewComponent from '${viteImport(viewPath)}';`,
|
|
558
|
-
...layoutPaths.map((layoutPath, index) => `import Layout${index} from '${viteImport(layoutPath)}';`),
|
|
559
|
-
...controllerPath ? [`import PageController from '${viteImport(controllerPath)}';`] : [],
|
|
560
|
-
...layoutControllerPaths.map(
|
|
561
|
-
(controllerPath2, index) => controllerPath2 ? `import LayoutController${index} from '${viteImport(controllerPath2)}';` : ""
|
|
562
|
-
).filter(Boolean)
|
|
563
|
-
];
|
|
564
|
-
const layoutControllerClasses = layoutControllerPaths.map(
|
|
565
|
-
(controllerPath2, index) => controllerPath2 ? `LayoutController${index}` : "undefined"
|
|
566
|
-
);
|
|
567
|
-
return `${imports.join("\n")}
|
|
568
|
-
|
|
569
|
-
const entry = {
|
|
570
|
-
key: ${JSON.stringify(routeKey)},
|
|
571
|
-
windowName: ${JSON.stringify(view.windowName)},
|
|
572
|
-
pageName: ${JSON.stringify(view.pageName)},
|
|
573
|
-
viewPath: ${JSON.stringify(view.viewPath)},
|
|
574
|
-
layoutPaths: ${JSON.stringify(view.layouts)},
|
|
575
|
-
layoutControllers: ${JSON.stringify(view.layoutControllers ?? [])},
|
|
576
|
-
controller: ${JSON.stringify(view.controller)},
|
|
577
|
-
layoutControllerClasses: [${layoutControllerClasses.join(", ")}],
|
|
578
|
-
controllerClass: ${controllerPath ? "PageController" : "undefined"},
|
|
579
|
-
view: ViewComponent,
|
|
580
|
-
layouts: [${layoutPaths.map((_, index) => `Layout${index}`).join(", ")}],
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
function createScopedContainer() {
|
|
584
|
-
const root = getContainer();
|
|
585
|
-
const scope = root.extend();
|
|
586
|
-
return scope;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
async function resolveController(ControllerClass) {
|
|
590
|
-
if (!ControllerClass) return undefined;
|
|
591
|
-
const scope = createScopedContainer();
|
|
592
|
-
scope.registerClass?.(ControllerClass);
|
|
593
|
-
return await scope.get(ControllerClass);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
async function createViewController(nextEntry) {
|
|
597
|
-
return await resolveController(nextEntry.controllerClass);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async function createLayoutControllers(nextEntry) {
|
|
601
|
-
const controllers = [];
|
|
602
|
-
for (const ControllerClass of nextEntry.layoutControllerClasses) {
|
|
603
|
-
controllers.push(await resolveController(ControllerClass));
|
|
604
|
-
}
|
|
605
|
-
return controllers;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function getViewManifestEntry(nextKey) {
|
|
609
|
-
return nextKey === entry.key ? entry : null;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const navigation = getNavigation();
|
|
613
|
-
navigation.replace(entry.key, ${JSON.stringify(route.params)}, ${JSON.stringify(route.query)});
|
|
614
|
-
|
|
615
|
-
const controller = await createViewController(entry);
|
|
616
|
-
const layoutControllers = await createLayoutControllers(entry);
|
|
617
|
-
|
|
618
|
-
window.__WOOD_BROWSER_APP__ = mount(NavigationShell, {
|
|
619
|
-
target: document.getElementById('app'),
|
|
620
|
-
props: {
|
|
621
|
-
manifest: { [entry.key]: entry },
|
|
622
|
-
initialEntry: entry,
|
|
623
|
-
initialController: controller,
|
|
624
|
-
initialLayoutControllers: layoutControllers,
|
|
625
|
-
createViewControllerFn: createViewController,
|
|
626
|
-
createLayoutControllersFn: createLayoutControllers,
|
|
627
|
-
getViewManifestEntryFn: getViewManifestEntry,
|
|
628
|
-
},
|
|
629
|
-
});
|
|
630
|
-
`;
|
|
631
|
-
}
|
|
632
566
|
function buildGeneratedAppEntrySource(input) {
|
|
633
567
|
const hasRouteContext = Object.keys(input.route.params).length > 0 || Object.keys(input.route.query).length > 0;
|
|
634
568
|
const rendererTraceEnable = input.enableRendererTrace ? `try {
|
|
@@ -638,14 +572,14 @@ function buildGeneratedAppEntrySource(input) {
|
|
|
638
572
|
traceConfiguration.configureEnabled(true);
|
|
639
573
|
} catch (error) { /* best-effort renderer-trace enable */ }` : "";
|
|
640
574
|
const navigationApply = hasRouteContext ? `getNavigation().replace(${JSON.stringify(input.routeKey)}, ${JSON.stringify(input.route.params)}, ${JSON.stringify(input.route.query)});` : "";
|
|
641
|
-
return `import
|
|
575
|
+
return `import * as generated from '${viteImport(input.generatedApp)}';
|
|
642
576
|
import { getNavigation } from '${input.navigationModule}';
|
|
643
577
|
import { getContainer, TraceProviderConfiguration } from '${input.clientModule}';
|
|
644
578
|
window.__WOOD_BROWSER_DEFAULT_ROUTE__ = ${JSON.stringify(input.routeKey)};
|
|
645
|
-
window.__WOOD_BROWSER_APP__ = await app(document.getElementById('app'));
|
|
579
|
+
window.__WOOD_BROWSER_APP__ = await generated.app(document.getElementById('app'), ${JSON.stringify(input.route)});
|
|
646
580
|
window.__WOOD_BROWSER_NAV__ = getNavigation();
|
|
647
581
|
${rendererTraceEnable}
|
|
648
|
-
${navigationApply}
|
|
582
|
+
if (!generated.supportsInitialRoute) { ${navigationApply} }
|
|
649
583
|
`;
|
|
650
584
|
}
|
|
651
585
|
function buildComponentEntrySource(input) {
|
|
@@ -1048,7 +982,7 @@ async function captureCoverage(page, selector) {
|
|
|
1048
982
|
await cdp.detach().catch(() => void 0);
|
|
1049
983
|
}
|
|
1050
984
|
}
|
|
1051
|
-
function
|
|
985
|
+
function browserAssertions(page, outputDir = import_node_path.default.resolve(".wood-browser-output")) {
|
|
1052
986
|
async function waitForVisible(selector) {
|
|
1053
987
|
const locator = page.locator(selector).first();
|
|
1054
988
|
try {
|
|
@@ -1369,19 +1303,7 @@ function createSurface(input) {
|
|
|
1369
1303
|
page: input.page,
|
|
1370
1304
|
bridge: input.bridge,
|
|
1371
1305
|
navigation: createMountedNavigation(input.page),
|
|
1372
|
-
|
|
1373
|
-
locator(selector) {
|
|
1374
|
-
return input.page.locator(selector);
|
|
1375
|
-
},
|
|
1376
|
-
async click(selector) {
|
|
1377
|
-
await input.page.locator(selector).click();
|
|
1378
|
-
},
|
|
1379
|
-
async fill(selector, value) {
|
|
1380
|
-
await input.page.locator(selector).fill(value);
|
|
1381
|
-
},
|
|
1382
|
-
async evaluate(fn) {
|
|
1383
|
-
return await input.page.evaluate(fn);
|
|
1384
|
-
},
|
|
1306
|
+
assertions: browserAssertions(input.page, input.outputDir),
|
|
1385
1307
|
destroy: input.destroy
|
|
1386
1308
|
};
|
|
1387
1309
|
}
|
|
@@ -1418,17 +1340,17 @@ async function openMountPage(input) {
|
|
|
1418
1340
|
bridge: mountedBridge,
|
|
1419
1341
|
outputDir: import_node_path.default.join(input.runtime.rootDir, "test-results", "wood-browser"),
|
|
1420
1342
|
destroy: async () => {
|
|
1421
|
-
await page.evaluate(() =>
|
|
1422
|
-
const app = window.__WOOD_BROWSER_APP__;
|
|
1423
|
-
if (app && typeof app === "object" && typeof app.$destroy === "function") {
|
|
1424
|
-
app.$destroy();
|
|
1425
|
-
}
|
|
1426
|
-
}).catch(() => void 0);
|
|
1343
|
+
if (!page.isClosed()) await page.evaluate(() => window.__WOOD_BROWSER_UNMOUNT__?.());
|
|
1427
1344
|
}
|
|
1428
1345
|
});
|
|
1429
1346
|
}
|
|
1430
|
-
async function
|
|
1347
|
+
async function createBrowserFixture(config = {}) {
|
|
1431
1348
|
const rootDir = normalizeRoot(config.rootDir);
|
|
1349
|
+
const css = (config.css ?? []).map((cssPath) => {
|
|
1350
|
+
const resolved = resolveFromRoot(rootDir, cssPath);
|
|
1351
|
+
assertFileExists(resolved, "CSS file");
|
|
1352
|
+
return resolved;
|
|
1353
|
+
});
|
|
1432
1354
|
const browserName = config.browser?.browserName ?? "chromium";
|
|
1433
1355
|
const headless = config.browser?.headless ?? true;
|
|
1434
1356
|
const workerRuntime = await acquireBrowserWorkerRuntime({
|
|
@@ -1437,7 +1359,7 @@ async function createHarness(config = {}) {
|
|
|
1437
1359
|
headless
|
|
1438
1360
|
});
|
|
1439
1361
|
let released = false;
|
|
1440
|
-
let
|
|
1362
|
+
let destroying;
|
|
1441
1363
|
let tempDir = null;
|
|
1442
1364
|
let context = null;
|
|
1443
1365
|
let page = null;
|
|
@@ -1476,11 +1398,6 @@ async function createHarness(config = {}) {
|
|
|
1476
1398
|
rootDir,
|
|
1477
1399
|
transportCleanups: []
|
|
1478
1400
|
};
|
|
1479
|
-
const css = (config.css ?? []).map((cssPath) => {
|
|
1480
|
-
const resolved = resolveFromRoot(rootDir, cssPath);
|
|
1481
|
-
assertFileExists(resolved, "CSS file");
|
|
1482
|
-
return resolved;
|
|
1483
|
-
});
|
|
1484
1401
|
return {
|
|
1485
1402
|
page,
|
|
1486
1403
|
...traceRecorder ? { traceRecorder } : {},
|
|
@@ -1495,12 +1412,20 @@ async function createHarness(config = {}) {
|
|
|
1495
1412
|
const viewsPath = resolveFromRoot(rootDir, config.viewsConfig);
|
|
1496
1413
|
const views = await loadViews(viewsPath);
|
|
1497
1414
|
const view = findView(views, routeKey);
|
|
1498
|
-
|
|
1415
|
+
const generatedApp = import_node_path.default.join(runtime.tempDir, `route-app-${routeKey.replace(/[^a-zA-Z0-9_-]/g, "-")}.ts`);
|
|
1416
|
+
import_node_fs.default.writeFileSync(generatedApp, (0, import_renderer_app_generator.generateRendererApp)([view], {
|
|
1417
|
+
componentDir: config.componentDir ? resolveFromRoot(rootDir, config.componentDir) : rootDir,
|
|
1418
|
+
outDir: runtime.tempDir,
|
|
1419
|
+
entryViewKey: routeKey
|
|
1420
|
+
}));
|
|
1421
|
+
entrySource = buildGeneratedAppEntrySource({
|
|
1422
|
+
generatedApp,
|
|
1499
1423
|
routeKey,
|
|
1500
|
-
|
|
1501
|
-
rootDir,
|
|
1502
|
-
|
|
1503
|
-
|
|
1424
|
+
route,
|
|
1425
|
+
navigationModule: resolveEsmModule(rootDir, "@noego/wood/navigation"),
|
|
1426
|
+
clientModule: resolveEsmModule(rootDir, "@noego/wood/client"),
|
|
1427
|
+
enableRendererTrace: config.trace?.enabled === true,
|
|
1428
|
+
traceMinLevel: config.trace?.minLevel ?? "debug"
|
|
1504
1429
|
});
|
|
1505
1430
|
} else if (config.generatedApp) {
|
|
1506
1431
|
const generatedApp = resolveFromRoot(rootDir, config.generatedApp);
|
|
@@ -1515,7 +1440,7 @@ async function createHarness(config = {}) {
|
|
|
1515
1440
|
traceMinLevel: config.trace?.minLevel ?? "debug"
|
|
1516
1441
|
});
|
|
1517
1442
|
} else {
|
|
1518
|
-
throw new Error("mountRoute requires either viewsConfig or generatedApp in
|
|
1443
|
+
throw new Error("mountRoute requires either viewsConfig or generatedApp in createBrowserFixture().");
|
|
1519
1444
|
}
|
|
1520
1445
|
const mounted = await openMountPage({
|
|
1521
1446
|
runtime,
|
|
@@ -1613,34 +1538,38 @@ async function createHarness(config = {}) {
|
|
|
1613
1538
|
async flushRendererTraces() {
|
|
1614
1539
|
await page.evaluate(() => window.__woodBrowserTraceFlush?.()).catch(() => void 0);
|
|
1615
1540
|
},
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1541
|
+
destroy() {
|
|
1542
|
+
return destroying ?? (destroying = Promise.resolve().then(async () => {
|
|
1543
|
+
const errors = [];
|
|
1544
|
+
const attempt = async (action) => {
|
|
1545
|
+
try {
|
|
1546
|
+
await action();
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
errors.push(error);
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
if (!page.isClosed()) await attempt(() => closeRequired(
|
|
1552
|
+
page.evaluate(() => window.__WOOD_BROWSER_UNMOUNT__?.()),
|
|
1553
|
+
"renderer"
|
|
1554
|
+
));
|
|
1555
|
+
for (const cleanup of runtime.transportCleanups.splice(0)) await attempt(cleanup);
|
|
1556
|
+
await attempt(() => closeRequired(page.close(), "browser page"));
|
|
1557
|
+
await attempt(() => closeRequired(context.close(), "browser context"));
|
|
1558
|
+
await attempt(() => traceRecorder?.disable());
|
|
1559
|
+
await attempt(() => import_node_fs.default.rmSync(tempDir, { recursive: true, force: true }));
|
|
1560
|
+
releaseRuntimeOnce();
|
|
1561
|
+
if (errors.length) throw Object.assign(new Error("Wood browser cleanup failed: " + errors.map((error) => error instanceof Error ? error.message : String(error)).join("; ")), { errors });
|
|
1562
|
+
}));
|
|
1634
1563
|
}
|
|
1635
1564
|
};
|
|
1636
1565
|
}
|
|
1637
|
-
const browser = {
|
|
1638
|
-
createHarness
|
|
1639
|
-
};
|
|
1640
1566
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1641
1567
|
0 && (module.exports = {
|
|
1642
|
-
|
|
1568
|
+
browserAssertions,
|
|
1643
1569
|
createBrowserBridgeFixture,
|
|
1570
|
+
createBrowserFixture,
|
|
1571
|
+
createWoodBrowserResolver,
|
|
1572
|
+
loadSveltePlugin,
|
|
1644
1573
|
startSharedBrowserViteServer
|
|
1645
1574
|
});
|
|
1646
1575
|
//# sourceMappingURL=browser.cjs.map
|