@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,370 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
|
|
4
|
+
import {
|
|
5
|
+
assertToolcraftBrowserProofSession,
|
|
6
|
+
getToolcraftBrowserActionTarget,
|
|
7
|
+
readToolcraftBrowserObservation,
|
|
8
|
+
runToolcraftBrowserAction,
|
|
9
|
+
runToolcraftBrowserValueAction,
|
|
10
|
+
type ToolcraftBrowserAction,
|
|
11
|
+
type ToolcraftBrowserObservation,
|
|
12
|
+
type ToolcraftBrowserProofSession,
|
|
13
|
+
} from "./browser-proof-session";
|
|
14
|
+
import {
|
|
15
|
+
assertToolcraftProducedArtifact,
|
|
16
|
+
validateToolcraftExportArtifactInspection,
|
|
17
|
+
type ToolcraftExportArtifactInspection,
|
|
18
|
+
} from "./export-artifact-helpers";
|
|
19
|
+
|
|
20
|
+
declare const toolcraftUnavailableImageResourceFixtureBrand: unique symbol;
|
|
21
|
+
|
|
22
|
+
export type ToolcraftUnavailableImageResourceObservation = Readonly<{
|
|
23
|
+
assetId?: string;
|
|
24
|
+
fileName?: string;
|
|
25
|
+
lifecycle?: "ready" | "unavailable";
|
|
26
|
+
message?: string;
|
|
27
|
+
requestId?: string;
|
|
28
|
+
resolverAvailable?: boolean;
|
|
29
|
+
status: "active" | "cleaned" | "error" | "missing";
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
export type ToolcraftUnavailableImageResourceFixture = Readonly<{
|
|
33
|
+
readonly [toolcraftUnavailableImageResourceFixtureBrand]: true;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
type ToolcraftUnavailableImageResourceFixtureRecord = Readonly<{
|
|
37
|
+
activate: ToolcraftBrowserAction;
|
|
38
|
+
cleanup: ToolcraftBrowserAction;
|
|
39
|
+
fileName: string;
|
|
40
|
+
observe: ToolcraftBrowserObservation<string | null>;
|
|
41
|
+
requestId: string;
|
|
42
|
+
target: string;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
const unavailableImageResourceFixtureRecords = new WeakMap<
|
|
46
|
+
object,
|
|
47
|
+
ToolcraftUnavailableImageResourceFixtureRecord
|
|
48
|
+
>();
|
|
49
|
+
const malformedUnavailableImageResourceObservation = Object.freeze({
|
|
50
|
+
message: "Unavailable-resource proof published malformed state.",
|
|
51
|
+
status: "error" as const,
|
|
52
|
+
});
|
|
53
|
+
let unavailableImageResourceFixtureSequence = 0;
|
|
54
|
+
|
|
55
|
+
function requireUnavailableImageResourceFixture(
|
|
56
|
+
fixture: ToolcraftUnavailableImageResourceFixture,
|
|
57
|
+
): ToolcraftUnavailableImageResourceFixtureRecord {
|
|
58
|
+
const record =
|
|
59
|
+
typeof fixture === "object" && fixture !== null
|
|
60
|
+
? unavailableImageResourceFixtureRecords.get(fixture)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!record) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Unavailable-image export evidence requires the protected Toolcraft unavailable-resource fixture facade.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return record;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseUnavailableImageResourceObservation(
|
|
71
|
+
raw: string | null,
|
|
72
|
+
): ToolcraftUnavailableImageResourceObservation {
|
|
73
|
+
if (!raw) return { status: "missing" };
|
|
74
|
+
let value: unknown;
|
|
75
|
+
try {
|
|
76
|
+
value = JSON.parse(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
return malformedUnavailableImageResourceObservation;
|
|
79
|
+
}
|
|
80
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
81
|
+
return malformedUnavailableImageResourceObservation;
|
|
82
|
+
}
|
|
83
|
+
const observation = value as Record<string, unknown>;
|
|
84
|
+
const optionalStringsAreValid = [
|
|
85
|
+
"assetId",
|
|
86
|
+
"fileName",
|
|
87
|
+
"message",
|
|
88
|
+
"requestId",
|
|
89
|
+
].every(
|
|
90
|
+
(field) =>
|
|
91
|
+
observation[field] === undefined ||
|
|
92
|
+
typeof observation[field] === "string",
|
|
93
|
+
);
|
|
94
|
+
const valid =
|
|
95
|
+
typeof observation.status === "string" &&
|
|
96
|
+
["active", "cleaned", "error", "missing"].includes(observation.status) &&
|
|
97
|
+
optionalStringsAreValid &&
|
|
98
|
+
(
|
|
99
|
+
observation.resolverAvailable === undefined ||
|
|
100
|
+
typeof observation.resolverAvailable === "boolean"
|
|
101
|
+
) &&
|
|
102
|
+
(
|
|
103
|
+
observation.lifecycle === undefined ||
|
|
104
|
+
observation.lifecycle === "ready" ||
|
|
105
|
+
observation.lifecycle === "unavailable"
|
|
106
|
+
);
|
|
107
|
+
return valid
|
|
108
|
+
? observation as ToolcraftUnavailableImageResourceObservation
|
|
109
|
+
: malformedUnavailableImageResourceObservation;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function readUnavailableImageResourceObservation(
|
|
113
|
+
observation: ToolcraftBrowserObservation<string | null>,
|
|
114
|
+
): Promise<ToolcraftUnavailableImageResourceObservation> {
|
|
115
|
+
const raw = await readToolcraftBrowserObservation(observation);
|
|
116
|
+
return parseUnavailableImageResourceObservation(raw);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validateUnavailableImageResourceObservation(
|
|
120
|
+
observation: ToolcraftUnavailableImageResourceObservation,
|
|
121
|
+
expected: Readonly<{
|
|
122
|
+
fileName: string;
|
|
123
|
+
requestId: string;
|
|
124
|
+
status: "active" | "cleaned";
|
|
125
|
+
}>,
|
|
126
|
+
): void {
|
|
127
|
+
if (observation.status === "error") {
|
|
128
|
+
throw new Error(
|
|
129
|
+
observation.message ??
|
|
130
|
+
"The Toolcraft unavailable-resource proof fixture failed.",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
expect(
|
|
134
|
+
observation,
|
|
135
|
+
`Unavailable-resource proof "${expected.requestId}" must publish its exact typed state.`,
|
|
136
|
+
).toMatchObject(
|
|
137
|
+
expected.status === "active"
|
|
138
|
+
? {
|
|
139
|
+
fileName: expected.fileName,
|
|
140
|
+
lifecycle: "unavailable",
|
|
141
|
+
requestId: expected.requestId,
|
|
142
|
+
resolverAvailable: false,
|
|
143
|
+
status: "active",
|
|
144
|
+
}
|
|
145
|
+
: {
|
|
146
|
+
fileName: expected.fileName,
|
|
147
|
+
lifecycle: "ready",
|
|
148
|
+
requestId: expected.requestId,
|
|
149
|
+
resolverAvailable: true,
|
|
150
|
+
status: "cleaned",
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
expect(
|
|
154
|
+
observation.assetId?.trim(),
|
|
155
|
+
"Unavailable-resource proof must identify the exact affected runtime asset.",
|
|
156
|
+
).toBeTruthy();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createToolcraftUnavailableImageResourceFixture(
|
|
160
|
+
session: ToolcraftBrowserProofSession,
|
|
161
|
+
{
|
|
162
|
+
fileName,
|
|
163
|
+
sourceTarget,
|
|
164
|
+
target,
|
|
165
|
+
}: Readonly<{
|
|
166
|
+
fileName: string;
|
|
167
|
+
sourceTarget?: string;
|
|
168
|
+
target: string;
|
|
169
|
+
}>,
|
|
170
|
+
): ToolcraftUnavailableImageResourceFixture {
|
|
171
|
+
const normalizedFileName = fileName.trim();
|
|
172
|
+
const normalizedSourceTarget = sourceTarget?.trim();
|
|
173
|
+
const normalizedTarget = target.trim();
|
|
174
|
+
expect(
|
|
175
|
+
normalizedFileName,
|
|
176
|
+
"Unavailable-resource proof requires an exact non-empty image file name.",
|
|
177
|
+
).not.toBe("");
|
|
178
|
+
expect(
|
|
179
|
+
normalizedTarget,
|
|
180
|
+
"Unavailable-resource proof requires an exact semantic acceptance target.",
|
|
181
|
+
).not.toBe("");
|
|
182
|
+
if (sourceTarget !== undefined) {
|
|
183
|
+
expect(
|
|
184
|
+
normalizedSourceTarget,
|
|
185
|
+
"Unavailable-resource proof sourceTarget must be non-empty when provided.",
|
|
186
|
+
).not.toBe("");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const requestId =
|
|
190
|
+
`unavailable-image-${++unavailableImageResourceFixtureSequence}`;
|
|
191
|
+
const request = {
|
|
192
|
+
fileName: normalizedFileName,
|
|
193
|
+
requestId,
|
|
194
|
+
...(normalizedSourceTarget
|
|
195
|
+
? { sourceTarget: normalizedSourceTarget }
|
|
196
|
+
: {}),
|
|
197
|
+
};
|
|
198
|
+
const runRequest = (
|
|
199
|
+
action: "activate" | "cleanup",
|
|
200
|
+
expectedStatus: "active" | "cleaned",
|
|
201
|
+
) =>
|
|
202
|
+
session.targetAction(normalizedTarget, async (page) => {
|
|
203
|
+
await page.evaluate(
|
|
204
|
+
({ detail, eventName }) => {
|
|
205
|
+
document.dispatchEvent(
|
|
206
|
+
new CustomEvent(eventName, {
|
|
207
|
+
detail,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
detail: { ...request, action },
|
|
213
|
+
eventName:
|
|
214
|
+
"toolcraft.browser-proof.unavailable-resource-request",
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
await expect
|
|
218
|
+
.poll(
|
|
219
|
+
async () => {
|
|
220
|
+
const raw = await page
|
|
221
|
+
.locator('[data-slot="toolcraft-runtime-app"]')
|
|
222
|
+
.getAttribute(
|
|
223
|
+
"data-toolcraft-unavailable-resource-evidence",
|
|
224
|
+
);
|
|
225
|
+
const observation =
|
|
226
|
+
parseUnavailableImageResourceObservation(raw);
|
|
227
|
+
if (
|
|
228
|
+
observation.status === "error" &&
|
|
229
|
+
(
|
|
230
|
+
observation.requestId === undefined ||
|
|
231
|
+
observation.requestId === requestId
|
|
232
|
+
)
|
|
233
|
+
) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
observation.message ??
|
|
236
|
+
"The Toolcraft unavailable-resource proof fixture failed.",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return observation.requestId === requestId
|
|
240
|
+
? observation.status
|
|
241
|
+
: "other-request";
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
message:
|
|
245
|
+
`Unavailable-resource proof "${requestId}" should reach ${expectedStatus}.`,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
.toBe(expectedStatus);
|
|
249
|
+
});
|
|
250
|
+
const activate = runRequest("activate", "active");
|
|
251
|
+
const cleanup = runRequest("cleanup", "cleaned");
|
|
252
|
+
const observe = session.observe((root) =>
|
|
253
|
+
root.getAttribute("data-toolcraft-unavailable-resource-evidence"),
|
|
254
|
+
);
|
|
255
|
+
const fixture = Object.freeze({}) as ToolcraftUnavailableImageResourceFixture;
|
|
256
|
+
unavailableImageResourceFixtureRecords.set(fixture, {
|
|
257
|
+
activate,
|
|
258
|
+
cleanup,
|
|
259
|
+
fileName: normalizedFileName,
|
|
260
|
+
observe,
|
|
261
|
+
requestId,
|
|
262
|
+
target: normalizedTarget,
|
|
263
|
+
});
|
|
264
|
+
return fixture;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function expectToolcraftInfinityCanvasUnavailableImageExportEvidence<
|
|
268
|
+
TArtifact,
|
|
269
|
+
>(
|
|
270
|
+
fixture: ToolcraftUnavailableImageResourceFixture,
|
|
271
|
+
produceArtifact: ToolcraftBrowserAction<"interaction", TArtifact>,
|
|
272
|
+
inspectArtifact: (
|
|
273
|
+
artifact: TArtifact,
|
|
274
|
+
) => Promise<ToolcraftExportArtifactInspection> | ToolcraftExportArtifactInspection,
|
|
275
|
+
{
|
|
276
|
+
expectedSize,
|
|
277
|
+
requirementId,
|
|
278
|
+
}: Readonly<{
|
|
279
|
+
expectedSize: Readonly<{ height: number; width: number }>;
|
|
280
|
+
requirementId: string;
|
|
281
|
+
}>,
|
|
282
|
+
): Promise<ToolcraftExportArtifactInspection> {
|
|
283
|
+
const record = requireUnavailableImageResourceFixture(fixture);
|
|
284
|
+
assertToolcraftBrowserProofSession(
|
|
285
|
+
record.observe,
|
|
286
|
+
record.activate,
|
|
287
|
+
record.cleanup,
|
|
288
|
+
produceArtifact,
|
|
289
|
+
);
|
|
290
|
+
const exportTarget = getToolcraftBrowserActionTarget(produceArtifact)?.trim();
|
|
291
|
+
expect(
|
|
292
|
+
exportTarget,
|
|
293
|
+
"Unavailable-image export evidence requires a target-scoped artifact action.",
|
|
294
|
+
).toBe(record.target);
|
|
295
|
+
|
|
296
|
+
let activationAttempted = false;
|
|
297
|
+
let cleanupSucceeded = false;
|
|
298
|
+
let inspection: ToolcraftExportArtifactInspection | null = null;
|
|
299
|
+
let outputVerified = false;
|
|
300
|
+
try {
|
|
301
|
+
activationAttempted = true;
|
|
302
|
+
await runToolcraftBrowserAction(record.activate);
|
|
303
|
+
validateUnavailableImageResourceObservation(
|
|
304
|
+
await readUnavailableImageResourceObservation(record.observe),
|
|
305
|
+
{
|
|
306
|
+
fileName: record.fileName,
|
|
307
|
+
requestId: record.requestId,
|
|
308
|
+
status: "active",
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const artifact = await runToolcraftBrowserValueAction(produceArtifact);
|
|
313
|
+
assertToolcraftProducedArtifact(artifact, requirementId);
|
|
314
|
+
inspection = await inspectArtifact(artifact);
|
|
315
|
+
validateToolcraftExportArtifactInspection(inspection, requirementId);
|
|
316
|
+
expect(
|
|
317
|
+
inspection,
|
|
318
|
+
`Unavailable-image export "${requirementId}" must decode to the expected visible scene bounds.`,
|
|
319
|
+
).toMatchObject(expectedSize);
|
|
320
|
+
validateUnavailableImageResourceObservation(
|
|
321
|
+
await readUnavailableImageResourceObservation(record.observe),
|
|
322
|
+
{
|
|
323
|
+
fileName: record.fileName,
|
|
324
|
+
requestId: record.requestId,
|
|
325
|
+
status: "active",
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
outputVerified = true;
|
|
329
|
+
} finally {
|
|
330
|
+
const current = activationAttempted
|
|
331
|
+
? await readUnavailableImageResourceObservation(record.observe)
|
|
332
|
+
: null;
|
|
333
|
+
if (
|
|
334
|
+
current?.requestId === record.requestId &&
|
|
335
|
+
current.status === "active"
|
|
336
|
+
) {
|
|
337
|
+
await runToolcraftBrowserAction(record.cleanup);
|
|
338
|
+
validateUnavailableImageResourceObservation(
|
|
339
|
+
await readUnavailableImageResourceObservation(record.observe),
|
|
340
|
+
{
|
|
341
|
+
fileName: record.fileName,
|
|
342
|
+
requestId: record.requestId,
|
|
343
|
+
status: "cleaned",
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
cleanupSucceeded = true;
|
|
347
|
+
} else if (outputVerified) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
"Unavailable-image export evidence requires the exact active fixture to remain available for deterministic cleanup.",
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!cleanupSucceeded || !inspection) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
"Unavailable-image export evidence requires successful fixture cleanup before publication.",
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
await attachToolcraftBrowserRuntimeEvidence({
|
|
360
|
+
evidenceType: "exported-artifact",
|
|
361
|
+
requirementId,
|
|
362
|
+
target: exportTarget,
|
|
363
|
+
});
|
|
364
|
+
await attachToolcraftBrowserRuntimeEvidence({
|
|
365
|
+
evidenceType: "infinity-scene-bounds-image-export",
|
|
366
|
+
requirementId,
|
|
367
|
+
target: exportTarget,
|
|
368
|
+
});
|
|
369
|
+
return inspection;
|
|
370
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { expect, test, type Download, type Page } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
import { expectToolcraftImageExportArtifact } from "./browser-media-export-evidence";
|
|
6
|
+
|
|
7
|
+
async function createImageFixture(
|
|
8
|
+
page: Page,
|
|
9
|
+
): Promise<Buffer> {
|
|
10
|
+
const base64 = await page.evaluate(() => {
|
|
11
|
+
const canvas = document.createElement("canvas");
|
|
12
|
+
canvas.width = 64;
|
|
13
|
+
canvas.height = 64;
|
|
14
|
+
const context = canvas.getContext("2d");
|
|
15
|
+
if (!context) throw new Error("Missing test canvas context.");
|
|
16
|
+
context.fillStyle = "#000000";
|
|
17
|
+
context.fillRect(0, 0, 64, 64);
|
|
18
|
+
context.fillStyle = "#FF0000";
|
|
19
|
+
context.fillRect(16, 16, 32, 32);
|
|
20
|
+
return canvas.toDataURL("image/png").split(",")[1]!;
|
|
21
|
+
});
|
|
22
|
+
return Buffer.from(base64, "base64");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("protected image evidence attaches only after decoded semantics pass", async ({
|
|
26
|
+
page,
|
|
27
|
+
}, testInfo) => {
|
|
28
|
+
await page.goto("/");
|
|
29
|
+
const artifactPath = testInfo.outputPath("protected-image.png");
|
|
30
|
+
await fs.writeFile(artifactPath, await createImageFixture(page));
|
|
31
|
+
const download = { path: async () => artifactPath } as Download;
|
|
32
|
+
|
|
33
|
+
await expectToolcraftImageExportArtifact(async () => download, {
|
|
34
|
+
backgroundRgba: [0, 0, 0, 255],
|
|
35
|
+
expectedBounds: { height: 0.5, width: 0.5, x: 0.25, y: 0.25 },
|
|
36
|
+
expectedHeight: 64,
|
|
37
|
+
expectedMediaType: "image/png",
|
|
38
|
+
expectedPixels: [
|
|
39
|
+
{ rgba: [0, 0, 0, 255], xRatio: 0.1, yRatio: 0.1 },
|
|
40
|
+
{ rgba: [255, 0, 0, 255], xRatio: 0.5, yRatio: 0.5 },
|
|
41
|
+
],
|
|
42
|
+
expectedWidth: 64,
|
|
43
|
+
page,
|
|
44
|
+
requirementId: "fixture.image",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
test.info().attachments.some(
|
|
49
|
+
({ name }) => name === "toolcraft.browser-runtime-evidence",
|
|
50
|
+
),
|
|
51
|
+
).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("protected image evidence rejects empty product expectations before download", async ({
|
|
55
|
+
page,
|
|
56
|
+
}) => {
|
|
57
|
+
let called = false;
|
|
58
|
+
await expect(
|
|
59
|
+
expectToolcraftImageExportArtifact(
|
|
60
|
+
async () => {
|
|
61
|
+
called = true;
|
|
62
|
+
throw new Error("must not run");
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
backgroundRgba: [0, 0, 0, 255],
|
|
66
|
+
expectedBounds: { height: 0.5, width: 0.5, x: 0.25, y: 0.25 },
|
|
67
|
+
expectedHeight: 64,
|
|
68
|
+
expectedMediaType: "image/png",
|
|
69
|
+
expectedPixels: [],
|
|
70
|
+
expectedWidth: 64,
|
|
71
|
+
page,
|
|
72
|
+
requirementId: "fixture.empty",
|
|
73
|
+
},
|
|
74
|
+
),
|
|
75
|
+
).rejects.toThrow("at least one exact pixel sample");
|
|
76
|
+
expect(called).toBe(false);
|
|
77
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { expect, type Download, type Page } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assertToolcraftExpectedDecodedPixels,
|
|
5
|
+
getToolcraftRgbaDistance,
|
|
6
|
+
TOOLCRAFT_BACKGROUND_PIXEL_DISTANCE_THRESHOLD,
|
|
7
|
+
validateToolcraftExpectedDecodedPixels,
|
|
8
|
+
type ToolcraftExpectedDecodedPixel,
|
|
9
|
+
type ToolcraftExpectedDecodedVideoSample,
|
|
10
|
+
} from "./decoded-pixel-observation";
|
|
11
|
+
import type { ToolcraftNormalizedPixelBounds } from "./export-artifact-helpers";
|
|
12
|
+
import { inspectToolcraftImageDownload } from "./image-artifact-inspection";
|
|
13
|
+
import { attachToolcraftBrowserRuntimeEvidence } from "./browser-runtime-evidence";
|
|
14
|
+
import {
|
|
15
|
+
assertToolcraftVideoPacketSchedule,
|
|
16
|
+
inspectToolcraftVideoDownload,
|
|
17
|
+
type ToolcraftInspectedVideoArtifact,
|
|
18
|
+
type ToolcraftScheduledVideoFrame,
|
|
19
|
+
} from "./video-artifact-inspection";
|
|
20
|
+
import {
|
|
21
|
+
getToolcraftBrowserActionTarget,
|
|
22
|
+
isToolcraftBrowserAction,
|
|
23
|
+
runToolcraftBrowserValueAction,
|
|
24
|
+
type ToolcraftBrowserAction,
|
|
25
|
+
} from "./browser-proof-session";
|
|
26
|
+
|
|
27
|
+
type AdditionalArtifactRequirement = Readonly<{
|
|
28
|
+
requirementId: string;
|
|
29
|
+
target: string;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
async function attachAdditionalArtifactRequirements(
|
|
33
|
+
requirements: readonly AdditionalArtifactRequirement[] | undefined,
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
for (const requirement of requirements ?? []) {
|
|
36
|
+
await attachToolcraftBrowserRuntimeEvidence({
|
|
37
|
+
evidenceType: "exported-artifact",
|
|
38
|
+
requirementId: requirement.requirementId,
|
|
39
|
+
target: requirement.target,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalizedBoundsTolerance = 2 / 64;
|
|
45
|
+
|
|
46
|
+
function assertContainsProductExpectation(
|
|
47
|
+
expectedPixels: readonly ToolcraftExpectedDecodedPixel[],
|
|
48
|
+
backgroundRgba: readonly [number, number, number, number],
|
|
49
|
+
): void {
|
|
50
|
+
validateToolcraftExpectedDecodedPixels(expectedPixels);
|
|
51
|
+
if (
|
|
52
|
+
!expectedPixels.some(
|
|
53
|
+
({ rgba }) =>
|
|
54
|
+
getToolcraftRgbaDistance(rgba, backgroundRgba) >
|
|
55
|
+
TOOLCRAFT_BACKGROUND_PIXEL_DISTANCE_THRESHOLD,
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Media export evidence requires at least one non-background product pixel expectation.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function assertExpectedBounds(
|
|
65
|
+
actual: ToolcraftNormalizedPixelBounds | null,
|
|
66
|
+
expected: ToolcraftNormalizedPixelBounds,
|
|
67
|
+
): void {
|
|
68
|
+
expect(actual, "Decoded export must contain product pixels.").not.toBeNull();
|
|
69
|
+
if (!actual) return;
|
|
70
|
+
for (const field of ["height", "width", "x", "y"] as const) {
|
|
71
|
+
expect(
|
|
72
|
+
Math.abs(actual[field] - expected[field]),
|
|
73
|
+
`Decoded product ${field} must match the expected normalized bounds.`,
|
|
74
|
+
).toBeLessThanOrEqual(normalizedBoundsTolerance);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function expectToolcraftImageExportArtifact(
|
|
79
|
+
produceDownload:
|
|
80
|
+
| (() => Promise<Download>)
|
|
81
|
+
| ToolcraftBrowserAction<"interaction", Download>,
|
|
82
|
+
options: Readonly<{
|
|
83
|
+
additionalArtifactRequirements?: readonly AdditionalArtifactRequirement[];
|
|
84
|
+
backgroundRgba: readonly [number, number, number, number];
|
|
85
|
+
expectedBounds: ToolcraftNormalizedPixelBounds;
|
|
86
|
+
expectedHeight: number;
|
|
87
|
+
expectedMediaType: "image/jpeg" | "image/png";
|
|
88
|
+
expectedPixels: readonly ToolcraftExpectedDecodedPixel[];
|
|
89
|
+
expectedWidth: number;
|
|
90
|
+
page: Page;
|
|
91
|
+
requirementId: string;
|
|
92
|
+
}>,
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
assertContainsProductExpectation(options.expectedPixels, options.backgroundRgba);
|
|
95
|
+
const target = isToolcraftBrowserAction(produceDownload)
|
|
96
|
+
? getToolcraftBrowserActionTarget(produceDownload)
|
|
97
|
+
: undefined;
|
|
98
|
+
const download = isToolcraftBrowserAction(produceDownload)
|
|
99
|
+
? await runToolcraftBrowserValueAction(produceDownload)
|
|
100
|
+
: await produceDownload();
|
|
101
|
+
const inspected = await inspectToolcraftImageDownload({
|
|
102
|
+
backgroundRgba: options.backgroundRgba,
|
|
103
|
+
download,
|
|
104
|
+
page: options.page,
|
|
105
|
+
});
|
|
106
|
+
expect(inspected.inspection.byteLength).toBeGreaterThan(0);
|
|
107
|
+
expect(inspected.inspection.mediaType).toBe(options.expectedMediaType);
|
|
108
|
+
expect(inspected.inspection.width).toBe(options.expectedWidth);
|
|
109
|
+
expect(inspected.inspection.height).toBe(options.expectedHeight);
|
|
110
|
+
expect(inspected.inspection.decodedPixelHash).not.toHaveLength(0);
|
|
111
|
+
assertExpectedBounds(
|
|
112
|
+
inspected.inspection.nonBackgroundBounds,
|
|
113
|
+
options.expectedBounds,
|
|
114
|
+
);
|
|
115
|
+
assertToolcraftExpectedDecodedPixels({
|
|
116
|
+
expectedPixels: options.expectedPixels,
|
|
117
|
+
mediaType: inspected.inspection.mediaType,
|
|
118
|
+
observation: inspected.observation,
|
|
119
|
+
});
|
|
120
|
+
await attachAdditionalArtifactRequirements(
|
|
121
|
+
options.additionalArtifactRequirements,
|
|
122
|
+
);
|
|
123
|
+
await attachToolcraftBrowserRuntimeEvidence({
|
|
124
|
+
evidenceType: "image-export-artifact",
|
|
125
|
+
requirementId: options.requirementId,
|
|
126
|
+
target,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function expectToolcraftVideoExportArtifact(
|
|
131
|
+
produceDownload:
|
|
132
|
+
| (() => Promise<Download>)
|
|
133
|
+
| ToolcraftBrowserAction<"interaction", Download>,
|
|
134
|
+
options: Readonly<{
|
|
135
|
+
additionalArtifactRequirements?: readonly AdditionalArtifactRequirement[];
|
|
136
|
+
animated: boolean;
|
|
137
|
+
backgroundRgba: readonly [number, number, number, number];
|
|
138
|
+
expectedDurationSeconds: number;
|
|
139
|
+
expectedHeight: number;
|
|
140
|
+
expectedMediaType: "video/mp4" | "video/webm";
|
|
141
|
+
expectedSamples: readonly ToolcraftExpectedDecodedVideoSample[];
|
|
142
|
+
expectedWidth: number;
|
|
143
|
+
page: Page;
|
|
144
|
+
requirementId: string;
|
|
145
|
+
schedule: readonly ToolcraftScheduledVideoFrame[];
|
|
146
|
+
}>,
|
|
147
|
+
): Promise<ToolcraftInspectedVideoArtifact> {
|
|
148
|
+
if (options.schedule.length === 0) {
|
|
149
|
+
throw new Error("Video export evidence requires the runtime frame schedule.");
|
|
150
|
+
}
|
|
151
|
+
for (const sample of options.expectedSamples) {
|
|
152
|
+
assertContainsProductExpectation(sample.pixels, options.backgroundRgba);
|
|
153
|
+
}
|
|
154
|
+
const target = isToolcraftBrowserAction(produceDownload)
|
|
155
|
+
? getToolcraftBrowserActionTarget(produceDownload)
|
|
156
|
+
: undefined;
|
|
157
|
+
const download = isToolcraftBrowserAction(produceDownload)
|
|
158
|
+
? await runToolcraftBrowserValueAction(produceDownload)
|
|
159
|
+
: await produceDownload();
|
|
160
|
+
const inspected = await inspectToolcraftVideoDownload({
|
|
161
|
+
backgroundRgba: options.backgroundRgba,
|
|
162
|
+
download,
|
|
163
|
+
page: options.page,
|
|
164
|
+
schedule: options.schedule,
|
|
165
|
+
});
|
|
166
|
+
const toleranceSeconds = 1 / inspected.timeResolution;
|
|
167
|
+
expect(inspected.inspection.byteLength).toBeGreaterThan(0);
|
|
168
|
+
expect(inspected.inspection.mediaType).toBe(options.expectedMediaType);
|
|
169
|
+
expect(inspected.inspection.width).toBe(options.expectedWidth);
|
|
170
|
+
expect(inspected.inspection.height).toBe(options.expectedHeight);
|
|
171
|
+
expect(inspected.inspection.frameCount).toBe(options.schedule.length);
|
|
172
|
+
expect(
|
|
173
|
+
Math.abs(
|
|
174
|
+
inspected.inspection.durationMs - options.expectedDurationSeconds * 1000,
|
|
175
|
+
),
|
|
176
|
+
).toBeLessThanOrEqual(toleranceSeconds * 1000);
|
|
177
|
+
assertToolcraftVideoPacketSchedule({
|
|
178
|
+
packetTimings: inspected.inspection.packetTimings,
|
|
179
|
+
schedule: options.schedule,
|
|
180
|
+
timeResolution: inspected.timeResolution,
|
|
181
|
+
});
|
|
182
|
+
expect(inspected.sampleTimes).toEqual(
|
|
183
|
+
options.expectedSamples.map(({ timeSeconds }) => timeSeconds),
|
|
184
|
+
);
|
|
185
|
+
expect(inspected.observations).toHaveLength(options.expectedSamples.length);
|
|
186
|
+
for (const [index, observation] of inspected.observations.entries()) {
|
|
187
|
+
const expected = options.expectedSamples[index];
|
|
188
|
+
if (!expected) throw new Error(`Missing expected decoded sample ${index}.`);
|
|
189
|
+
expect(observation.nonBackgroundBounds).not.toBeNull();
|
|
190
|
+
assertToolcraftExpectedDecodedPixels({
|
|
191
|
+
expectedPixels: expected.pixels,
|
|
192
|
+
mediaType: inspected.inspection.mediaType,
|
|
193
|
+
observation,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (options.animated) {
|
|
197
|
+
expect(new Set(inspected.inspection.samplePixelHashes).size).toBeGreaterThan(1);
|
|
198
|
+
}
|
|
199
|
+
await attachAdditionalArtifactRequirements(
|
|
200
|
+
options.additionalArtifactRequirements,
|
|
201
|
+
);
|
|
202
|
+
await attachToolcraftBrowserRuntimeEvidence({
|
|
203
|
+
evidenceType: "video-export-artifact",
|
|
204
|
+
requirementId: options.requirementId,
|
|
205
|
+
target,
|
|
206
|
+
});
|
|
207
|
+
return inspected;
|
|
208
|
+
}
|
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
getToolcraftOrientationAxisPosition,
|
|
27
27
|
type ToolcraftOrientationAxis,
|
|
28
28
|
} from "./browser-orientation-gizmo-actions";
|
|
29
|
+
import {
|
|
30
|
+
expectToolcraftOrientationLiveDragPreconditions,
|
|
31
|
+
} from "./browser-orientation-gizmo-live-preconditions";
|
|
29
32
|
|
|
30
33
|
export type ToolcraftOrientationBrowserObservation = {
|
|
31
34
|
outputSignature: string;
|
|
@@ -222,6 +225,7 @@ export async function expectToolcraftOrientationAxisDrag(
|
|
|
222
225
|
message,
|
|
223
226
|
options,
|
|
224
227
|
);
|
|
228
|
+
await expectToolcraftOrientationLiveDragPreconditions(session);
|
|
225
229
|
const before = snapshotToolcraftOutcome(await observe(), message);
|
|
226
230
|
|
|
227
231
|
validateObservation(before, options);
|