@pixel-point/toolcraft 0.0.16 → 0.0.18
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/package.json +1 -1
- package/src/generate-performance-iteration-execution.test.mjs +2 -2
- package/src/generate-test-demand-only-lifecycle.mjs +14 -3
- package/src/generate-test-dependency-sandbox.mjs +42 -5
- package/src/generate-test-doc-assertions.mjs +109 -69
- package/src/generate-test-doc-assertions.test.mjs +65 -0
- package/src/generate-test-doc-policy-assertions.mjs +92 -0
- package/src/generate-test-file-assertions.mjs +36 -118
- package/src/generate-test-output-assertions.mjs +95 -0
- package/src/generate-test-package-assertions.mjs +6 -0
- package/src/generate-test-performance-boundary-assertions.mjs +45 -0
- package/src/generate-test-product-fixture.mjs +1 -0
- package/src/generate-test-product-fixture.test.mjs +18 -0
- package/src/generate-test-product-iteration.mjs +1 -0
- package/src/generate-test-product-iteration.test.mjs +18 -0
- package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
- package/src/generate-test-standalone-assertions.mjs +10 -0
- package/src/generate-test-standalone-delivery-stages.mjs +14 -6
- package/src/generate.test.mjs +14 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +59 -12
- package/templates/runtime/contracts/component-contracts.choices.ts +58 -12
- package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
- package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
- package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
- package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +21 -6
- package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +4 -1
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
- package/templates/runtime/contracts/component-contracts.runtime.ts +15 -10
- package/templates/runtime/contracts/component-contracts.test.ts +2 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +43 -4
- package/templates/runtime/contracts/decision-contracts.ts +10 -10
- package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
- package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
- package/templates/runtime/export/artifact-download.test.ts +71 -0
- package/templates/runtime/export/artifact-download.ts +80 -0
- package/templates/runtime/export/artifact-export-request.ts +23 -0
- package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
- package/templates/runtime/export/artifact-export-settings.ts +96 -0
- package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
- package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
- package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
- package/templates/runtime/export/artifact-frame-state.ts +44 -0
- package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
- package/templates/runtime/export/artifact-scene-frame.ts +118 -0
- package/templates/runtime/export/export-background.ts +46 -0
- package/templates/runtime/export/export-error.ts +43 -0
- package/templates/runtime/export/export.test.ts +49 -226
- package/templates/runtime/export/image-artifact-export.test.ts +111 -0
- package/templates/runtime/export/image-artifact-export.ts +145 -0
- package/templates/runtime/export/index.ts +44 -3
- package/templates/runtime/export/product-export-renderer.test.ts +77 -0
- package/templates/runtime/export/product-export-renderer.ts +63 -0
- package/templates/runtime/export/video-artifact-export.test.ts +166 -0
- package/templates/runtime/export/video-artifact-export.ts +164 -0
- package/templates/runtime/export/video-encoding-backend.ts +124 -0
- package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
- package/templates/runtime/export/video-encoding-policy.ts +86 -0
- package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
- package/templates/runtime/export/video-frame-schedule.ts +42 -0
- package/templates/runtime/index.ts +3 -0
- package/templates/runtime/model-import/model-import-limits.ts +15 -4
- package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
- package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +15 -304
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +41 -27
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +39 -11
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
- package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
- package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
- package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
- package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
- package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
- package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
- package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +18 -84
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +43 -87
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +68 -0
- package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
- package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
- package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
- package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
- package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
- package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
- package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
- package/templates/runtime/react/index.ts +4 -0
- package/templates/runtime/react/model-rendering/model-export.ts +1 -1
- package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
- package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
- package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
- package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
- package/templates/runtime/scene/scene-bounds.test.ts +23 -0
- package/templates/runtime/scene/scene-bounds.ts +32 -19
- package/templates/runtime/schema/app-capabilities.test.ts +4 -0
- package/templates/runtime/schema/canvas-render-scale.ts +62 -0
- package/templates/runtime/schema/collection-item-controls.ts +29 -0
- package/templates/runtime/schema/control-applicability.test.ts +127 -0
- package/templates/runtime/schema/control-applicability.ts +433 -0
- package/templates/runtime/schema/control-schema-normalization.ts +177 -11
- package/templates/runtime/schema/controls-panel-actions.ts +2 -2
- package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
- package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +62 -3
- package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
- package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
- package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
- package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
- package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
- package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
- package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
- package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
- package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
- package/templates/runtime/schema/define-toolcraft.ts +1 -1
- package/templates/runtime/schema/runtime-setup-section.ts +24 -5
- package/templates/runtime/schema/schema-resolvers.ts +0 -56
- package/templates/runtime/schema/types.ts +72 -17
- package/templates/runtime/state/canvas-background-state.ts +6 -0
- package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
- package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
- package/templates/starter/AGENTS.md +18 -15
- package/templates/starter/NOTICE.md +6 -0
- package/templates/starter/docs/toolcraft/README.md +2 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +23 -35
- package/templates/starter/docs/toolcraft/agent-worklog.md +18 -3
- package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -5
- package/templates/starter/docs/toolcraft/component-rules.md +8 -6
- package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
- package/templates/starter/docs/toolcraft/core/layout.md +7 -7
- package/templates/starter/docs/toolcraft/core/media-upload.md +4 -2
- package/templates/starter/docs/toolcraft/core/performance.md +14 -3
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +16 -3
- package/templates/starter/docs/toolcraft/core/setup-export.md +49 -19
- package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
- package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
- package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
- package/templates/starter/docs/toolcraft/performance.md +20 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +5 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +23 -25
- package/templates/starter/docs/toolcraft/workflow.md +7 -2
- package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
- package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +50 -3
- package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
- package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
- package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
- package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
- package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +5 -34
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -16
- package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
- package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
- package/templates/starter/e2e/app-performance.spec.ts +16 -9
- package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
- package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +29 -341
- package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
- package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
- package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
- package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
- package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
- package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
- package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +13 -3
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +72 -35
- package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
- package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
- package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
- package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
- package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
- package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
- package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
- package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
- package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
- package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
- package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
- package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
- package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
- package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
- package/templates/starter/e2e/performance-path-helpers.ts +210 -91
- package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
- package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
- package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
- package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
- package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
- package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
- package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
- package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +95 -103
- package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
- package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -26
- package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
- package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
- package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
- package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +157 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
- package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
- package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +157 -206
- package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
- package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
- package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
- package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
- package/templates/starter/src/app/acceptance/conditions.ts +1 -1
- package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
- package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
- package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
- package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
- package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
- package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
- package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
- package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
- package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
- package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
- package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
- package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
- package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
- package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
- package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
- package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
- package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
- package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
- package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
- package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
- package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
- package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
- package/templates/starter/src/app/acceptance/output-export.ts +35 -12
- package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
- package/templates/starter/src/app/acceptance/types.ts +30 -8
- package/templates/starter/src/app/acceptance/validate-coverage.ts +20 -2
- package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
- package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
- package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
- package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
- package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
- package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
- package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
- package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
- package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
- package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
- package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
- package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
- package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
- package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
- package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
- package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +46 -1
- package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
- package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
- package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
- package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
- package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
- package/templates/starter/src/app/starter-acceptance.ts +13 -2
- package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
- package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
- package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
- package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
- package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +4 -2
- package/templates/starter/src/routes/index.tsx +2 -0
- package/templates/ui/components/composites/tabs.tsx +2 -2
- package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
- package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
- package/templates/ui/components/controls/collection-actions/index.ts +2 -0
- package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
- package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
- package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
- package/templates/ui/components/controls/file-drop/index.ts +1 -0
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
- package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
- package/templates/runtime/export/export.ts +0 -166
- package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
- package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { acquireToolcraftSourceAssetOwner } from "./toolcraft-source-asset-owner";
|
|
4
|
+
import {
|
|
5
|
+
createTestSourceAssetCoordinator,
|
|
6
|
+
createTestSourceAssetStore,
|
|
7
|
+
} from "./toolcraft-source-asset-test-support";
|
|
8
|
+
|
|
9
|
+
function createDeferred<T>() {
|
|
10
|
+
let resolve!: (value: T) => void;
|
|
11
|
+
const promise = new Promise<T>((resolvePromise) => {
|
|
12
|
+
resolve = resolvePromise;
|
|
13
|
+
});
|
|
14
|
+
return { promise, resolve };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
18
|
+
|
|
19
|
+
describe("Toolcraft source asset owner", () => {
|
|
20
|
+
it("binds the resolver without relying on the proof decorator", async () => {
|
|
21
|
+
type ReceiverAwareCoordinator = ReturnType<
|
|
22
|
+
typeof createTestSourceAssetCoordinator
|
|
23
|
+
> & Readonly<{ resolverBytes: Uint8Array }>;
|
|
24
|
+
|
|
25
|
+
const resolverBytes = new Uint8Array([7, 8, 9]);
|
|
26
|
+
const coordinator: ReceiverAwareCoordinator = {
|
|
27
|
+
...createTestSourceAssetCoordinator(),
|
|
28
|
+
resolverBytes,
|
|
29
|
+
resolveResource: vi.fn(async function (
|
|
30
|
+
this: ReceiverAwareCoordinator,
|
|
31
|
+
) {
|
|
32
|
+
expect(this).toBe(coordinator);
|
|
33
|
+
return this.resolverBytes;
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
const lease = acquireToolcraftSourceAssetOwner({
|
|
37
|
+
createCoordinator: () => coordinator,
|
|
38
|
+
store: createTestSourceAssetStore(),
|
|
39
|
+
});
|
|
40
|
+
expect(lease.coordinator).toBe(coordinator);
|
|
41
|
+
const resolveResource = lease.coordinator.resolveResource;
|
|
42
|
+
if (!resolveResource) throw new Error("Expected a resource resolver.");
|
|
43
|
+
|
|
44
|
+
await expect(resolveResource(
|
|
45
|
+
"media:image:production-receiver",
|
|
46
|
+
{ signal: new AbortController().signal },
|
|
47
|
+
)).resolves.toBe(resolverBytes);
|
|
48
|
+
lease.release();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("attempts every cleanup and reports their failures once", async () => {
|
|
52
|
+
const presentationFailure = new Error("presentation cleanup failed");
|
|
53
|
+
const coordinatorFailure = new Error("coordinator cleanup failed");
|
|
54
|
+
const releaseResourceRef = vi.fn(() => {
|
|
55
|
+
throw presentationFailure;
|
|
56
|
+
});
|
|
57
|
+
const disposeCoordinator = vi.fn(async () => {
|
|
58
|
+
throw coordinatorFailure;
|
|
59
|
+
});
|
|
60
|
+
const onError = vi.fn();
|
|
61
|
+
const store = createTestSourceAssetStore();
|
|
62
|
+
const lease = acquireToolcraftSourceAssetOwner({
|
|
63
|
+
createCoordinator: () => createTestSourceAssetCoordinator({
|
|
64
|
+
dispose: disposeCoordinator,
|
|
65
|
+
resolveResource: vi.fn(async () => new Uint8Array([1, 2, 3])),
|
|
66
|
+
retainResourceRef: () => releaseResourceRef,
|
|
67
|
+
}),
|
|
68
|
+
onError,
|
|
69
|
+
store,
|
|
70
|
+
});
|
|
71
|
+
const resources = lease.presentationResources;
|
|
72
|
+
if (!resources) throw new Error("Expected presentation resources.");
|
|
73
|
+
await resources.retain("media:image:cleanup", "image/png");
|
|
74
|
+
|
|
75
|
+
lease.release();
|
|
76
|
+
|
|
77
|
+
await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1));
|
|
78
|
+
expect(releaseResourceRef).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(disposeCoordinator).toHaveBeenCalledTimes(1);
|
|
80
|
+
const reported = onError.mock.calls[0]?.[0];
|
|
81
|
+
expect(reported).toBeInstanceOf(AggregateError);
|
|
82
|
+
expect((reported as AggregateError).errors).toEqual([
|
|
83
|
+
presentationFailure,
|
|
84
|
+
coordinatorFailure,
|
|
85
|
+
]);
|
|
86
|
+
expect(() => acquireToolcraftSourceAssetOwner({ store })).toThrow(
|
|
87
|
+
"Toolcraft source asset owner failed to dispose.",
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("keeps a disposing owner registered until cleanup settles", async () => {
|
|
92
|
+
const disposal = createDeferred<void>();
|
|
93
|
+
const store = createTestSourceAssetStore();
|
|
94
|
+
const firstCoordinator = createTestSourceAssetCoordinator({
|
|
95
|
+
dispose: vi.fn(() => disposal.promise),
|
|
96
|
+
});
|
|
97
|
+
const first = acquireToolcraftSourceAssetOwner({
|
|
98
|
+
createCoordinator: () => firstCoordinator,
|
|
99
|
+
store,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
first.release();
|
|
103
|
+
first.release();
|
|
104
|
+
await Promise.resolve();
|
|
105
|
+
|
|
106
|
+
const createReplacement = vi.fn(() => createTestSourceAssetCoordinator());
|
|
107
|
+
expect(() => acquireToolcraftSourceAssetOwner({
|
|
108
|
+
createCoordinator: createReplacement,
|
|
109
|
+
store,
|
|
110
|
+
})).toThrow("Toolcraft source asset owner is disposing.");
|
|
111
|
+
expect(createReplacement).not.toHaveBeenCalled();
|
|
112
|
+
|
|
113
|
+
disposal.resolve(undefined);
|
|
114
|
+
await vi.waitFor(() =>
|
|
115
|
+
expect(firstCoordinator.dispose).toHaveBeenCalledTimes(1)
|
|
116
|
+
);
|
|
117
|
+
await Promise.resolve();
|
|
118
|
+
const replacement = acquireToolcraftSourceAssetOwner({
|
|
119
|
+
createCoordinator: createReplacement,
|
|
120
|
+
store,
|
|
121
|
+
});
|
|
122
|
+
expect(createReplacement).toHaveBeenCalledTimes(1);
|
|
123
|
+
replacement.release();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("reuses the active store owner regardless of later construction inputs", () => {
|
|
127
|
+
const store = createTestSourceAssetStore();
|
|
128
|
+
const firstFactory = vi.fn(() => createTestSourceAssetCoordinator());
|
|
129
|
+
const replacementFactory = vi.fn(() => createTestSourceAssetCoordinator());
|
|
130
|
+
const firstReporter = vi.fn();
|
|
131
|
+
const replacementReporter = vi.fn();
|
|
132
|
+
const first = acquireToolcraftSourceAssetOwner({
|
|
133
|
+
createCoordinator: firstFactory,
|
|
134
|
+
onError: firstReporter,
|
|
135
|
+
store,
|
|
136
|
+
});
|
|
137
|
+
const second = acquireToolcraftSourceAssetOwner({
|
|
138
|
+
createCoordinator: replacementFactory,
|
|
139
|
+
onError: replacementReporter,
|
|
140
|
+
store,
|
|
141
|
+
});
|
|
142
|
+
const failure = new Error("existing owner operation failed");
|
|
143
|
+
|
|
144
|
+
expect(second.coordinator).toBe(first.coordinator);
|
|
145
|
+
expect(replacementFactory).not.toHaveBeenCalled();
|
|
146
|
+
second.reportError(failure);
|
|
147
|
+
expect(firstReporter).toHaveBeenCalledWith(failure);
|
|
148
|
+
expect(replacementReporter).not.toHaveBeenCalled();
|
|
149
|
+
|
|
150
|
+
second.release();
|
|
151
|
+
first.release();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("reports operational failures through the contextual error channel", () => {
|
|
155
|
+
const failure = new Error("model hydration failed");
|
|
156
|
+
const onError = vi.fn();
|
|
157
|
+
const lease = acquireToolcraftSourceAssetOwner({
|
|
158
|
+
createCoordinator: () => createTestSourceAssetCoordinator(),
|
|
159
|
+
onError,
|
|
160
|
+
store: createTestSourceAssetStore(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
lease.reportError(failure);
|
|
164
|
+
|
|
165
|
+
expect(onError).toHaveBeenCalledWith(failure);
|
|
166
|
+
lease.release();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("preserves cleanup and contextual reporter failures", async () => {
|
|
170
|
+
const cleanupFailure = new Error("cleanup failed");
|
|
171
|
+
const reporterFailure = new Error("reporter failed");
|
|
172
|
+
const reportError = vi.fn();
|
|
173
|
+
vi.stubGlobal("reportError", reportError);
|
|
174
|
+
const lease = acquireToolcraftSourceAssetOwner({
|
|
175
|
+
createCoordinator: () => createTestSourceAssetCoordinator({
|
|
176
|
+
dispose: vi.fn(async () => Promise.reject(cleanupFailure)),
|
|
177
|
+
}),
|
|
178
|
+
onError: () => {
|
|
179
|
+
throw reporterFailure;
|
|
180
|
+
},
|
|
181
|
+
store: createTestSourceAssetStore(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
lease.release();
|
|
185
|
+
|
|
186
|
+
await vi.waitFor(() => expect(reportError).toHaveBeenCalledTimes(1));
|
|
187
|
+
const reported = reportError.mock.calls[0]?.[0];
|
|
188
|
+
expect(reported).toBeInstanceOf(AggregateError);
|
|
189
|
+
expect((reported as AggregateError).errors).toEqual([
|
|
190
|
+
cleanupFailure,
|
|
191
|
+
reporterFailure,
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type { ToolcraftBinaryAssetRepository } from "../../source-assets/repository/binary-asset-repository";
|
|
2
|
+
import { createIndexedDbToolcraftBinaryAssetRepository } from "../../source-assets/repository/indexeddb-binary-asset-repository";
|
|
3
|
+
import { createMemoryToolcraftBinaryAssetRepository } from "../../source-assets/repository/memory-binary-asset-repository";
|
|
4
|
+
import { createUnavailableToolcraftBinaryAssetRepository } from "../../source-assets/repository/unavailable-binary-asset-repository";
|
|
5
|
+
import {
|
|
6
|
+
createToolcraftSourceAssetCoordinator,
|
|
7
|
+
type ToolcraftSourceAssetCoordinator,
|
|
8
|
+
} from "../../source-assets/source-asset-coordinator";
|
|
9
|
+
import {
|
|
10
|
+
createToolcraftSourceAssetPresentationResources,
|
|
11
|
+
type ToolcraftSourceAssetPresentationResources,
|
|
12
|
+
} from "../../source-assets/source-asset-presentation-resources";
|
|
13
|
+
import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store";
|
|
14
|
+
|
|
15
|
+
type ToolcraftSourceAssetCoordinatorFactory = (
|
|
16
|
+
store: ToolcraftExternalStore,
|
|
17
|
+
) => ToolcraftSourceAssetCoordinator;
|
|
18
|
+
|
|
19
|
+
type ToolcraftSourceAssetCoordinatorDecorator = (
|
|
20
|
+
coordinator: ToolcraftSourceAssetCoordinator,
|
|
21
|
+
store: ToolcraftExternalStore,
|
|
22
|
+
) => ToolcraftSourceAssetCoordinator;
|
|
23
|
+
|
|
24
|
+
type ToolcraftSourceAssetOwner = {
|
|
25
|
+
coordinator: ToolcraftSourceAssetCoordinator;
|
|
26
|
+
leases: number;
|
|
27
|
+
lifecycle: "active" | "disposing" | "failed";
|
|
28
|
+
lifecycleGeneration: number;
|
|
29
|
+
onError: (error: unknown) => void;
|
|
30
|
+
presentationResources: ToolcraftSourceAssetPresentationResources | null;
|
|
31
|
+
store: ToolcraftExternalStore;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ToolcraftSourceAssetOwnerLease = Readonly<{
|
|
35
|
+
coordinator: ToolcraftSourceAssetCoordinator;
|
|
36
|
+
presentationResources: ToolcraftSourceAssetPresentationResources | null;
|
|
37
|
+
release: () => void;
|
|
38
|
+
reportError: (error: unknown) => void;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
const sourceAssetOwners = new WeakMap<
|
|
42
|
+
ToolcraftExternalStore,
|
|
43
|
+
ToolcraftSourceAssetOwner
|
|
44
|
+
>();
|
|
45
|
+
|
|
46
|
+
function createRepository(): ToolcraftBinaryAssetRepository {
|
|
47
|
+
if (typeof globalThis.indexedDB === "undefined") {
|
|
48
|
+
return typeof navigator !== "undefined" &&
|
|
49
|
+
navigator.userAgent.toLowerCase().includes("jsdom")
|
|
50
|
+
? createMemoryToolcraftBinaryAssetRepository()
|
|
51
|
+
: createUnavailableToolcraftBinaryAssetRepository();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
return createIndexedDbToolcraftBinaryAssetRepository();
|
|
56
|
+
} catch {
|
|
57
|
+
return createUnavailableToolcraftBinaryAssetRepository();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createDefaultCoordinator(
|
|
62
|
+
store: ToolcraftExternalStore,
|
|
63
|
+
): ToolcraftSourceAssetCoordinator {
|
|
64
|
+
return createToolcraftSourceAssetCoordinator({
|
|
65
|
+
dispatch: store.dispatch,
|
|
66
|
+
getState: store.getCommittedState,
|
|
67
|
+
repository: createRepository(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function reportSourceAssetOwnerError(error: unknown): void {
|
|
72
|
+
const globalReportError = globalThis.reportError;
|
|
73
|
+
if (typeof globalReportError === "function") {
|
|
74
|
+
try {
|
|
75
|
+
globalReportError(error);
|
|
76
|
+
return;
|
|
77
|
+
} catch (reportingFailure) {
|
|
78
|
+
console.error(
|
|
79
|
+
"Toolcraft source asset owner error reporting failed.",
|
|
80
|
+
new AggregateError(
|
|
81
|
+
[error, reportingFailure],
|
|
82
|
+
"Toolcraft source asset owner operation and reporting both failed.",
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
console.error("Toolcraft source asset owner operation failed.", error);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function reportOwnerError(
|
|
92
|
+
owner: ToolcraftSourceAssetOwner,
|
|
93
|
+
error: unknown,
|
|
94
|
+
): void {
|
|
95
|
+
try {
|
|
96
|
+
owner.onError(error);
|
|
97
|
+
} catch (reportingFailure) {
|
|
98
|
+
reportSourceAssetOwnerError(
|
|
99
|
+
new AggregateError(
|
|
100
|
+
[error, reportingFailure],
|
|
101
|
+
"Toolcraft source asset owner operation and its injected reporter both failed.",
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createOwner(
|
|
108
|
+
store: ToolcraftExternalStore,
|
|
109
|
+
createCoordinator: ToolcraftSourceAssetCoordinatorFactory,
|
|
110
|
+
decorateCoordinator: ToolcraftSourceAssetCoordinatorDecorator | undefined,
|
|
111
|
+
onError: (error: unknown) => void,
|
|
112
|
+
): ToolcraftSourceAssetOwner {
|
|
113
|
+
const baseCoordinator = createCoordinator(store);
|
|
114
|
+
const resolveResource = baseCoordinator.resolveResource;
|
|
115
|
+
if (resolveResource) {
|
|
116
|
+
baseCoordinator.resolveResource = resolveResource.bind(baseCoordinator);
|
|
117
|
+
}
|
|
118
|
+
const coordinator = decorateCoordinator
|
|
119
|
+
? decorateCoordinator(baseCoordinator, store)
|
|
120
|
+
: baseCoordinator;
|
|
121
|
+
const presentationResources =
|
|
122
|
+
coordinator.resolveResource && coordinator.retainResourceRef
|
|
123
|
+
? createToolcraftSourceAssetPresentationResources({
|
|
124
|
+
resolveResource: coordinator.resolveResource,
|
|
125
|
+
retainResourceRef: coordinator.retainResourceRef,
|
|
126
|
+
})
|
|
127
|
+
: null;
|
|
128
|
+
return {
|
|
129
|
+
coordinator,
|
|
130
|
+
leases: 0,
|
|
131
|
+
lifecycle: "active",
|
|
132
|
+
lifecycleGeneration: 0,
|
|
133
|
+
onError,
|
|
134
|
+
presentationResources,
|
|
135
|
+
store,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function disposeOwner(owner: ToolcraftSourceAssetOwner): Promise<void> {
|
|
140
|
+
const failures: unknown[] = [];
|
|
141
|
+
try {
|
|
142
|
+
owner.presentationResources?.dispose();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
failures.push(error);
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
await owner.coordinator.dispose.call(owner.coordinator);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
failures.push(error);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (failures.length === 1) {
|
|
153
|
+
owner.lifecycle = "failed";
|
|
154
|
+
reportOwnerError(owner, failures[0]);
|
|
155
|
+
} else if (failures.length > 1) {
|
|
156
|
+
owner.lifecycle = "failed";
|
|
157
|
+
reportOwnerError(
|
|
158
|
+
owner,
|
|
159
|
+
new AggregateError(
|
|
160
|
+
failures,
|
|
161
|
+
"Toolcraft source asset owner failed to dispose all owned resources.",
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
} else if (sourceAssetOwners.get(owner.store) === owner) {
|
|
165
|
+
sourceAssetOwners.delete(owner.store);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function scheduleOwnerDisposal(owner: ToolcraftSourceAssetOwner): void {
|
|
170
|
+
const lifecycleGeneration = ++owner.lifecycleGeneration;
|
|
171
|
+
queueMicrotask(() => {
|
|
172
|
+
if (
|
|
173
|
+
owner.lifecycle !== "active" ||
|
|
174
|
+
owner.leases !== 0 ||
|
|
175
|
+
owner.lifecycleGeneration !== lifecycleGeneration ||
|
|
176
|
+
sourceAssetOwners.get(owner.store) !== owner
|
|
177
|
+
) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
owner.lifecycle = "disposing";
|
|
181
|
+
void disposeOwner(owner);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function acquireToolcraftSourceAssetOwner({
|
|
186
|
+
createCoordinator = createDefaultCoordinator,
|
|
187
|
+
decorateCoordinator,
|
|
188
|
+
onError = reportSourceAssetOwnerError,
|
|
189
|
+
store,
|
|
190
|
+
}: Readonly<{
|
|
191
|
+
createCoordinator?: ToolcraftSourceAssetCoordinatorFactory;
|
|
192
|
+
decorateCoordinator?: ToolcraftSourceAssetCoordinatorDecorator;
|
|
193
|
+
onError?: (error: unknown) => void;
|
|
194
|
+
store: ToolcraftExternalStore;
|
|
195
|
+
}>): ToolcraftSourceAssetOwnerLease {
|
|
196
|
+
let owner = sourceAssetOwners.get(store);
|
|
197
|
+
if (owner?.lifecycle === "disposing") {
|
|
198
|
+
throw new Error("Toolcraft source asset owner is disposing.");
|
|
199
|
+
}
|
|
200
|
+
if (owner?.lifecycle === "failed") {
|
|
201
|
+
throw new Error("Toolcraft source asset owner failed to dispose.");
|
|
202
|
+
}
|
|
203
|
+
if (!owner) {
|
|
204
|
+
owner = createOwner(
|
|
205
|
+
store,
|
|
206
|
+
createCoordinator,
|
|
207
|
+
decorateCoordinator,
|
|
208
|
+
onError,
|
|
209
|
+
);
|
|
210
|
+
sourceAssetOwners.set(store, owner);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
owner.leases += 1;
|
|
214
|
+
owner.lifecycleGeneration += 1;
|
|
215
|
+
let released = false;
|
|
216
|
+
return Object.freeze({
|
|
217
|
+
coordinator: owner.coordinator,
|
|
218
|
+
presentationResources: owner.presentationResources,
|
|
219
|
+
release: () => {
|
|
220
|
+
if (released) return;
|
|
221
|
+
released = true;
|
|
222
|
+
owner.leases -= 1;
|
|
223
|
+
if (owner.leases === 0) scheduleOwnerDisposal(owner);
|
|
224
|
+
},
|
|
225
|
+
reportError: (error: unknown) => reportOwnerError(owner, error),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cleanup, render, waitFor } from "@testing-library/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import type { ToolcraftSourceAssetCoordinator } from "../../source-assets/source-asset-coordinator";
|
|
7
|
+
import { ToolcraftSourceAssetProvider } from "./toolcraft-source-asset-context";
|
|
8
|
+
import {
|
|
9
|
+
ToolcraftSourceAssetCoordinatorProbe as CoordinatorProbe,
|
|
10
|
+
createTestSourceAssetCoordinator as createTestCoordinator,
|
|
11
|
+
createTestSourceAssetStore as createTestStore,
|
|
12
|
+
} from "./toolcraft-source-asset-test-support";
|
|
13
|
+
|
|
14
|
+
afterEach(cleanup);
|
|
15
|
+
|
|
16
|
+
function createDeferred<T>() {
|
|
17
|
+
let reject!: (reason?: unknown) => void;
|
|
18
|
+
const promise = new Promise<T>((_resolve, rejectPromise) => {
|
|
19
|
+
reject = rejectPromise;
|
|
20
|
+
});
|
|
21
|
+
return { promise, reject };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("Toolcraft source asset provider ownership", () => {
|
|
25
|
+
it("does not acquire or cache an owner for an uncommitted render", () => {
|
|
26
|
+
const store = createTestStore();
|
|
27
|
+
const coordinator = createTestCoordinator();
|
|
28
|
+
const createCoordinator = vi.fn(() => coordinator);
|
|
29
|
+
|
|
30
|
+
expect(renderToString(
|
|
31
|
+
<ToolcraftSourceAssetProvider
|
|
32
|
+
createCoordinator={createCoordinator}
|
|
33
|
+
store={store}
|
|
34
|
+
>
|
|
35
|
+
<span>Server probe</span>
|
|
36
|
+
</ToolcraftSourceAssetProvider>,
|
|
37
|
+
)).toBe("");
|
|
38
|
+
expect(createCoordinator).not.toHaveBeenCalled();
|
|
39
|
+
|
|
40
|
+
const rendered = render(
|
|
41
|
+
<ToolcraftSourceAssetProvider
|
|
42
|
+
createCoordinator={createCoordinator}
|
|
43
|
+
store={store}
|
|
44
|
+
>
|
|
45
|
+
<span data-testid="committed-source-asset-child" />
|
|
46
|
+
</ToolcraftSourceAssetProvider>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(rendered.getByTestId("committed-source-asset-child")).toBeDefined();
|
|
50
|
+
expect(createCoordinator).toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("preserves the injected coordinator receiver through the provider", async () => {
|
|
54
|
+
type ReceiverAwareCoordinator = ToolcraftSourceAssetCoordinator & {
|
|
55
|
+
resolverBytes: Uint8Array;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const resolverBytes = new Uint8Array([4, 5, 6]);
|
|
59
|
+
const coordinator: ReceiverAwareCoordinator = {
|
|
60
|
+
...createTestCoordinator(),
|
|
61
|
+
resolverBytes,
|
|
62
|
+
resolveResource: vi.fn(async function (
|
|
63
|
+
this: ReceiverAwareCoordinator,
|
|
64
|
+
) {
|
|
65
|
+
expect(this).toBe(coordinator);
|
|
66
|
+
return this.resolverBytes;
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
const observed: ToolcraftSourceAssetCoordinator[] = [];
|
|
70
|
+
render(
|
|
71
|
+
<ToolcraftSourceAssetProvider
|
|
72
|
+
createCoordinator={() => coordinator}
|
|
73
|
+
store={createTestStore()}
|
|
74
|
+
>
|
|
75
|
+
<CoordinatorProbe capture={(value) => observed.push(value)} />
|
|
76
|
+
</ToolcraftSourceAssetProvider>,
|
|
77
|
+
);
|
|
78
|
+
const resolveResource = observed[0]?.resolveResource;
|
|
79
|
+
if (!resolveResource) throw new Error("Expected a resource resolver.");
|
|
80
|
+
|
|
81
|
+
await expect(resolveResource(
|
|
82
|
+
"media:image:receiver-aware",
|
|
83
|
+
{ signal: new AbortController().signal },
|
|
84
|
+
)).resolves.toBe(resolverBytes);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("reports hydration failures through the contextual owner channel", async () => {
|
|
88
|
+
const failure = new Error("model hydration failed");
|
|
89
|
+
const onError = vi.fn();
|
|
90
|
+
const coordinator = createTestCoordinator({
|
|
91
|
+
hydrateModels: vi.fn(async () => Promise.reject(failure)),
|
|
92
|
+
});
|
|
93
|
+
const rendered = render(
|
|
94
|
+
<ToolcraftSourceAssetProvider
|
|
95
|
+
createCoordinator={() => coordinator}
|
|
96
|
+
onError={onError}
|
|
97
|
+
store={createTestStore()}
|
|
98
|
+
>
|
|
99
|
+
<span />
|
|
100
|
+
</ToolcraftSourceAssetProvider>,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await waitFor(() => expect(onError).toHaveBeenCalledWith(failure));
|
|
104
|
+
rendered.unmount();
|
|
105
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("keeps same-store construction inputs stable across prop identity changes", async () => {
|
|
109
|
+
const store = createTestStore();
|
|
110
|
+
const firstReporter = vi.fn();
|
|
111
|
+
const replacementReporter = vi.fn();
|
|
112
|
+
const hydration = createDeferred<void>();
|
|
113
|
+
const hydrationFailure = new Error("original owner hydration failed");
|
|
114
|
+
const coordinator = createTestCoordinator({
|
|
115
|
+
hydrateModels: vi.fn(() => hydration.promise),
|
|
116
|
+
});
|
|
117
|
+
const observed: ToolcraftSourceAssetCoordinator[] = [];
|
|
118
|
+
const firstFactory = vi.fn(() => coordinator);
|
|
119
|
+
const replacementFactory = vi.fn(() => createTestCoordinator());
|
|
120
|
+
const rendered = render(
|
|
121
|
+
<ToolcraftSourceAssetProvider
|
|
122
|
+
createCoordinator={firstFactory}
|
|
123
|
+
onError={firstReporter}
|
|
124
|
+
store={store}
|
|
125
|
+
>
|
|
126
|
+
<CoordinatorProbe capture={(value) => observed.push(value)} />
|
|
127
|
+
<span data-testid="stable-source-asset-child" />
|
|
128
|
+
</ToolcraftSourceAssetProvider>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
rendered.rerender(
|
|
132
|
+
<ToolcraftSourceAssetProvider
|
|
133
|
+
createCoordinator={replacementFactory}
|
|
134
|
+
onError={replacementReporter}
|
|
135
|
+
store={store}
|
|
136
|
+
>
|
|
137
|
+
<CoordinatorProbe capture={(value) => observed.push(value)} />
|
|
138
|
+
<span data-testid="stable-source-asset-child" />
|
|
139
|
+
</ToolcraftSourceAssetProvider>,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(rendered.getByTestId("stable-source-asset-child")).toBeDefined();
|
|
143
|
+
expect(observed.length).toBeGreaterThan(0);
|
|
144
|
+
expect(observed.every((value) => value === coordinator)).toBe(true);
|
|
145
|
+
expect(firstFactory).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(replacementFactory).not.toHaveBeenCalled();
|
|
147
|
+
hydration.reject(hydrationFailure);
|
|
148
|
+
await waitFor(() => expect(firstReporter).toHaveBeenCalledWith(
|
|
149
|
+
hydrationFailure,
|
|
150
|
+
));
|
|
151
|
+
expect(replacementReporter).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("uses current construction inputs when the store changes", async () => {
|
|
155
|
+
const firstStore = createTestStore();
|
|
156
|
+
const secondStore = createTestStore();
|
|
157
|
+
const firstReporter = vi.fn();
|
|
158
|
+
const secondReporter = vi.fn();
|
|
159
|
+
const secondFailure = new Error("second store hydration failed");
|
|
160
|
+
const firstCoordinator = createTestCoordinator();
|
|
161
|
+
const secondCoordinator = createTestCoordinator({
|
|
162
|
+
hydrateModels: vi.fn(async () => Promise.reject(secondFailure)),
|
|
163
|
+
});
|
|
164
|
+
const firstFactory = vi.fn(() => firstCoordinator);
|
|
165
|
+
const secondFactory = vi.fn(() => secondCoordinator);
|
|
166
|
+
const rendered = render(
|
|
167
|
+
<ToolcraftSourceAssetProvider
|
|
168
|
+
createCoordinator={firstFactory}
|
|
169
|
+
onError={firstReporter}
|
|
170
|
+
store={firstStore}
|
|
171
|
+
>
|
|
172
|
+
<span data-testid="switched-source-asset-child" />
|
|
173
|
+
</ToolcraftSourceAssetProvider>,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
rendered.rerender(
|
|
177
|
+
<ToolcraftSourceAssetProvider
|
|
178
|
+
createCoordinator={secondFactory}
|
|
179
|
+
onError={secondReporter}
|
|
180
|
+
store={secondStore}
|
|
181
|
+
>
|
|
182
|
+
<span data-testid="switched-source-asset-child" />
|
|
183
|
+
</ToolcraftSourceAssetProvider>,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await waitFor(() => expect(secondReporter).toHaveBeenCalledWith(
|
|
187
|
+
secondFailure,
|
|
188
|
+
));
|
|
189
|
+
expect(rendered.getByTestId("switched-source-asset-child")).toBeDefined();
|
|
190
|
+
expect(firstFactory).toHaveBeenCalledWith(firstStore);
|
|
191
|
+
expect(secondFactory).toHaveBeenCalledWith(secondStore);
|
|
192
|
+
expect(firstReporter).not.toHaveBeenCalledWith(secondFailure);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("supports the deprecated error reporter alias for all owner failures", async () => {
|
|
196
|
+
const operationalFailure = new Error("model hydration failed through alias");
|
|
197
|
+
const disposalFailure = new Error("owner disposal failed through alias");
|
|
198
|
+
const onDisposeError = vi.fn();
|
|
199
|
+
const coordinator = createTestCoordinator({
|
|
200
|
+
dispose: vi.fn(async () => Promise.reject(disposalFailure)),
|
|
201
|
+
hydrateModels: vi.fn(async () => Promise.reject(operationalFailure)),
|
|
202
|
+
});
|
|
203
|
+
const rendered = render(
|
|
204
|
+
<ToolcraftSourceAssetProvider
|
|
205
|
+
createCoordinator={() => coordinator}
|
|
206
|
+
onDisposeError={onDisposeError}
|
|
207
|
+
store={createTestStore()}
|
|
208
|
+
>
|
|
209
|
+
<span />
|
|
210
|
+
</ToolcraftSourceAssetProvider>,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
await waitFor(() => expect(onDisposeError).toHaveBeenCalledWith(
|
|
214
|
+
operationalFailure,
|
|
215
|
+
));
|
|
216
|
+
rendered.unmount();
|
|
217
|
+
await waitFor(() => expect(onDisposeError).toHaveBeenCalledWith(
|
|
218
|
+
disposalFailure,
|
|
219
|
+
));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("prefers onError over the deprecated reporter alias", async () => {
|
|
223
|
+
const failure = new Error("primary reporter failure");
|
|
224
|
+
const onError = vi.fn();
|
|
225
|
+
const onDisposeError = vi.fn();
|
|
226
|
+
const rendered = render(
|
|
227
|
+
<ToolcraftSourceAssetProvider
|
|
228
|
+
createCoordinator={() => createTestCoordinator({
|
|
229
|
+
hydrateModels: vi.fn(async () => Promise.reject(failure)),
|
|
230
|
+
})}
|
|
231
|
+
onDisposeError={onDisposeError}
|
|
232
|
+
onError={onError}
|
|
233
|
+
store={createTestStore()}
|
|
234
|
+
>
|
|
235
|
+
<span />
|
|
236
|
+
</ToolcraftSourceAssetProvider>,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
await waitFor(() => expect(onError).toHaveBeenCalledWith(failure));
|
|
240
|
+
expect(onDisposeError).not.toHaveBeenCalled();
|
|
241
|
+
rendered.unmount();
|
|
242
|
+
});
|
|
243
|
+
});
|