@noego/wood 0.4.9 → 0.5.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 +118 -42
- package/bin/axe.js +313 -98
- package/dist/{loader/__fixtures__/layouts/app.load.cjs → automation/actions.cjs} +3 -13
- package/dist/automation/actions.cjs.map +1 -0
- package/dist/automation/actions.d.cts +57 -0
- package/dist/automation/actions.d.ts +57 -0
- package/dist/automation/actions.js +1 -0
- package/dist/automation/actions.js.map +1 -0
- package/dist/automation/actions_executor.cjs +101 -0
- package/dist/automation/actions_executor.cjs.map +1 -0
- package/dist/automation/actions_executor.d.cts +16 -0
- package/dist/automation/actions_executor.d.ts +16 -0
- package/dist/automation/actions_executor.js +76 -0
- package/dist/automation/actions_executor.js.map +1 -0
- package/dist/automation/connection.cjs +86 -0
- package/dist/automation/connection.cjs.map +1 -0
- package/dist/automation/connection.d.cts +18 -0
- package/dist/automation/connection.d.ts +18 -0
- package/dist/automation/connection.js +51 -0
- package/dist/automation/connection.js.map +1 -0
- package/dist/{middleware/__fixtures__/middleware/sequence.cjs → automation/errors.cjs} +25 -22
- package/dist/automation/errors.cjs.map +1 -0
- package/dist/automation/errors.d.cts +31 -0
- package/dist/automation/errors.d.ts +31 -0
- package/dist/automation/errors.js +24 -0
- package/dist/automation/errors.js.map +1 -0
- package/dist/automation/index.cjs +49 -0
- package/dist/automation/index.cjs.map +1 -0
- package/dist/automation/index.d.cts +16 -0
- package/dist/automation/index.d.ts +16 -0
- package/dist/automation/index.js +15 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/introspection_reader.cjs +75 -0
- package/dist/automation/introspection_reader.cjs.map +1 -0
- package/dist/automation/introspection_reader.d.cts +20 -0
- package/dist/automation/introspection_reader.d.ts +20 -0
- package/dist/automation/introspection_reader.js +48 -0
- package/dist/automation/introspection_reader.js.map +1 -0
- package/dist/automation/locator.cjs +102 -0
- package/dist/automation/locator.cjs.map +1 -0
- package/dist/automation/locator.d.cts +51 -0
- package/dist/automation/locator.d.ts +51 -0
- package/dist/automation/locator.js +75 -0
- package/dist/automation/locator.js.map +1 -0
- package/dist/{loader/__fixtures__/views/test/page.load.cjs → automation/observation.cjs} +3 -13
- package/dist/automation/observation.cjs.map +1 -0
- package/dist/automation/observation.d.cts +44 -0
- package/dist/automation/observation.d.ts +44 -0
- package/dist/automation/observation.js +1 -0
- package/dist/automation/observation.js.map +1 -0
- package/dist/{controller/__fixtures__/controllers/test.cjs → automation/receipts.cjs} +3 -15
- package/dist/automation/receipts.cjs.map +1 -0
- package/dist/automation/receipts.d.cts +27 -0
- package/dist/automation/receipts.d.ts +27 -0
- package/dist/automation/receipts.js +1 -0
- package/dist/automation/receipts.js.map +1 -0
- package/dist/automation/scenario.cjs +696 -0
- package/dist/automation/scenario.cjs.map +1 -0
- package/dist/automation/scenario.d.cts +89 -0
- package/dist/automation/scenario.d.ts +89 -0
- package/dist/automation/scenario.js +660 -0
- package/dist/automation/scenario.js.map +1 -0
- package/dist/automation/scrolling.cjs +17 -0
- package/dist/automation/scrolling.cjs.map +1 -0
- package/dist/automation/scrolling.d.cts +51 -0
- package/dist/automation/scrolling.d.ts +51 -0
- package/dist/automation/scrolling.js +1 -0
- package/dist/automation/scrolling.js.map +1 -0
- package/dist/automation/scrolling_executor.cjs +109 -0
- package/dist/automation/scrolling_executor.cjs.map +1 -0
- package/dist/automation/scrolling_executor.d.cts +13 -0
- package/dist/automation/scrolling_executor.d.ts +13 -0
- package/dist/automation/scrolling_executor.js +84 -0
- package/dist/automation/scrolling_executor.js.map +1 -0
- package/dist/automation/target_identity.cjs +17 -0
- package/dist/automation/target_identity.cjs.map +1 -0
- package/dist/automation/target_identity.d.cts +28 -0
- package/dist/automation/target_identity.d.ts +28 -0
- package/dist/automation/target_identity.js +1 -0
- package/dist/automation/target_identity.js.map +1 -0
- package/dist/automation/target_registry.cjs +148 -0
- package/dist/automation/target_registry.cjs.map +1 -0
- package/dist/automation/target_registry.d.cts +32 -0
- package/dist/automation/target_registry.d.ts +32 -0
- package/dist/automation/target_registry.js +122 -0
- package/dist/automation/target_registry.js.map +1 -0
- package/dist/automation/waits.cjs +17 -0
- package/dist/automation/waits.cjs.map +1 -0
- package/dist/automation/waits.d.cts +44 -0
- package/dist/automation/waits.d.ts +44 -0
- package/dist/automation/waits.js +1 -0
- package/dist/automation/waits.js.map +1 -0
- package/dist/automation/waits_executor.cjs +104 -0
- package/dist/automation/waits_executor.cjs.map +1 -0
- package/dist/automation/waits_executor.d.cts +14 -0
- package/dist/automation/waits_executor.d.ts +14 -0
- package/dist/automation/waits_executor.js +79 -0
- package/dist/automation/waits_executor.js.map +1 -0
- package/dist/bridge/backend_bridge.cjs.map +1 -1
- package/dist/bridge/backend_bridge.js.map +1 -1
- package/dist/client/index.cjs +78 -21
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +19 -3
- package/dist/client/index.d.ts +19 -3
- package/dist/client/index.js +80 -22
- package/dist/client/index.js.map +1 -1
- package/dist/client/renderer_log_transport.cjs +151 -0
- package/dist/client/renderer_log_transport.cjs.map +1 -0
- package/dist/client/renderer_log_transport.d.cts +54 -0
- package/dist/client/renderer_log_transport.d.ts +54 -0
- package/dist/client/renderer_log_transport.js +123 -0
- package/dist/client/renderer_log_transport.js.map +1 -0
- package/dist/codegen/controller_registration_generator.cjs +13 -2
- package/dist/codegen/controller_registration_generator.cjs.map +1 -1
- package/dist/codegen/controller_registration_generator.d.cts +12 -1
- package/dist/codegen/controller_registration_generator.d.ts +12 -1
- package/dist/codegen/controller_registration_generator.js +10 -1
- package/dist/codegen/controller_registration_generator.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/preload_generator.cjs +13 -6
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.d.cts +11 -2
- package/dist/codegen/preload_generator.d.ts +11 -2
- package/dist/codegen/preload_generator.js +13 -6
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +95 -44
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +95 -44
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +1 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +1 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +95 -4
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.d.cts +10 -2
- package/dist/config/config_parser.d.ts +10 -2
- package/dist/config/config_parser.js +92 -3
- package/dist/config/config_parser.js.map +1 -1
- package/dist/config/index.cjs +6 -2
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +8 -2
- package/dist/config/index.js.map +1 -1
- package/dist/controller/controller_resolver.cjs +51 -3
- package/dist/controller/controller_resolver.cjs.map +1 -1
- package/dist/controller/controller_resolver.d.cts +27 -4
- package/dist/controller/controller_resolver.d.ts +27 -4
- package/dist/controller/controller_resolver.js +48 -2
- package/dist/controller/controller_resolver.js.map +1 -1
- package/dist/controller/index.cjs +6 -2
- package/dist/controller/index.cjs.map +1 -1
- package/dist/controller/index.d.cts +1 -1
- package/dist/controller/index.d.ts +1 -1
- package/dist/controller/index.js +4 -2
- package/dist/controller/index.js.map +1 -1
- package/dist/dev/wood_dev_server.cjs +4 -1
- package/dist/dev/wood_dev_server.cjs.map +1 -1
- package/dist/dev/wood_dev_server.js +5 -2
- package/dist/dev/wood_dev_server.js.map +1 -1
- package/dist/dev/yaml_watcher.cjs +1 -1
- package/dist/dev/yaml_watcher.cjs.map +1 -1
- package/dist/dev/yaml_watcher.d.cts +4 -0
- package/dist/dev/yaml_watcher.d.ts +4 -0
- package/dist/dev/yaml_watcher.js +1 -1
- package/dist/dev/yaml_watcher.js.map +1 -1
- package/dist/frontend/frontend_lifecycle.cjs +74 -0
- package/dist/frontend/frontend_lifecycle.cjs.map +1 -0
- package/dist/frontend/frontend_lifecycle.d.cts +50 -0
- package/dist/frontend/frontend_lifecycle.d.ts +50 -0
- package/dist/frontend/frontend_lifecycle.js +50 -0
- package/dist/frontend/frontend_lifecycle.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +122 -0
- package/dist/frontend/frontend_page.cjs.map +1 -0
- package/dist/frontend/frontend_page.d.cts +62 -0
- package/dist/frontend/frontend_page.d.ts +62 -0
- package/dist/frontend/frontend_page.js +98 -0
- package/dist/frontend/frontend_page.js.map +1 -0
- package/dist/frontend/frontend_surface.cjs +249 -0
- package/dist/frontend/frontend_surface.cjs.map +1 -0
- package/dist/frontend/frontend_surface.d.cts +64 -0
- package/dist/frontend/frontend_surface.d.ts +64 -0
- package/dist/frontend/frontend_surface.js +224 -0
- package/dist/frontend/frontend_surface.js.map +1 -0
- package/dist/frontend/index.cjs +52 -0
- package/dist/frontend/index.cjs.map +1 -0
- package/dist/frontend/index.d.cts +10 -0
- package/dist/frontend/index.d.ts +10 -0
- package/dist/frontend/index.js +19 -0
- package/dist/frontend/index.js.map +1 -0
- package/dist/frontend/page_catalog.cjs +135 -0
- package/dist/frontend/page_catalog.cjs.map +1 -0
- package/dist/frontend/page_catalog.d.cts +56 -0
- package/dist/frontend/page_catalog.d.ts +56 -0
- package/dist/frontend/page_catalog.js +108 -0
- package/dist/frontend/page_catalog.js.map +1 -0
- package/dist/frontend/tracked_transport.cjs +55 -0
- package/dist/frontend/tracked_transport.cjs.map +1 -0
- package/dist/frontend/tracked_transport.d.cts +36 -0
- package/dist/frontend/tracked_transport.d.ts +36 -0
- package/dist/frontend/tracked_transport.js +31 -0
- package/dist/frontend/tracked_transport.js.map +1 -0
- package/dist/frontend/work_registry.cjs +153 -0
- package/dist/frontend/work_registry.cjs.map +1 -0
- package/dist/frontend/work_registry.d.cts +70 -0
- package/dist/frontend/work_registry.d.ts +70 -0
- package/dist/frontend/work_registry.js +128 -0
- package/dist/frontend/work_registry.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -7
- package/dist/index.d.ts +11 -7
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/main/index.cjs +10 -0
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +14 -2
- package/dist/main/index.d.ts +14 -2
- package/dist/main/index.js +16 -0
- package/dist/main/index.js.map +1 -1
- package/dist/middleware/middleware_resolver.cjs +22 -1
- package/dist/middleware/middleware_resolver.cjs.map +1 -1
- package/dist/middleware/middleware_resolver.d.cts +13 -1
- package/dist/middleware/middleware_resolver.d.ts +13 -1
- package/dist/middleware/middleware_resolver.js +22 -1
- package/dist/middleware/middleware_resolver.js.map +1 -1
- package/dist/navigation/automation_introspection.cjs +110 -0
- package/dist/navigation/automation_introspection.cjs.map +1 -0
- package/dist/navigation/automation_introspection.d.cts +60 -0
- package/dist/navigation/automation_introspection.d.ts +60 -0
- package/dist/navigation/automation_introspection.js +85 -0
- package/dist/navigation/automation_introspection.js.map +1 -0
- package/dist/navigation/index.cjs +10 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +27 -1
- package/dist/navigation/index.d.ts +27 -1
- package/dist/navigation/index.js +6 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/{middleware/__fixtures__/middleware/auth.cjs → navigation/layout_preservation.cjs} +18 -20
- package/dist/navigation/layout_preservation.cjs.map +1 -0
- package/dist/navigation/layout_preservation.d.cts +2 -0
- package/dist/navigation/layout_preservation.d.ts +2 -0
- package/dist/navigation/layout_preservation.js +18 -0
- package/dist/navigation/layout_preservation.js.map +1 -0
- package/dist/parser/index.cjs +2 -0
- package/dist/parser/index.cjs.map +1 -1
- package/dist/parser/index.d.cts +1 -1
- package/dist/parser/index.d.ts +1 -1
- package/dist/parser/index.js +2 -1
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/operations_parser.cjs +6 -0
- package/dist/parser/operations_parser.cjs.map +1 -1
- package/dist/parser/operations_parser.d.cts +9 -2
- package/dist/parser/operations_parser.d.ts +9 -2
- package/dist/parser/operations_parser.js +5 -0
- package/dist/parser/operations_parser.js.map +1 -1
- package/dist/parser/views_parser.cjs.map +1 -1
- package/dist/parser/views_parser.js.map +1 -1
- package/dist/router/index.d.cts +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/wood_router.cjs +35 -3
- package/dist/router/wood_router.cjs.map +1 -1
- package/dist/router/wood_router.d.cts +15 -0
- package/dist/router/wood_router.d.ts +15 -0
- package/dist/router/wood_router.js +36 -4
- package/dist/router/wood_router.js.map +1 -1
- package/dist/runtime/get_container.cjs +9 -34
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.d.cts +24 -4
- package/dist/runtime/get_container.d.ts +24 -4
- package/dist/runtime/get_container.js +8 -23
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +9 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +5 -2
- package/dist/runtime/index.d.ts +5 -2
- package/dist/runtime/index.js +10 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderer_config_extension.cjs +50 -0
- package/dist/runtime/renderer_config_extension.cjs.map +1 -0
- package/dist/runtime/renderer_config_extension.d.cts +19 -0
- package/dist/runtime/renderer_config_extension.d.ts +19 -0
- package/dist/runtime/renderer_config_extension.js +24 -0
- package/dist/runtime/renderer_config_extension.js.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs +73 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.d.cts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.d.ts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.js +48 -0
- package/dist/runtime/renderer_log_batch_receiver.js.map +1 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.cts +1 -1
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.ts +1 -1
- package/dist/runtime/runtime.cjs +14 -0
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +2 -1
- package/dist/runtime/runtime.d.ts +2 -1
- package/dist/runtime/runtime.js +14 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/stick/index.cjs.map +1 -1
- package/dist/stick/index.js.map +1 -1
- package/dist/testing/automation_assertions.cjs +417 -0
- package/dist/testing/automation_assertions.cjs.map +1 -0
- package/dist/testing/automation_assertions.d.cts +44 -0
- package/dist/testing/automation_assertions.d.ts +44 -0
- package/dist/testing/automation_assertions.js +385 -0
- package/dist/testing/automation_assertions.js.map +1 -0
- package/dist/testing/browser.cjs +130 -57
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +10 -4
- package/dist/testing/browser.d.ts +10 -4
- package/dist/testing/browser.js +132 -53
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.cjs +28 -8
- package/dist/testing/create_controller_harness.cjs.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +18 -2
- package/dist/testing/create_controller_harness.d.ts +18 -2
- package/dist/testing/create_controller_harness.js +28 -8
- package/dist/testing/create_controller_harness.js.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +1 -0
- package/dist/testing/create_router_bridge.d.ts +1 -0
- package/dist/testing/create_test_router.d.cts +1 -0
- package/dist/testing/create_test_router.d.ts +1 -0
- package/dist/testing/geometry.cjs +109 -0
- package/dist/testing/geometry.cjs.map +1 -0
- package/dist/testing/geometry.d.cts +45 -0
- package/dist/testing/geometry.d.ts +45 -0
- package/dist/testing/geometry.js +79 -0
- package/dist/testing/geometry.js.map +1 -0
- package/dist/testing/index.cjs +62 -2
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +23 -4
- package/dist/testing/index.d.ts +23 -4
- package/dist/testing/index.js +50 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/{controller/__fixtures__/controllers/not_a_class.cjs → testing/test_wood.cjs} +15 -7
- package/dist/testing/test_wood.cjs.map +1 -0
- package/dist/testing/test_wood.d.cts +16 -0
- package/dist/testing/test_wood.d.ts +16 -0
- package/dist/testing/test_wood.js +12 -0
- package/dist/testing/test_wood.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +272 -0
- package/dist/testing/test_wood_config.cjs.map +1 -0
- package/dist/testing/test_wood_config.d.cts +111 -0
- package/dist/testing/test_wood_config.d.ts +111 -0
- package/dist/testing/test_wood_config.js +247 -0
- package/dist/testing/test_wood_config.js.map +1 -0
- package/dist/testing/wood_config_source.cjs +146 -0
- package/dist/testing/wood_config_source.cjs.map +1 -0
- package/dist/testing/wood_config_source.d.cts +36 -0
- package/dist/testing/wood_config_source.d.ts +36 -0
- package/dist/testing/wood_config_source.js +112 -0
- package/dist/testing/wood_config_source.js.map +1 -0
- package/dist/testing/wood_env_renderer.cjs +210 -0
- package/dist/testing/wood_env_renderer.cjs.map +1 -0
- package/dist/testing/wood_env_renderer.d.cts +74 -0
- package/dist/testing/wood_env_renderer.d.ts +74 -0
- package/dist/testing/wood_env_renderer.js +188 -0
- package/dist/testing/wood_env_renderer.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +94 -0
- package/dist/testing/wood_test_selection.cjs.map +1 -0
- package/dist/testing/wood_test_selection.d.cts +39 -0
- package/dist/testing/wood_test_selection.d.ts +39 -0
- package/dist/testing/wood_test_selection.js +68 -0
- package/dist/testing/wood_test_selection.js.map +1 -0
- package/dist/trace-testing/index.d.cts +1 -2
- package/dist/trace-testing/index.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.cjs +1 -1
- package/dist/trace-testing/trace_recorder.cjs.map +1 -1
- package/dist/trace-testing/trace_recorder.d.cts +1 -2
- package/dist/trace-testing/trace_recorder.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.js +1 -1
- package/dist/trace-testing/trace_recorder.js.map +1 -1
- package/dist/trace-transport/index.d.cts +1 -1
- package/dist/trace-transport/index.d.ts +1 -1
- package/dist/{trace_hub-Cob54qRx.d.cts → trace_hub-Cpo_nVze.d.cts} +21 -1
- package/dist/{trace_hub-Cob54qRx.d.ts → trace_hub-Cpo_nVze.d.ts} +21 -1
- package/dist/tracing/index.cjs +9 -2
- package/dist/tracing/index.cjs.map +1 -1
- package/dist/tracing/index.d.cts +3 -2
- package/dist/tracing/index.d.ts +3 -2
- package/dist/tracing/index.js +7 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.cts +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.ts +1 -1
- package/dist/tracing/renderer_trace_batch_transport.cjs +48 -6
- package/dist/tracing/renderer_trace_batch_transport.cjs.map +1 -1
- package/dist/tracing/renderer_trace_batch_transport.d.cts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.d.ts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.js +48 -6
- package/dist/tracing/renderer_trace_batch_transport.js.map +1 -1
- package/dist/tracing/trace_batch_adapter.cjs +110 -0
- package/dist/tracing/trace_batch_adapter.cjs.map +1 -0
- package/dist/tracing/trace_batch_adapter.d.cts +36 -0
- package/dist/tracing/trace_batch_adapter.d.ts +36 -0
- package/dist/tracing/trace_batch_adapter.js +86 -0
- package/dist/tracing/trace_batch_adapter.js.map +1 -0
- package/dist/tracing/trace_commit_writer.d.cts +1 -1
- package/dist/tracing/trace_commit_writer.d.ts +1 -1
- package/dist/tracing/trace_hub.cjs +87 -20
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.d.cts +1 -1
- package/dist/tracing/trace_hub.d.ts +1 -1
- package/dist/tracing/trace_hub.js +85 -20
- package/dist/tracing/trace_hub.js.map +1 -1
- package/dist/tracing/trace_provider.cjs +21 -6
- package/dist/tracing/trace_provider.cjs.map +1 -1
- package/dist/tracing/trace_provider.d.cts +13 -4
- package/dist/tracing/trace_provider.d.ts +13 -4
- package/dist/tracing/trace_provider.js +22 -7
- package/dist/tracing/trace_provider.js.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.cjs.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.d.cts +15 -1
- package/dist/tracing/types/wood_trace_batch.types.d.ts +15 -1
- package/dist/tracing/types/wood_trace_sink.types.d.cts +1 -1
- package/dist/tracing/types/wood_trace_sink.types.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.cjs +2 -0
- package/dist/tracing/wood_trace_batch_contract.cjs.map +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.cts +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.js +2 -0
- package/dist/tracing/wood_trace_batch_contract.js.map +1 -1
- package/dist/transport/build_transport_bridge.cjs +4 -2
- package/dist/transport/build_transport_bridge.cjs.map +1 -1
- package/dist/transport/build_transport_bridge.d.cts +7 -1
- package/dist/transport/build_transport_bridge.d.ts +7 -1
- package/dist/transport/build_transport_bridge.js +2 -1
- package/dist/transport/build_transport_bridge.js.map +1 -1
- package/dist/transport/in_process_router_transport.d.cts +1 -0
- package/dist/transport/in_process_router_transport.d.ts +1 -0
- package/dist/transport/index.d.cts +1 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +47 -1
- package/dist/types/config.d.ts +47 -1
- package/dist/types/context.cjs +12 -0
- package/dist/types/context.cjs.map +1 -1
- package/dist/types/context.d.cts +27 -1
- package/dist/types/context.d.ts +27 -1
- package/dist/types/context.js +4 -0
- package/dist/types/context.js.map +1 -1
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/operations.cjs.map +1 -1
- package/dist/types/operations.d.cts +2 -0
- package/dist/types/operations.d.ts +2 -0
- package/docs/axe.md +706 -0
- package/package.json +19 -5
- package/src/components/NavigationShell.scopes.browser.test.ts +363 -0
- package/src/components/NavigationShell.svelte +126 -49
- package/dist/controller/__fixtures__/controllers/chat.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/chat.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/chat.d.cts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.d.ts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.js +0 -12
- package/dist/controller/__fixtures__/controllers/chat.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.d.cts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.d.ts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.js +0 -5
- package/dist/controller/__fixtures__/controllers/not_a_class.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.d.cts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.d.ts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.d.cts +0 -7
- package/dist/controller/__fixtures__/controllers/test.d.ts +0 -7
- package/dist/controller/__fixtures__/controllers/test.js +0 -9
- package/dist/controller/__fixtures__/controllers/test.js.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.d.cts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.d.ts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.js +0 -7
- package/dist/loader/__fixtures__/layouts/app.load.js.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.d.cts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.d.ts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.js +0 -7
- package/dist/loader/__fixtures__/views/test/page.load.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.d.cts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.d.ts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.js +0 -19
- package/dist/middleware/__fixtures__/middleware/auth.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.d.cts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.d.ts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.js +0 -21
- package/dist/middleware/__fixtures__/middleware/sequence.js.map +0 -1
|
@@ -22,6 +22,7 @@ __export(create_controller_harness_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(create_controller_harness_exports);
|
|
24
24
|
var import_node_fs = require("node:fs");
|
|
25
|
+
var import_ioc = require("@noego/ioc");
|
|
25
26
|
var import_client = require("../client/index.js");
|
|
26
27
|
var import_ipc = require("../ipc/index.js");
|
|
27
28
|
var import_navigation = require("../navigation/index.js");
|
|
@@ -29,6 +30,8 @@ var import_tracing = require("../tracing/index.js");
|
|
|
29
30
|
var import_trace_testing = require("../trace-testing/index.js");
|
|
30
31
|
var import_operations_parser = require("../parser/operations_parser.cjs");
|
|
31
32
|
var import_controller_lifecycle = require("../controller/controller_lifecycle.cjs");
|
|
33
|
+
var import_get_container = require("../runtime/get_container.js");
|
|
34
|
+
var import_build_transport_bridge = require("../transport/build_transport_bridge.js");
|
|
32
35
|
var import_create_router_bridge = require("./create_router_bridge.js");
|
|
33
36
|
var import_create_test_router = require("./create_test_router.js");
|
|
34
37
|
function readOperations(input) {
|
|
@@ -52,7 +55,7 @@ function createRpcManifest(operations) {
|
|
|
52
55
|
controller: operation.controller,
|
|
53
56
|
action: operation.action,
|
|
54
57
|
path: operation.channel.split("."),
|
|
55
|
-
hasInput:
|
|
58
|
+
hasInput: (0, import_build_transport_bridge.operationHasInput)(operation)
|
|
56
59
|
}));
|
|
57
60
|
}
|
|
58
61
|
function registerClientBuiltins(container) {
|
|
@@ -63,11 +66,12 @@ function registerClientBuiltins(container) {
|
|
|
63
66
|
traceConfiguration.configureMode("test");
|
|
64
67
|
traceConfiguration.configureRuntime("renderer");
|
|
65
68
|
traceConfiguration.configureEnabled(true);
|
|
66
|
-
|
|
67
|
-
container.registerFunction(
|
|
68
|
-
container.registerFunction(import_client.
|
|
69
|
-
container.registerFunction(
|
|
70
|
-
container.registerFunction(
|
|
69
|
+
const singleton = { loadAs: import_ioc.LoadAs.Singleton };
|
|
70
|
+
container.registerFunction(import_navigation.Navigation, () => (0, import_navigation.getNavigation)(), singleton);
|
|
71
|
+
container.registerFunction(import_client.App, () => (0, import_client.getApp)(), singleton);
|
|
72
|
+
container.registerFunction(import_client.Ipc, () => (0, import_ipc.getIpc)(), singleton);
|
|
73
|
+
container.registerFunction(WindowControls, () => (0, import_client.getWindowControls)(), singleton);
|
|
74
|
+
container.registerFunction(import_tracing.TraceProviderConfiguration, () => traceConfiguration, singleton);
|
|
71
75
|
}
|
|
72
76
|
class WindowControls {
|
|
73
77
|
async open(_windowId, _options) {
|
|
@@ -110,7 +114,7 @@ async function createControllerHarness(options) {
|
|
|
110
114
|
(0, import_client.clearClientNavigationForTests)();
|
|
111
115
|
(0, import_client.clearClientWindowControlsForTests)();
|
|
112
116
|
(0, import_client.clearClientLoggersForTests)();
|
|
113
|
-
const scopedContainer = options.container.extend();
|
|
117
|
+
const scopedContainer = options.clientContainer === "extend" ? options.container.extend() : (0, import_get_container.createWoodRoot)();
|
|
114
118
|
registerClientBuiltins(scopedContainer);
|
|
115
119
|
if (typeof scopedContainer.registerClass === "function") {
|
|
116
120
|
scopedContainer.registerClass(options.controllerClass);
|
|
@@ -120,9 +124,24 @@ async function createControllerHarness(options) {
|
|
|
120
124
|
bridge: harnessBridge,
|
|
121
125
|
events,
|
|
122
126
|
navigation,
|
|
123
|
-
container: scopedContainer
|
|
127
|
+
container: scopedContainer,
|
|
128
|
+
backendContainer: options.container
|
|
124
129
|
});
|
|
125
130
|
const controller = await scopedContainer.instance(options.controllerClass);
|
|
131
|
+
const bridgeHandlers = (surface) => {
|
|
132
|
+
if (!surface || typeof surface !== "object") return;
|
|
133
|
+
const record = surface;
|
|
134
|
+
for (const key of Object.keys(record)) {
|
|
135
|
+
const handler = record[key];
|
|
136
|
+
if (typeof handler !== "function") continue;
|
|
137
|
+
record[key] = (...args) => import_ioc.ExecutionContext.current() !== void 0 ? handler(...args) : import_ioc.ExecutionContext.run(
|
|
138
|
+
scopedContainer,
|
|
139
|
+
() => handler(...args)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
bridgeHandlers(controller.input);
|
|
144
|
+
bridgeHandlers(controller.events);
|
|
126
145
|
const trace = await scopedContainer.instance(import_tracing.TraceProvider);
|
|
127
146
|
const traceRecorder = await scopedContainer.instance(import_trace_testing.TraceRecorder);
|
|
128
147
|
if (options.initialize && typeof controller.initialize === "function") {
|
|
@@ -143,6 +162,7 @@ async function createControllerHarness(options) {
|
|
|
143
162
|
events,
|
|
144
163
|
router,
|
|
145
164
|
container: scopedContainer,
|
|
165
|
+
backendContainer: options.container,
|
|
146
166
|
navigation,
|
|
147
167
|
traceRecorder,
|
|
148
168
|
destroy: async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/create_controller_harness.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport {\n App,\n clearClientAppForTests,\n clearClientContainerForTests,\n clearClientLoggersForTests,\n clearClientNavigationForTests,\n clearClientWindowControlsForTests,\n getApp,\n getWindowControls,\n Ipc,\n WOOD_INTERNAL_BRIDGE_KEY,\n} from '../client/index.js';\nimport { getIpc } from '../ipc/index.js';\nimport { Navigation, getNavigation } from '../navigation/index.js';\nimport { TraceProvider, TraceProviderConfiguration } from '../tracing/index.js';\nimport { TraceRecorder } from '../trace-testing/index.js';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport type { OperationDef } from '../types/operations.cjs';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.cjs';\nimport { createRouterBridge, type RouterBridgeResult } from './create_router_bridge.js';\nimport { createTestRouter } from './create_test_router.js';\n\nexport interface CreateControllerHarnessOptions<T> {\n controllerClass: new (...args: any[]) => T;\n container: any;\n operations: string | string[];\n controllersDir: string;\n middlewareDir?: string;\n configureContainer?: (container: any) => void | Promise<void>;\n configureRouter?: (router: ReturnType<typeof createTestRouter>['router']) => void | Promise<void>;\n prepareClient?: (context: {\n bridge: Record<string, any>;\n events: RouterBridgeResult['events'];\n navigation: Navigation;\n container: any;\n }) => void | Promise<void>;\n initialize?: boolean;\n}\n\nexport interface ControllerHarnessResult<T> extends RouterBridgeResult {\n controller: T;\n container: any;\n router: ReturnType<typeof createTestRouter>['router'];\n navigation: Navigation;\n traceRecorder: TraceRecorder;\n destroy: () => void | Promise<void>;\n}\n\ntype MutableGlobal = typeof globalThis & {\n [WOOD_INTERNAL_BRIDGE_KEY]?: unknown;\n wood?: unknown;\n};\n\nfunction readOperations(input: string): Map<string, OperationDef> {\n if (input.includes('\\n') || input.includes(':')) {\n return parseOperations(input);\n }\n\n return parseOperations(readFileSync(input, 'utf-8'));\n}\n\nfunction mergeOperations(inputs: string | string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n\n for (const input of Array.isArray(inputs) ? inputs : [inputs]) {\n for (const [channel, operation] of readOperations(input)) {\n merged.set(channel, operation);\n }\n }\n\n return merged;\n}\n\nfunction createRpcManifest(operations: Map<string, OperationDef>) {\n return Array.from(operations.values()).map((operation) => ({\n channel: operation.channel,\n controller: operation.controller,\n action: operation.action,\n path: operation.channel.split('.'),\n hasInput: !!operation.inputSchema,\n }));\n}\n\nfunction registerClientBuiltins(container: any): void {\n if (typeof container.registerFunction !== 'function') {\n return;\n }\n\n const traceConfiguration = new TraceProviderConfiguration();\n traceConfiguration.configureMode('test');\n traceConfiguration.configureRuntime('renderer');\n traceConfiguration.configureEnabled(true);\n\n container.registerFunction(Navigation, () => getNavigation());\n container.registerFunction(App, () => getApp());\n container.registerFunction(Ipc, () => getIpc());\n container.registerFunction(WindowControls, () => getWindowControls());\n container.registerFunction(TraceProviderConfiguration, () => traceConfiguration);\n}\n\nclass WindowControls {\n async open(_windowId: string, _options?: Record<string, unknown>): Promise<void> {}\n async close(_windowId?: string): Promise<void> {}\n async focus(_windowId: string): Promise<void> {}\n async minimize(_windowId?: string): Promise<void> {}\n async maximize(_windowId?: string): Promise<void> {}\n}\n\nexport async function createControllerHarness<T>(\n options: CreateControllerHarnessOptions<T>,\n): Promise<ControllerHarnessResult<T>> {\n await options.configureContainer?.(options.container);\n\n if (typeof options.container.instance === 'function') {\n const rootTraceConfiguration = await options.container.instance(TraceProviderConfiguration);\n rootTraceConfiguration.configureMode('test');\n rootTraceConfiguration.configureRuntime('main');\n }\n\n const operations = mergeOperations(options.operations);\n const { router } = createTestRouter('', {\n container: options.container,\n controllersDir: options.controllersDir,\n middlewareDir: options.middlewareDir,\n });\n router.loadOperations(operations);\n await options.configureRouter?.(router);\n\n const { bridge, events } = createRouterBridge(router);\n const harnessBridge = Object.assign(bridge, {\n __rpcManifest: createRpcManifest(operations),\n });\n\n const globalObj = globalThis as MutableGlobal;\n const previousInternalBridge = globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n const previousWoodBridge = globalObj.wood;\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = harnessBridge;\n globalObj.wood = harnessBridge;\n\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n const scopedContainer = options.container.extend();\n registerClientBuiltins(scopedContainer);\n if (typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(options.controllerClass);\n }\n const navigation = getNavigation();\n await options.prepareClient?.({\n bridge: harnessBridge,\n events,\n navigation,\n container: scopedContainer,\n });\n\n const controller = await scopedContainer.instance(options.controllerClass);\n const trace = await scopedContainer.instance(TraceProvider);\n const traceRecorder = await scopedContainer.instance(TraceRecorder);\n if (options.initialize && typeof (controller as { initialize?: () => void }).initialize === 'function') {\n await Promise.resolve(\n (controller as { initialize: () => void | Promise<void> }).initialize(),\n );\n }\n\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.created',\n payload: {\n controllerClass: options.controllerClass.name,\n },\n });\n\n return {\n controller,\n bridge: harnessBridge,\n events,\n router,\n container: scopedContainer,\n navigation,\n traceRecorder,\n destroy: async () => {\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.destroyed',\n });\n await disposeControllerLifecycle(controller);\n events.clear();\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n if (previousInternalBridge === undefined) {\n delete globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n } else {\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = previousInternalBridge;\n }\n\n if (previousWoodBridge === undefined) {\n delete globalObj.wood;\n } else {\n globalObj.wood = previousWoodBridge;\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/create_controller_harness.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { LoadAs, ExecutionContext } from '@noego/ioc';\nimport {\n App,\n clearClientAppForTests,\n clearClientContainerForTests,\n clearClientLoggersForTests,\n clearClientNavigationForTests,\n clearClientWindowControlsForTests,\n getApp,\n getWindowControls,\n Ipc,\n WOOD_INTERNAL_BRIDGE_KEY,\n} from '../client/index.js';\nimport { getIpc } from '../ipc/index.js';\nimport { Navigation, getNavigation } from '../navigation/index.js';\nimport { TraceProvider, TraceProviderConfiguration } from '../tracing/index.js';\nimport { TraceRecorder } from '../trace-testing/index.js';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport type { OperationDef } from '../types/operations.cjs';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.cjs';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport { operationHasInput } from '../transport/build_transport_bridge.js';\nimport { createRouterBridge, type RouterBridgeResult } from './create_router_bridge.js';\nimport { createTestRouter } from './create_test_router.js';\n\nexport interface CreateControllerHarnessOptions<T> {\n controllerClass: new (...args: any[]) => T;\n container: any;\n operations: string | string[];\n controllersDir: string;\n middlewareDir?: string;\n configureContainer?: (container: any) => void | Promise<void>;\n configureRouter?: (router: ReturnType<typeof createTestRouter>['router']) => void | Promise<void>;\n prepareClient?: (context: {\n bridge: Record<string, any>;\n events: RouterBridgeResult['events'];\n navigation: Navigation;\n /** The renderer root (fresh per harness). */\n container: any;\n /** The backend root (options.container) — resolve backend services here. */\n backendContainer: any;\n }) => void | Promise<void>;\n initialize?: boolean;\n /**\n * How the client (renderer) container relates to the backend root.\n * 'fresh-root' (default) mirrors production's process split: a separate\n * renderer hierarchy, cross-root traffic through the bridge globals only.\n * 'extend' is the merged node-frontend tier: renderer services resolve\n * backend singletons directly in one hierarchy — for integration tests\n * that intentionally span both sides in-process.\n */\n clientContainer?: 'fresh-root' | 'extend';\n}\n\nexport interface ControllerHarnessResult<T> extends RouterBridgeResult {\n controller: T;\n /** The renderer root the controller lives in (fresh per harness). */\n container: any;\n /** The backend root supplied via options.container — resolve backend services here. */\n backendContainer: any;\n router: ReturnType<typeof createTestRouter>['router'];\n navigation: Navigation;\n traceRecorder: TraceRecorder;\n destroy: () => void | Promise<void>;\n}\n\ntype MutableGlobal = typeof globalThis & {\n [WOOD_INTERNAL_BRIDGE_KEY]?: unknown;\n wood?: unknown;\n};\n\nfunction readOperations(input: string): Map<string, OperationDef> {\n if (input.includes('\\n') || input.includes(':')) {\n return parseOperations(input);\n }\n\n return parseOperations(readFileSync(input, 'utf-8'));\n}\n\nfunction mergeOperations(inputs: string | string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n\n for (const input of Array.isArray(inputs) ? inputs : [inputs]) {\n for (const [channel, operation] of readOperations(input)) {\n merged.set(channel, operation);\n }\n }\n\n return merged;\n}\n\nfunction createRpcManifest(operations: Map<string, OperationDef>) {\n return Array.from(operations.values()).map((operation) => ({\n channel: operation.channel,\n controller: operation.controller,\n action: operation.action,\n path: operation.channel.split('.'),\n hasInput: operationHasInput(operation),\n }));\n}\n\nfunction registerClientBuiltins(container: any): void {\n if (typeof container.registerFunction !== 'function') {\n return;\n }\n\n const traceConfiguration = new TraceProviderConfiguration();\n traceConfiguration.configureMode('test');\n traceConfiguration.configureRuntime('renderer');\n traceConfiguration.configureEnabled(true);\n\n // Explicit Singleton lifetime: these factories return root-global values,\n // and registerFunction defaults to Transient, which would override the\n // classes' own @Component(Singleton) declarations and trip ioc's\n // captive-lifetime validation for singleton dependents.\n const singleton = { loadAs: LoadAs.Singleton };\n container.registerFunction(Navigation, () => getNavigation(), singleton);\n container.registerFunction(App, () => getApp(), singleton);\n container.registerFunction(Ipc, () => getIpc(), singleton);\n container.registerFunction(WindowControls, () => getWindowControls(), singleton);\n container.registerFunction(TraceProviderConfiguration, () => traceConfiguration, singleton);\n}\n\nclass WindowControls {\n async open(_windowId: string, _options?: Record<string, unknown>): Promise<void> {}\n async close(_windowId?: string): Promise<void> {}\n async focus(_windowId: string): Promise<void> {}\n async minimize(_windowId?: string): Promise<void> {}\n async maximize(_windowId?: string): Promise<void> {}\n}\n\nexport async function createControllerHarness<T>(\n options: CreateControllerHarnessOptions<T>,\n): Promise<ControllerHarnessResult<T>> {\n await options.configureContainer?.(options.container);\n\n if (typeof options.container.instance === 'function') {\n const rootTraceConfiguration = await options.container.instance(TraceProviderConfiguration);\n rootTraceConfiguration.configureMode('test');\n rootTraceConfiguration.configureRuntime('main');\n }\n\n const operations = mergeOperations(options.operations);\n const { router } = createTestRouter('', {\n container: options.container,\n controllersDir: options.controllersDir,\n middlewareDir: options.middlewareDir,\n });\n router.loadOperations(operations);\n await options.configureRouter?.(router);\n\n const { bridge, events } = createRouterBridge(router);\n const harnessBridge = Object.assign(bridge, {\n __rpcManifest: createRpcManifest(operations),\n });\n\n const globalObj = globalThis as MutableGlobal;\n const previousInternalBridge = globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n const previousWoodBridge = globalObj.wood;\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = harnessBridge;\n globalObj.wood = harnessBridge;\n\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n // The client container is a FRESH RENDERER ROOT, mirroring production\n // (renderer and main are separate processes with separate roots). A child\n // scope of the backend root would share singleton storage, so renderer\n // builtins (notably TraceProviderConfiguration with runtime 'renderer')\n // would collide with the backend's own singletons. Cross-root traffic\n // flows through the bridge globals, exactly as in production.\n const scopedContainer = options.clientContainer === 'extend'\n ? options.container.extend()\n : (createWoodRoot() as any);\n registerClientBuiltins(scopedContainer);\n if (typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(options.controllerClass);\n }\n const navigation = getNavigation();\n await options.prepareClient?.({\n bridge: harnessBridge,\n events,\n navigation,\n container: scopedContainer,\n backendContainer: options.container,\n });\n\n const controller = await scopedContainer.instance(options.controllerClass);\n // Controller interaction surfaces (input/events handlers) are plain data\n // properties holding arrow functions — they bypass the IoC context proxy,\n // so invoking them from a test would run context-less and singleton\n // collaborators (e.g. TraceProvider) would resolve root-scope state\n // instead of this harness scope's registrations. Bridge them so every\n // handler runs under the harness scope unless a context is already active.\n const bridgeHandlers = (surface: unknown): void => {\n if (!surface || typeof surface !== 'object') return;\n const record = surface as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n const handler = record[key];\n if (typeof handler !== 'function') continue;\n record[key] = (...args: unknown[]) =>\n ExecutionContext.current() !== undefined\n ? (handler as (...a: unknown[]) => unknown)(...args)\n : ExecutionContext.run(scopedContainer as any, () =>\n (handler as (...a: unknown[]) => unknown)(...args),\n );\n }\n };\n bridgeHandlers((controller as { input?: unknown }).input);\n bridgeHandlers((controller as { events?: unknown }).events);\n const trace = await scopedContainer.instance(TraceProvider);\n const traceRecorder = await scopedContainer.instance(TraceRecorder);\n if (options.initialize && typeof (controller as { initialize?: () => void }).initialize === 'function') {\n await Promise.resolve(\n (controller as { initialize: () => void | Promise<void> }).initialize(),\n );\n }\n\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.created',\n payload: {\n controllerClass: options.controllerClass.name,\n },\n });\n\n return {\n controller,\n bridge: harnessBridge,\n events,\n router,\n container: scopedContainer,\n backendContainer: options.container,\n navigation,\n traceRecorder,\n destroy: async () => {\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.destroyed',\n });\n await disposeControllerLifecycle(controller);\n events.clear();\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n if (previousInternalBridge === undefined) {\n delete globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n } else {\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = previousInternalBridge;\n }\n\n if (previousWoodBridge === undefined) {\n delete globalObj.wood;\n } else {\n globalObj.wood = previousWoodBridge;\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,13 +2,14 @@ import { Navigation } from '../navigation/index.cjs';
|
|
|
2
2
|
import { TraceRecorder } from '../trace-testing/trace_recorder.cjs';
|
|
3
3
|
import { RouterBridgeResult } from './create_router_bridge.cjs';
|
|
4
4
|
import { createTestRouter } from './create_test_router.cjs';
|
|
5
|
+
import '../navigation/automation_introspection.cjs';
|
|
5
6
|
import '@noego/trace/testing';
|
|
6
|
-
import '../trace_hub-
|
|
7
|
+
import '../trace_hub-Cpo_nVze.cjs';
|
|
7
8
|
import 'rxjs';
|
|
8
9
|
import '../tracing/trace_provider.cjs';
|
|
9
|
-
import '@noego/ioc';
|
|
10
10
|
import '../router/wood_router.cjs';
|
|
11
11
|
import '../types/operations.cjs';
|
|
12
|
+
import '../types/context.cjs';
|
|
12
13
|
|
|
13
14
|
interface CreateControllerHarnessOptions<T> {
|
|
14
15
|
controllerClass: new (...args: any[]) => T;
|
|
@@ -22,13 +23,28 @@ interface CreateControllerHarnessOptions<T> {
|
|
|
22
23
|
bridge: Record<string, any>;
|
|
23
24
|
events: RouterBridgeResult['events'];
|
|
24
25
|
navigation: Navigation;
|
|
26
|
+
/** The renderer root (fresh per harness). */
|
|
25
27
|
container: any;
|
|
28
|
+
/** The backend root (options.container) — resolve backend services here. */
|
|
29
|
+
backendContainer: any;
|
|
26
30
|
}) => void | Promise<void>;
|
|
27
31
|
initialize?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* How the client (renderer) container relates to the backend root.
|
|
34
|
+
* 'fresh-root' (default) mirrors production's process split: a separate
|
|
35
|
+
* renderer hierarchy, cross-root traffic through the bridge globals only.
|
|
36
|
+
* 'extend' is the merged node-frontend tier: renderer services resolve
|
|
37
|
+
* backend singletons directly in one hierarchy — for integration tests
|
|
38
|
+
* that intentionally span both sides in-process.
|
|
39
|
+
*/
|
|
40
|
+
clientContainer?: 'fresh-root' | 'extend';
|
|
28
41
|
}
|
|
29
42
|
interface ControllerHarnessResult<T> extends RouterBridgeResult {
|
|
30
43
|
controller: T;
|
|
44
|
+
/** The renderer root the controller lives in (fresh per harness). */
|
|
31
45
|
container: any;
|
|
46
|
+
/** The backend root supplied via options.container — resolve backend services here. */
|
|
47
|
+
backendContainer: any;
|
|
32
48
|
router: ReturnType<typeof createTestRouter>['router'];
|
|
33
49
|
navigation: Navigation;
|
|
34
50
|
traceRecorder: TraceRecorder;
|
|
@@ -2,13 +2,14 @@ import { Navigation } from '../navigation/index.js';
|
|
|
2
2
|
import { TraceRecorder } from '../trace-testing/trace_recorder.js';
|
|
3
3
|
import { RouterBridgeResult } from './create_router_bridge.js';
|
|
4
4
|
import { createTestRouter } from './create_test_router.js';
|
|
5
|
+
import '../navigation/automation_introspection.js';
|
|
5
6
|
import '@noego/trace/testing';
|
|
6
|
-
import '../trace_hub-
|
|
7
|
+
import '../trace_hub-Cpo_nVze.js';
|
|
7
8
|
import 'rxjs';
|
|
8
9
|
import '../tracing/trace_provider.js';
|
|
9
|
-
import '@noego/ioc';
|
|
10
10
|
import '../router/wood_router.js';
|
|
11
11
|
import '../types/operations.js';
|
|
12
|
+
import '../types/context.js';
|
|
12
13
|
|
|
13
14
|
interface CreateControllerHarnessOptions<T> {
|
|
14
15
|
controllerClass: new (...args: any[]) => T;
|
|
@@ -22,13 +23,28 @@ interface CreateControllerHarnessOptions<T> {
|
|
|
22
23
|
bridge: Record<string, any>;
|
|
23
24
|
events: RouterBridgeResult['events'];
|
|
24
25
|
navigation: Navigation;
|
|
26
|
+
/** The renderer root (fresh per harness). */
|
|
25
27
|
container: any;
|
|
28
|
+
/** The backend root (options.container) — resolve backend services here. */
|
|
29
|
+
backendContainer: any;
|
|
26
30
|
}) => void | Promise<void>;
|
|
27
31
|
initialize?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* How the client (renderer) container relates to the backend root.
|
|
34
|
+
* 'fresh-root' (default) mirrors production's process split: a separate
|
|
35
|
+
* renderer hierarchy, cross-root traffic through the bridge globals only.
|
|
36
|
+
* 'extend' is the merged node-frontend tier: renderer services resolve
|
|
37
|
+
* backend singletons directly in one hierarchy — for integration tests
|
|
38
|
+
* that intentionally span both sides in-process.
|
|
39
|
+
*/
|
|
40
|
+
clientContainer?: 'fresh-root' | 'extend';
|
|
28
41
|
}
|
|
29
42
|
interface ControllerHarnessResult<T> extends RouterBridgeResult {
|
|
30
43
|
controller: T;
|
|
44
|
+
/** The renderer root the controller lives in (fresh per harness). */
|
|
31
45
|
container: any;
|
|
46
|
+
/** The backend root supplied via options.container — resolve backend services here. */
|
|
47
|
+
backendContainer: any;
|
|
32
48
|
router: ReturnType<typeof createTestRouter>['router'];
|
|
33
49
|
navigation: Navigation;
|
|
34
50
|
traceRecorder: TraceRecorder;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { LoadAs, ExecutionContext } from "@noego/ioc";
|
|
2
3
|
import {
|
|
3
4
|
App,
|
|
4
5
|
clearClientAppForTests,
|
|
@@ -17,6 +18,8 @@ import { TraceProvider, TraceProviderConfiguration } from "../tracing/index.js";
|
|
|
17
18
|
import { TraceRecorder } from "../trace-testing/index.js";
|
|
18
19
|
import { parseOperations } from "../parser/operations_parser.js";
|
|
19
20
|
import { disposeControllerLifecycle } from "../controller/controller_lifecycle.js";
|
|
21
|
+
import { createWoodRoot } from "../runtime/get_container.js";
|
|
22
|
+
import { operationHasInput } from "../transport/build_transport_bridge.js";
|
|
20
23
|
import { createRouterBridge } from "./create_router_bridge.js";
|
|
21
24
|
import { createTestRouter } from "./create_test_router.js";
|
|
22
25
|
function readOperations(input) {
|
|
@@ -40,7 +43,7 @@ function createRpcManifest(operations) {
|
|
|
40
43
|
controller: operation.controller,
|
|
41
44
|
action: operation.action,
|
|
42
45
|
path: operation.channel.split("."),
|
|
43
|
-
hasInput:
|
|
46
|
+
hasInput: operationHasInput(operation)
|
|
44
47
|
}));
|
|
45
48
|
}
|
|
46
49
|
function registerClientBuiltins(container) {
|
|
@@ -51,11 +54,12 @@ function registerClientBuiltins(container) {
|
|
|
51
54
|
traceConfiguration.configureMode("test");
|
|
52
55
|
traceConfiguration.configureRuntime("renderer");
|
|
53
56
|
traceConfiguration.configureEnabled(true);
|
|
54
|
-
|
|
55
|
-
container.registerFunction(
|
|
56
|
-
container.registerFunction(
|
|
57
|
-
container.registerFunction(
|
|
58
|
-
container.registerFunction(
|
|
57
|
+
const singleton = { loadAs: LoadAs.Singleton };
|
|
58
|
+
container.registerFunction(Navigation, () => getNavigation(), singleton);
|
|
59
|
+
container.registerFunction(App, () => getApp(), singleton);
|
|
60
|
+
container.registerFunction(Ipc, () => getIpc(), singleton);
|
|
61
|
+
container.registerFunction(WindowControls, () => getWindowControls(), singleton);
|
|
62
|
+
container.registerFunction(TraceProviderConfiguration, () => traceConfiguration, singleton);
|
|
59
63
|
}
|
|
60
64
|
class WindowControls {
|
|
61
65
|
async open(_windowId, _options) {
|
|
@@ -98,7 +102,7 @@ async function createControllerHarness(options) {
|
|
|
98
102
|
clearClientNavigationForTests();
|
|
99
103
|
clearClientWindowControlsForTests();
|
|
100
104
|
clearClientLoggersForTests();
|
|
101
|
-
const scopedContainer = options.container.extend();
|
|
105
|
+
const scopedContainer = options.clientContainer === "extend" ? options.container.extend() : createWoodRoot();
|
|
102
106
|
registerClientBuiltins(scopedContainer);
|
|
103
107
|
if (typeof scopedContainer.registerClass === "function") {
|
|
104
108
|
scopedContainer.registerClass(options.controllerClass);
|
|
@@ -108,9 +112,24 @@ async function createControllerHarness(options) {
|
|
|
108
112
|
bridge: harnessBridge,
|
|
109
113
|
events,
|
|
110
114
|
navigation,
|
|
111
|
-
container: scopedContainer
|
|
115
|
+
container: scopedContainer,
|
|
116
|
+
backendContainer: options.container
|
|
112
117
|
});
|
|
113
118
|
const controller = await scopedContainer.instance(options.controllerClass);
|
|
119
|
+
const bridgeHandlers = (surface) => {
|
|
120
|
+
if (!surface || typeof surface !== "object") return;
|
|
121
|
+
const record = surface;
|
|
122
|
+
for (const key of Object.keys(record)) {
|
|
123
|
+
const handler = record[key];
|
|
124
|
+
if (typeof handler !== "function") continue;
|
|
125
|
+
record[key] = (...args) => ExecutionContext.current() !== void 0 ? handler(...args) : ExecutionContext.run(
|
|
126
|
+
scopedContainer,
|
|
127
|
+
() => handler(...args)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
bridgeHandlers(controller.input);
|
|
132
|
+
bridgeHandlers(controller.events);
|
|
114
133
|
const trace = await scopedContainer.instance(TraceProvider);
|
|
115
134
|
const traceRecorder = await scopedContainer.instance(TraceRecorder);
|
|
116
135
|
if (options.initialize && typeof controller.initialize === "function") {
|
|
@@ -131,6 +150,7 @@ async function createControllerHarness(options) {
|
|
|
131
150
|
events,
|
|
132
151
|
router,
|
|
133
152
|
container: scopedContainer,
|
|
153
|
+
backendContainer: options.container,
|
|
134
154
|
navigation,
|
|
135
155
|
traceRecorder,
|
|
136
156
|
destroy: async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/create_controller_harness.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport {\n App,\n clearClientAppForTests,\n clearClientContainerForTests,\n clearClientLoggersForTests,\n clearClientNavigationForTests,\n clearClientWindowControlsForTests,\n getApp,\n getWindowControls,\n Ipc,\n WOOD_INTERNAL_BRIDGE_KEY,\n} from '../client/index.js';\nimport { getIpc } from '../ipc/index.js';\nimport { Navigation, getNavigation } from '../navigation/index.js';\nimport { TraceProvider, TraceProviderConfiguration } from '../tracing/index.js';\nimport { TraceRecorder } from '../trace-testing/index.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport type { OperationDef } from '../types/operations.js';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.js';\nimport { createRouterBridge, type RouterBridgeResult } from './create_router_bridge.js';\nimport { createTestRouter } from './create_test_router.js';\n\nexport interface CreateControllerHarnessOptions<T> {\n controllerClass: new (...args: any[]) => T;\n container: any;\n operations: string | string[];\n controllersDir: string;\n middlewareDir?: string;\n configureContainer?: (container: any) => void | Promise<void>;\n configureRouter?: (router: ReturnType<typeof createTestRouter>['router']) => void | Promise<void>;\n prepareClient?: (context: {\n bridge: Record<string, any>;\n events: RouterBridgeResult['events'];\n navigation: Navigation;\n container: any;\n }) => void | Promise<void>;\n initialize?: boolean;\n}\n\nexport interface ControllerHarnessResult<T> extends RouterBridgeResult {\n controller: T;\n container: any;\n router: ReturnType<typeof createTestRouter>['router'];\n navigation: Navigation;\n traceRecorder: TraceRecorder;\n destroy: () => void | Promise<void>;\n}\n\ntype MutableGlobal = typeof globalThis & {\n [WOOD_INTERNAL_BRIDGE_KEY]?: unknown;\n wood?: unknown;\n};\n\nfunction readOperations(input: string): Map<string, OperationDef> {\n if (input.includes('\\n') || input.includes(':')) {\n return parseOperations(input);\n }\n\n return parseOperations(readFileSync(input, 'utf-8'));\n}\n\nfunction mergeOperations(inputs: string | string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n\n for (const input of Array.isArray(inputs) ? inputs : [inputs]) {\n for (const [channel, operation] of readOperations(input)) {\n merged.set(channel, operation);\n }\n }\n\n return merged;\n}\n\nfunction createRpcManifest(operations: Map<string, OperationDef>) {\n return Array.from(operations.values()).map((operation) => ({\n channel: operation.channel,\n controller: operation.controller,\n action: operation.action,\n path: operation.channel.split('.'),\n hasInput: !!operation.inputSchema,\n }));\n}\n\nfunction registerClientBuiltins(container: any): void {\n if (typeof container.registerFunction !== 'function') {\n return;\n }\n\n const traceConfiguration = new TraceProviderConfiguration();\n traceConfiguration.configureMode('test');\n traceConfiguration.configureRuntime('renderer');\n traceConfiguration.configureEnabled(true);\n\n container.registerFunction(Navigation, () => getNavigation());\n container.registerFunction(App, () => getApp());\n container.registerFunction(Ipc, () => getIpc());\n container.registerFunction(WindowControls, () => getWindowControls());\n container.registerFunction(TraceProviderConfiguration, () => traceConfiguration);\n}\n\nclass WindowControls {\n async open(_windowId: string, _options?: Record<string, unknown>): Promise<void> {}\n async close(_windowId?: string): Promise<void> {}\n async focus(_windowId: string): Promise<void> {}\n async minimize(_windowId?: string): Promise<void> {}\n async maximize(_windowId?: string): Promise<void> {}\n}\n\nexport async function createControllerHarness<T>(\n options: CreateControllerHarnessOptions<T>,\n): Promise<ControllerHarnessResult<T>> {\n await options.configureContainer?.(options.container);\n\n if (typeof options.container.instance === 'function') {\n const rootTraceConfiguration = await options.container.instance(TraceProviderConfiguration);\n rootTraceConfiguration.configureMode('test');\n rootTraceConfiguration.configureRuntime('main');\n }\n\n const operations = mergeOperations(options.operations);\n const { router } = createTestRouter('', {\n container: options.container,\n controllersDir: options.controllersDir,\n middlewareDir: options.middlewareDir,\n });\n router.loadOperations(operations);\n await options.configureRouter?.(router);\n\n const { bridge, events } = createRouterBridge(router);\n const harnessBridge = Object.assign(bridge, {\n __rpcManifest: createRpcManifest(operations),\n });\n\n const globalObj = globalThis as MutableGlobal;\n const previousInternalBridge = globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n const previousWoodBridge = globalObj.wood;\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = harnessBridge;\n globalObj.wood = harnessBridge;\n\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n const scopedContainer = options.container.extend();\n registerClientBuiltins(scopedContainer);\n if (typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(options.controllerClass);\n }\n const navigation = getNavigation();\n await options.prepareClient?.({\n bridge: harnessBridge,\n events,\n navigation,\n container: scopedContainer,\n });\n\n const controller = await scopedContainer.instance(options.controllerClass);\n const trace = await scopedContainer.instance(TraceProvider);\n const traceRecorder = await scopedContainer.instance(TraceRecorder);\n if (options.initialize && typeof (controller as { initialize?: () => void }).initialize === 'function') {\n await Promise.resolve(\n (controller as { initialize: () => void | Promise<void> }).initialize(),\n );\n }\n\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.created',\n payload: {\n controllerClass: options.controllerClass.name,\n },\n });\n\n return {\n controller,\n bridge: harnessBridge,\n events,\n router,\n container: scopedContainer,\n navigation,\n traceRecorder,\n destroy: async () => {\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.destroyed',\n });\n await disposeControllerLifecycle(controller);\n events.clear();\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n if (previousInternalBridge === undefined) {\n delete globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n } else {\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = previousInternalBridge;\n }\n\n if (previousWoodBridge === undefined) {\n delete globalObj.wood;\n } else {\n globalObj.wood = previousWoodBridge;\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/create_controller_harness.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { LoadAs, ExecutionContext } from '@noego/ioc';\nimport {\n App,\n clearClientAppForTests,\n clearClientContainerForTests,\n clearClientLoggersForTests,\n clearClientNavigationForTests,\n clearClientWindowControlsForTests,\n getApp,\n getWindowControls,\n Ipc,\n WOOD_INTERNAL_BRIDGE_KEY,\n} from '../client/index.js';\nimport { getIpc } from '../ipc/index.js';\nimport { Navigation, getNavigation } from '../navigation/index.js';\nimport { TraceProvider, TraceProviderConfiguration } from '../tracing/index.js';\nimport { TraceRecorder } from '../trace-testing/index.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport type { OperationDef } from '../types/operations.js';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.js';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport { operationHasInput } from '../transport/build_transport_bridge.js';\nimport { createRouterBridge, type RouterBridgeResult } from './create_router_bridge.js';\nimport { createTestRouter } from './create_test_router.js';\n\nexport interface CreateControllerHarnessOptions<T> {\n controllerClass: new (...args: any[]) => T;\n container: any;\n operations: string | string[];\n controllersDir: string;\n middlewareDir?: string;\n configureContainer?: (container: any) => void | Promise<void>;\n configureRouter?: (router: ReturnType<typeof createTestRouter>['router']) => void | Promise<void>;\n prepareClient?: (context: {\n bridge: Record<string, any>;\n events: RouterBridgeResult['events'];\n navigation: Navigation;\n /** The renderer root (fresh per harness). */\n container: any;\n /** The backend root (options.container) — resolve backend services here. */\n backendContainer: any;\n }) => void | Promise<void>;\n initialize?: boolean;\n /**\n * How the client (renderer) container relates to the backend root.\n * 'fresh-root' (default) mirrors production's process split: a separate\n * renderer hierarchy, cross-root traffic through the bridge globals only.\n * 'extend' is the merged node-frontend tier: renderer services resolve\n * backend singletons directly in one hierarchy — for integration tests\n * that intentionally span both sides in-process.\n */\n clientContainer?: 'fresh-root' | 'extend';\n}\n\nexport interface ControllerHarnessResult<T> extends RouterBridgeResult {\n controller: T;\n /** The renderer root the controller lives in (fresh per harness). */\n container: any;\n /** The backend root supplied via options.container — resolve backend services here. */\n backendContainer: any;\n router: ReturnType<typeof createTestRouter>['router'];\n navigation: Navigation;\n traceRecorder: TraceRecorder;\n destroy: () => void | Promise<void>;\n}\n\ntype MutableGlobal = typeof globalThis & {\n [WOOD_INTERNAL_BRIDGE_KEY]?: unknown;\n wood?: unknown;\n};\n\nfunction readOperations(input: string): Map<string, OperationDef> {\n if (input.includes('\\n') || input.includes(':')) {\n return parseOperations(input);\n }\n\n return parseOperations(readFileSync(input, 'utf-8'));\n}\n\nfunction mergeOperations(inputs: string | string[]): Map<string, OperationDef> {\n const merged = new Map<string, OperationDef>();\n\n for (const input of Array.isArray(inputs) ? inputs : [inputs]) {\n for (const [channel, operation] of readOperations(input)) {\n merged.set(channel, operation);\n }\n }\n\n return merged;\n}\n\nfunction createRpcManifest(operations: Map<string, OperationDef>) {\n return Array.from(operations.values()).map((operation) => ({\n channel: operation.channel,\n controller: operation.controller,\n action: operation.action,\n path: operation.channel.split('.'),\n hasInput: operationHasInput(operation),\n }));\n}\n\nfunction registerClientBuiltins(container: any): void {\n if (typeof container.registerFunction !== 'function') {\n return;\n }\n\n const traceConfiguration = new TraceProviderConfiguration();\n traceConfiguration.configureMode('test');\n traceConfiguration.configureRuntime('renderer');\n traceConfiguration.configureEnabled(true);\n\n // Explicit Singleton lifetime: these factories return root-global values,\n // and registerFunction defaults to Transient, which would override the\n // classes' own @Component(Singleton) declarations and trip ioc's\n // captive-lifetime validation for singleton dependents.\n const singleton = { loadAs: LoadAs.Singleton };\n container.registerFunction(Navigation, () => getNavigation(), singleton);\n container.registerFunction(App, () => getApp(), singleton);\n container.registerFunction(Ipc, () => getIpc(), singleton);\n container.registerFunction(WindowControls, () => getWindowControls(), singleton);\n container.registerFunction(TraceProviderConfiguration, () => traceConfiguration, singleton);\n}\n\nclass WindowControls {\n async open(_windowId: string, _options?: Record<string, unknown>): Promise<void> {}\n async close(_windowId?: string): Promise<void> {}\n async focus(_windowId: string): Promise<void> {}\n async minimize(_windowId?: string): Promise<void> {}\n async maximize(_windowId?: string): Promise<void> {}\n}\n\nexport async function createControllerHarness<T>(\n options: CreateControllerHarnessOptions<T>,\n): Promise<ControllerHarnessResult<T>> {\n await options.configureContainer?.(options.container);\n\n if (typeof options.container.instance === 'function') {\n const rootTraceConfiguration = await options.container.instance(TraceProviderConfiguration);\n rootTraceConfiguration.configureMode('test');\n rootTraceConfiguration.configureRuntime('main');\n }\n\n const operations = mergeOperations(options.operations);\n const { router } = createTestRouter('', {\n container: options.container,\n controllersDir: options.controllersDir,\n middlewareDir: options.middlewareDir,\n });\n router.loadOperations(operations);\n await options.configureRouter?.(router);\n\n const { bridge, events } = createRouterBridge(router);\n const harnessBridge = Object.assign(bridge, {\n __rpcManifest: createRpcManifest(operations),\n });\n\n const globalObj = globalThis as MutableGlobal;\n const previousInternalBridge = globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n const previousWoodBridge = globalObj.wood;\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = harnessBridge;\n globalObj.wood = harnessBridge;\n\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n // The client container is a FRESH RENDERER ROOT, mirroring production\n // (renderer and main are separate processes with separate roots). A child\n // scope of the backend root would share singleton storage, so renderer\n // builtins (notably TraceProviderConfiguration with runtime 'renderer')\n // would collide with the backend's own singletons. Cross-root traffic\n // flows through the bridge globals, exactly as in production.\n const scopedContainer = options.clientContainer === 'extend'\n ? options.container.extend()\n : (createWoodRoot() as any);\n registerClientBuiltins(scopedContainer);\n if (typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(options.controllerClass);\n }\n const navigation = getNavigation();\n await options.prepareClient?.({\n bridge: harnessBridge,\n events,\n navigation,\n container: scopedContainer,\n backendContainer: options.container,\n });\n\n const controller = await scopedContainer.instance(options.controllerClass);\n // Controller interaction surfaces (input/events handlers) are plain data\n // properties holding arrow functions — they bypass the IoC context proxy,\n // so invoking them from a test would run context-less and singleton\n // collaborators (e.g. TraceProvider) would resolve root-scope state\n // instead of this harness scope's registrations. Bridge them so every\n // handler runs under the harness scope unless a context is already active.\n const bridgeHandlers = (surface: unknown): void => {\n if (!surface || typeof surface !== 'object') return;\n const record = surface as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n const handler = record[key];\n if (typeof handler !== 'function') continue;\n record[key] = (...args: unknown[]) =>\n ExecutionContext.current() !== undefined\n ? (handler as (...a: unknown[]) => unknown)(...args)\n : ExecutionContext.run(scopedContainer as any, () =>\n (handler as (...a: unknown[]) => unknown)(...args),\n );\n }\n };\n bridgeHandlers((controller as { input?: unknown }).input);\n bridgeHandlers((controller as { events?: unknown }).events);\n const trace = await scopedContainer.instance(TraceProvider);\n const traceRecorder = await scopedContainer.instance(TraceRecorder);\n if (options.initialize && typeof (controller as { initialize?: () => void }).initialize === 'function') {\n await Promise.resolve(\n (controller as { initialize: () => void | Promise<void> }).initialize(),\n );\n }\n\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.created',\n payload: {\n controllerClass: options.controllerClass.name,\n },\n });\n\n return {\n controller,\n bridge: harnessBridge,\n events,\n router,\n container: scopedContainer,\n backendContainer: options.container,\n navigation,\n traceRecorder,\n destroy: async () => {\n trace.info({\n source: 'wood.testing.controller-harness',\n type: 'harness.destroyed',\n });\n await disposeControllerLifecycle(controller);\n events.clear();\n clearClientContainerForTests();\n clearClientAppForTests();\n clearClientNavigationForTests();\n clearClientWindowControlsForTests();\n clearClientLoggersForTests();\n\n if (previousInternalBridge === undefined) {\n delete globalObj[WOOD_INTERNAL_BRIDGE_KEY];\n } else {\n globalObj[WOOD_INTERNAL_BRIDGE_KEY] = previousInternalBridge;\n }\n\n if (previousWoodBridge === undefined) {\n delete globalObj.wood;\n } else {\n globalObj.wood = previousWoodBridge;\n }\n },\n };\n}\n"],"mappings":"AAAA,SAAS,oBAAoB;AAC7B,SAAS,QAAQ,wBAAwB;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,YAAY,qBAAqB;AAC1C,SAAS,eAAe,kCAAkC;AAC1D,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAEhC,SAAS,kCAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,0BAAmD;AAC5D,SAAS,wBAAwB;AAgDjC,SAAS,eAAe,OAA0C;AAChE,MAAI,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,GAAG,GAAG;AAC/C,WAAO,gBAAgB,KAAK;AAAA,EAC9B;AAEA,SAAO,gBAAgB,aAAa,OAAO,OAAO,CAAC;AACrD;AAEA,SAAS,gBAAgB,QAAsD;AAC7E,QAAM,SAAS,oBAAI,IAA0B;AAE7C,aAAW,SAAS,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,GAAG;AAC7D,eAAW,CAAC,SAAS,SAAS,KAAK,eAAe,KAAK,GAAG;AACxD,aAAO,IAAI,SAAS,SAAS;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,YAAuC;AAChE,SAAO,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe;AAAA,IACzD,SAAS,UAAU;AAAA,IACnB,YAAY,UAAU;AAAA,IACtB,QAAQ,UAAU;AAAA,IAClB,MAAM,UAAU,QAAQ,MAAM,GAAG;AAAA,IACjC,UAAU,kBAAkB,SAAS;AAAA,EACvC,EAAE;AACJ;AAEA,SAAS,uBAAuB,WAAsB;AACpD,MAAI,OAAO,UAAU,qBAAqB,YAAY;AACpD;AAAA,EACF;AAEA,QAAM,qBAAqB,IAAI,2BAA2B;AAC1D,qBAAmB,cAAc,MAAM;AACvC,qBAAmB,iBAAiB,UAAU;AAC9C,qBAAmB,iBAAiB,IAAI;AAMxC,QAAM,YAAY,EAAE,QAAQ,OAAO,UAAU;AAC7C,YAAU,iBAAiB,YAAY,MAAM,cAAc,GAAG,SAAS;AACvE,YAAU,iBAAiB,KAAK,MAAM,OAAO,GAAG,SAAS;AACzD,YAAU,iBAAiB,KAAK,MAAM,OAAO,GAAG,SAAS;AACzD,YAAU,iBAAiB,gBAAgB,MAAM,kBAAkB,GAAG,SAAS;AAC/E,YAAU,iBAAiB,4BAA4B,MAAM,oBAAoB,SAAS;AAC5F;AAEA,MAAM,eAAe;AAAA,EACnB,MAAM,KAAK,WAAmB,UAAmD;AAAA,EAAC;AAAA,EAClF,MAAM,MAAM,WAAmC;AAAA,EAAC;AAAA,EAChD,MAAM,MAAM,WAAkC;AAAA,EAAC;AAAA,EAC/C,MAAM,SAAS,WAAmC;AAAA,EAAC;AAAA,EACnD,MAAM,SAAS,WAAmC;AAAA,EAAC;AACrD;AAEA,eAAsB,wBACpB,SACqC;AACrC,QAAM,QAAQ,qBAAqB,QAAQ,SAAS;AAEpD,MAAI,OAAO,QAAQ,UAAU,aAAa,YAAY;AACpD,UAAM,yBAAyB,MAAM,QAAQ,UAAU,SAAS,0BAA0B;AAC1F,2BAAuB,cAAc,MAAM;AAC3C,2BAAuB,iBAAiB,MAAM;AAAA,EAChD;AAEA,QAAM,aAAa,gBAAgB,QAAQ,UAAU;AACrD,QAAM,EAAE,OAAO,IAAI,iBAAiB,IAAI;AAAA,IACtC,WAAW,QAAQ;AAAA,IACnB,gBAAgB,QAAQ;AAAA,IACxB,eAAe,QAAQ;AAAA,EACzB,CAAC;AACD,SAAO,eAAe,UAAU;AAChC,QAAM,QAAQ,kBAAkB,MAAM;AAEtC,QAAM,EAAE,QAAQ,OAAO,IAAI,mBAAmB,MAAM;AACpD,QAAM,gBAAgB,OAAO,OAAO,QAAQ;AAAA,IAC1C,eAAe,kBAAkB,UAAU;AAAA,EAC7C,CAAC;AAED,QAAM,YAAY;AAClB,QAAM,yBAAyB,UAAU,wBAAwB;AACjE,QAAM,qBAAqB,UAAU;AACrC,YAAU,wBAAwB,IAAI;AACtC,YAAU,OAAO;AAEjB,+BAA6B;AAC7B,yBAAuB;AACvB,gCAA8B;AAC9B,oCAAkC;AAClC,6BAA2B;AAQ3B,QAAM,kBAAkB,QAAQ,oBAAoB,WAChD,QAAQ,UAAU,OAAO,IACxB,eAAe;AACpB,yBAAuB,eAAe;AACtC,MAAI,OAAO,gBAAgB,kBAAkB,YAAY;AACvD,oBAAgB,cAAc,QAAQ,eAAe;AAAA,EACvD;AACA,QAAM,aAAa,cAAc;AACjC,QAAM,QAAQ,gBAAgB;AAAA,IAC5B,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,kBAAkB,QAAQ;AAAA,EAC5B,CAAC;AAED,QAAM,aAAa,MAAM,gBAAgB,SAAS,QAAQ,eAAe;AAOzE,QAAM,iBAAiB,CAAC,YAA2B;AACjD,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU;AAC7C,UAAM,SAAS;AACf,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,YAAM,UAAU,OAAO,GAAG;AAC1B,UAAI,OAAO,YAAY,WAAY;AACnC,aAAO,GAAG,IAAI,IAAI,SAChB,iBAAiB,QAAQ,MAAM,SAC1B,QAAyC,GAAG,IAAI,IACjD,iBAAiB;AAAA,QAAI;AAAA,QAAwB,MAC1C,QAAyC,GAAG,IAAI;AAAA,MACnD;AAAA,IACR;AAAA,EACF;AACA,iBAAgB,WAAmC,KAAK;AACxD,iBAAgB,WAAoC,MAAM;AAC1D,QAAM,QAAQ,MAAM,gBAAgB,SAAS,aAAa;AAC1D,QAAM,gBAAgB,MAAM,gBAAgB,SAAS,aAAa;AAClE,MAAI,QAAQ,cAAc,OAAQ,WAA2C,eAAe,YAAY;AACtG,UAAM,QAAQ;AAAA,MACX,WAA0D,WAAW;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,KAAK;AAAA,IACT,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,MACP,iBAAiB,QAAQ,gBAAgB;AAAA,IAC3C;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,kBAAkB,QAAQ;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,SAAS,YAAY;AACnB,YAAM,KAAK;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,YAAM,2BAA2B,UAAU;AAC3C,aAAO,MAAM;AACb,mCAA6B;AAC7B,6BAAuB;AACvB,oCAA8B;AAC9B,wCAAkC;AAClC,iCAA2B;AAE3B,UAAI,2BAA2B,QAAW;AACxC,eAAO,UAAU,wBAAwB;AAAA,MAC3C,OAAO;AACL,kBAAU,wBAAwB,IAAI;AAAA,MACxC;AAEA,UAAI,uBAAuB,QAAW;AACpC,eAAO,UAAU;AAAA,MACnB,OAAO;AACL,kBAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var geometry_exports = {};
|
|
20
|
+
__export(geometry_exports, {
|
|
21
|
+
axisEdges: () => axisEdges,
|
|
22
|
+
axisGap: () => axisGap,
|
|
23
|
+
evenSpacing: () => evenSpacing,
|
|
24
|
+
gaps: () => gaps,
|
|
25
|
+
rectsOverlap: () => rectsOverlap,
|
|
26
|
+
sharedEdges: () => sharedEdges,
|
|
27
|
+
snapshotRectsOverlap: () => snapshotRectsOverlap
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(geometry_exports);
|
|
30
|
+
function axisEdges(rect, axis) {
|
|
31
|
+
return axis === "horizontal" ? { leading: rect.left, trailing: rect.right } : { leading: rect.top, trailing: rect.bottom };
|
|
32
|
+
}
|
|
33
|
+
function axisGap(first, second, axis) {
|
|
34
|
+
const a = axisEdges(first, axis);
|
|
35
|
+
const b = axisEdges(second, axis);
|
|
36
|
+
const [earlier, later] = a.leading <= b.leading ? [a, b] : [b, a];
|
|
37
|
+
return later.leading - earlier.trailing;
|
|
38
|
+
}
|
|
39
|
+
function rectsOverlap(a, b) {
|
|
40
|
+
return Boolean(
|
|
41
|
+
a && b && a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function snapshotRectsOverlap(a, b) {
|
|
45
|
+
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
46
|
+
}
|
|
47
|
+
function sharedEdges(rects, edge, options = {}) {
|
|
48
|
+
const tolerance = options.tolerance ?? 1;
|
|
49
|
+
if (rects.length < 2) {
|
|
50
|
+
return {
|
|
51
|
+
pass: false,
|
|
52
|
+
message: "sharedEdges requires at least two rects.",
|
|
53
|
+
details: { count: rects.length, edge, tolerance }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const expected = rects[0][edge];
|
|
57
|
+
const deltas = rects.slice(1).map((rect) => Math.abs(rect[edge] - expected));
|
|
58
|
+
const maximumDelta = Math.max(...deltas);
|
|
59
|
+
const pass = maximumDelta <= tolerance;
|
|
60
|
+
return {
|
|
61
|
+
pass,
|
|
62
|
+
message: pass ? `All rects share the ${edge} edge within ${tolerance}px.` : `Rects do not share the ${edge} edge within ${tolerance}px (maximum delta ${maximumDelta.toFixed(2)}px).`,
|
|
63
|
+
details: { edge, tolerance, expected, deltas, maximumDelta }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function gaps(rects, axis) {
|
|
67
|
+
const sorted = [...rects].sort(
|
|
68
|
+
(a, b) => axisEdges(a, axis).leading - axisEdges(b, axis).leading
|
|
69
|
+
);
|
|
70
|
+
return sorted.slice(1).map((rect, index) => axisGap(sorted[index], rect, axis));
|
|
71
|
+
}
|
|
72
|
+
function evenSpacing(rects, axis, options = {}) {
|
|
73
|
+
const tolerance = options.tolerance ?? 1;
|
|
74
|
+
if (rects.length < 3) {
|
|
75
|
+
return {
|
|
76
|
+
pass: false,
|
|
77
|
+
message: `evenSpacing requires at least three rects, got ${rects.length}.`,
|
|
78
|
+
details: { count: rects.length, axis, tolerance }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const measuredGaps = gaps(rects, axis);
|
|
82
|
+
const widest = Math.max(...measuredGaps);
|
|
83
|
+
const narrowest = Math.min(...measuredGaps);
|
|
84
|
+
const spread = widest - narrowest;
|
|
85
|
+
const pass = spread <= tolerance;
|
|
86
|
+
return {
|
|
87
|
+
pass,
|
|
88
|
+
message: pass ? `Rects are evenly spaced along ${axis} within ${tolerance}px.` : `Rects are not evenly spaced along ${axis}: gap spread ${spread.toFixed(2)}px exceeds ${tolerance}px.`,
|
|
89
|
+
details: {
|
|
90
|
+
axis,
|
|
91
|
+
tolerance,
|
|
92
|
+
gaps: measuredGaps,
|
|
93
|
+
widest,
|
|
94
|
+
narrowest,
|
|
95
|
+
spread
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
axisEdges,
|
|
102
|
+
axisGap,
|
|
103
|
+
evenSpacing,
|
|
104
|
+
gaps,
|
|
105
|
+
rectsOverlap,
|
|
106
|
+
sharedEdges,
|
|
107
|
+
snapshotRectsOverlap
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=geometry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/testing/geometry.ts"],"sourcesContent":["export type RectAxis = 'horizontal' | 'vertical';\n\nexport interface RectBox {\n top: number;\n left: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n}\n\nexport interface SnapshotRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface GeometryVerdict {\n pass: boolean;\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport type RectEdge = 'left' | 'right' | 'top' | 'bottom';\n\nexport function axisEdges(\n rect: RectBox,\n axis: RectAxis,\n): { leading: number; trailing: number } {\n return axis === 'horizontal'\n ? { leading: rect.left, trailing: rect.right }\n : { leading: rect.top, trailing: rect.bottom };\n}\n\n/**\n * Edge-to-edge gap between two border boxes. The box with the smaller leading\n * edge is treated as the earlier box, so negative values mean overlap.\n */\nexport function axisGap(first: RectBox, second: RectBox, axis: RectAxis): number {\n const a = axisEdges(first, axis);\n const b = axisEdges(second, axis);\n const [earlier, later] = a.leading <= b.leading ? [a, b] : [b, a];\n return later.leading - earlier.trailing;\n}\n\nexport function rectsOverlap(\n a: RectBox | null | undefined,\n b: RectBox | null | undefined,\n): boolean {\n return Boolean(\n a && b\n && a.left < b.right && a.right > b.left\n && a.top < b.bottom && a.bottom > b.top,\n );\n}\n\n/** Two CDP snapshot `[x,y,w,h]` rects overlap on both axes. */\nexport function snapshotRectsOverlap(a: SnapshotRect, b: SnapshotRect): boolean {\n return a.x < b.x + b.w\n && a.x + a.w > b.x\n && a.y < b.y + b.h\n && a.y + a.h > b.y;\n}\n\n/** Assert that every rect shares one named edge with the first rect. */\nexport function sharedEdges(\n rects: readonly RectBox[],\n edge: RectEdge,\n options: { tolerance?: number } = {},\n): GeometryVerdict {\n const tolerance = options.tolerance ?? 1;\n if (rects.length < 2) {\n return {\n pass: false,\n message: 'sharedEdges requires at least two rects.',\n details: { count: rects.length, edge, tolerance },\n };\n }\n\n const expected = rects[0][edge];\n const deltas = rects.slice(1).map((rect) => Math.abs(rect[edge] - expected));\n const maximumDelta = Math.max(...deltas);\n const pass = maximumDelta <= tolerance;\n return {\n pass,\n message: pass\n ? `All rects share the ${edge} edge within ${tolerance}px.`\n : `Rects do not share the ${edge} edge within ${tolerance}px (maximum delta ${maximumDelta.toFixed(2)}px).`,\n details: { edge, tolerance, expected, deltas, maximumDelta },\n };\n}\n\n/** Consecutive edge-to-edge gaps, after sorting rects along the requested axis. */\nexport function gaps(rects: readonly RectBox[], axis: RectAxis): number[] {\n const sorted = [...rects].sort(\n (a, b) => axisEdges(a, axis).leading - axisEdges(b, axis).leading,\n );\n return sorted.slice(1).map((rect, index) => axisGap(sorted[index], rect, axis));\n}\n\n/** Assert that consecutive rect gaps have no more than the requested spread. */\nexport function evenSpacing(\n rects: readonly RectBox[],\n axis: RectAxis,\n options: { tolerance?: number } = {},\n): GeometryVerdict {\n const tolerance = options.tolerance ?? 1;\n if (rects.length < 3) {\n return {\n pass: false,\n message: `evenSpacing requires at least three rects, got ${rects.length}.`,\n details: { count: rects.length, axis, tolerance },\n };\n }\n\n const measuredGaps = gaps(rects, axis);\n const widest = Math.max(...measuredGaps);\n const narrowest = Math.min(...measuredGaps);\n const spread = widest - narrowest;\n const pass = spread <= tolerance;\n return {\n pass,\n message: pass\n ? `Rects are evenly spaced along ${axis} within ${tolerance}px.`\n : `Rects are not evenly spaced along ${axis}: gap spread ${spread.toFixed(2)}px exceeds ${tolerance}px.`,\n details: {\n axis,\n tolerance,\n gaps: measuredGaps,\n widest,\n narrowest,\n spread,\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type RectAxis = 'horizontal' | 'vertical';
|
|
2
|
+
interface RectBox {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
right: number;
|
|
6
|
+
bottom: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
interface SnapshotRect {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
w: number;
|
|
14
|
+
h: number;
|
|
15
|
+
}
|
|
16
|
+
interface GeometryVerdict {
|
|
17
|
+
pass: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
details?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
type RectEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
22
|
+
declare function axisEdges(rect: RectBox, axis: RectAxis): {
|
|
23
|
+
leading: number;
|
|
24
|
+
trailing: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Edge-to-edge gap between two border boxes. The box with the smaller leading
|
|
28
|
+
* edge is treated as the earlier box, so negative values mean overlap.
|
|
29
|
+
*/
|
|
30
|
+
declare function axisGap(first: RectBox, second: RectBox, axis: RectAxis): number;
|
|
31
|
+
declare function rectsOverlap(a: RectBox | null | undefined, b: RectBox | null | undefined): boolean;
|
|
32
|
+
/** Two CDP snapshot `[x,y,w,h]` rects overlap on both axes. */
|
|
33
|
+
declare function snapshotRectsOverlap(a: SnapshotRect, b: SnapshotRect): boolean;
|
|
34
|
+
/** Assert that every rect shares one named edge with the first rect. */
|
|
35
|
+
declare function sharedEdges(rects: readonly RectBox[], edge: RectEdge, options?: {
|
|
36
|
+
tolerance?: number;
|
|
37
|
+
}): GeometryVerdict;
|
|
38
|
+
/** Consecutive edge-to-edge gaps, after sorting rects along the requested axis. */
|
|
39
|
+
declare function gaps(rects: readonly RectBox[], axis: RectAxis): number[];
|
|
40
|
+
/** Assert that consecutive rect gaps have no more than the requested spread. */
|
|
41
|
+
declare function evenSpacing(rects: readonly RectBox[], axis: RectAxis, options?: {
|
|
42
|
+
tolerance?: number;
|
|
43
|
+
}): GeometryVerdict;
|
|
44
|
+
|
|
45
|
+
export { type GeometryVerdict, type RectAxis, type RectBox, type RectEdge, type SnapshotRect, axisEdges, axisGap, evenSpacing, gaps, rectsOverlap, sharedEdges, snapshotRectsOverlap };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type RectAxis = 'horizontal' | 'vertical';
|
|
2
|
+
interface RectBox {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
right: number;
|
|
6
|
+
bottom: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
interface SnapshotRect {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
w: number;
|
|
14
|
+
h: number;
|
|
15
|
+
}
|
|
16
|
+
interface GeometryVerdict {
|
|
17
|
+
pass: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
details?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
type RectEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
22
|
+
declare function axisEdges(rect: RectBox, axis: RectAxis): {
|
|
23
|
+
leading: number;
|
|
24
|
+
trailing: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Edge-to-edge gap between two border boxes. The box with the smaller leading
|
|
28
|
+
* edge is treated as the earlier box, so negative values mean overlap.
|
|
29
|
+
*/
|
|
30
|
+
declare function axisGap(first: RectBox, second: RectBox, axis: RectAxis): number;
|
|
31
|
+
declare function rectsOverlap(a: RectBox | null | undefined, b: RectBox | null | undefined): boolean;
|
|
32
|
+
/** Two CDP snapshot `[x,y,w,h]` rects overlap on both axes. */
|
|
33
|
+
declare function snapshotRectsOverlap(a: SnapshotRect, b: SnapshotRect): boolean;
|
|
34
|
+
/** Assert that every rect shares one named edge with the first rect. */
|
|
35
|
+
declare function sharedEdges(rects: readonly RectBox[], edge: RectEdge, options?: {
|
|
36
|
+
tolerance?: number;
|
|
37
|
+
}): GeometryVerdict;
|
|
38
|
+
/** Consecutive edge-to-edge gaps, after sorting rects along the requested axis. */
|
|
39
|
+
declare function gaps(rects: readonly RectBox[], axis: RectAxis): number[];
|
|
40
|
+
/** Assert that consecutive rect gaps have no more than the requested spread. */
|
|
41
|
+
declare function evenSpacing(rects: readonly RectBox[], axis: RectAxis, options?: {
|
|
42
|
+
tolerance?: number;
|
|
43
|
+
}): GeometryVerdict;
|
|
44
|
+
|
|
45
|
+
export { type GeometryVerdict, type RectAxis, type RectBox, type RectEdge, type SnapshotRect, axisEdges, axisGap, evenSpacing, gaps, rectsOverlap, sharedEdges, snapshotRectsOverlap };
|