@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
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { getToolcraftSegmentedStaticFitError } from "../contracts/segmented-control-fit";
|
|
2
2
|
import { normalizeToolcraftModelFileDrop } from "../model-import/model-import-limits";
|
|
3
|
+
import { isToolcraftCollectionItemControlType } from "./collection-item-controls";
|
|
4
|
+
import { resolveToolcraftControlApplicability } from "./control-applicability";
|
|
3
5
|
import type {
|
|
4
6
|
ResolvedToolcraftControlSchema,
|
|
7
|
+
ToolcraftModelFileDropSchema,
|
|
8
|
+
ToolcraftNonModelControlSchema,
|
|
9
|
+
ToolcraftResolvedControlApplicabilitySchema,
|
|
5
10
|
ToolcraftControlSchema,
|
|
6
11
|
} from "./types";
|
|
7
12
|
|
|
13
|
+
type ControlWithResolvedApplicability =
|
|
14
|
+
| (Omit<
|
|
15
|
+
ToolcraftNonModelControlSchema,
|
|
16
|
+
"applicability" | "visibleWhen"
|
|
17
|
+
> & {
|
|
18
|
+
applicability: ToolcraftResolvedControlApplicabilitySchema;
|
|
19
|
+
})
|
|
20
|
+
| (Omit<
|
|
21
|
+
ToolcraftModelFileDropSchema,
|
|
22
|
+
"applicability" | "visibleWhen"
|
|
23
|
+
> & {
|
|
24
|
+
applicability: ToolcraftResolvedControlApplicabilitySchema;
|
|
25
|
+
});
|
|
26
|
+
|
|
8
27
|
const modelOnlyControlFieldNames = [
|
|
9
28
|
"modelFormats",
|
|
10
29
|
"modelLimits",
|
|
@@ -82,6 +101,117 @@ function assertFileDropHardMaxItems(control: ToolcraftControlSchema): void {
|
|
|
82
101
|
}
|
|
83
102
|
}
|
|
84
103
|
|
|
104
|
+
function assertFileDropVariant(control: ToolcraftControlSchema): void {
|
|
105
|
+
if (
|
|
106
|
+
control.type !== "fileDrop" ||
|
|
107
|
+
control.variant !== "collection-actions"
|
|
108
|
+
) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (control.assetKind !== "file" || control.multiple !== true) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Toolcraft fileDrop validation [filedrop-collection-actions]: variant "collection-actions" requires assetKind: "file" with multiple: true.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type ItemControlsOwner = Readonly<{
|
|
120
|
+
minimumFields: number;
|
|
121
|
+
name: "collectionActions" | "fileDrop";
|
|
122
|
+
}>;
|
|
123
|
+
|
|
124
|
+
function getItemControlsOwner(
|
|
125
|
+
control: ToolcraftControlSchema,
|
|
126
|
+
): ItemControlsOwner | null {
|
|
127
|
+
if (control.type === "collectionActions") {
|
|
128
|
+
return { minimumFields: 2, name: "collectionActions" };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
control.type === "fileDrop" &&
|
|
133
|
+
control.variant === "collection-actions" &&
|
|
134
|
+
control.assetKind === "file" &&
|
|
135
|
+
control.multiple === true
|
|
136
|
+
) {
|
|
137
|
+
return { minimumFields: 1, name: "fileDrop" };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isPlainRecord(value: unknown): boolean {
|
|
144
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const prototype = Object.getPrototypeOf(value);
|
|
149
|
+
return prototype === Object.prototype || prototype === null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function assertItemControls(control: ToolcraftControlSchema): void {
|
|
153
|
+
if (!control.itemControls) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const owner = getItemControlsOwner(control);
|
|
158
|
+
|
|
159
|
+
if (!owner) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Toolcraft control validation [collection-item-controls] for target "${control.target}": itemControls require collectionActions or a multiple file-kind collection-actions fileDrop.`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (control.itemControl) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Toolcraft control validation [collection-item-template] for target "${control.target}": itemControl and itemControls are mutually exclusive.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (Object.prototype.hasOwnProperty.call(control, "itemDefaultValue")) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Toolcraft control validation [collection-item-default] for target "${control.target}": itemDefaultValue cannot be used with itemControls; field defaults own the new record.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const entries = Object.entries(control.itemControls);
|
|
178
|
+
if (entries.length < owner.minimumFields) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Toolcraft control validation [collection-item-controls] for target "${control.target}": ${owner.name} itemControls require at least ${owner.minimumFields === 1 ? "one" : "two"} built-in field${owner.minimumFields === 1 ? "" : "s"}.`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const [id, itemControl] of entries) {
|
|
185
|
+
if (!id.trim() || !isToolcraftCollectionItemControlType(itemControl.type)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Toolcraft control validation [collection-item-controls] for target "${control.target}" itemControls.${id}: unsupported built-in field.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (!Object.prototype.hasOwnProperty.call(itemControl, "defaultValue")) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Toolcraft control validation [collection-item-controls] for target "${control.target}" itemControls.${id}: defaultValue is required.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const fitError = getToolcraftSegmentedStaticFitError(itemControl);
|
|
197
|
+
if (fitError) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Toolcraft control validation [segmented-control-fit] for target "${control.target}" itemControls.${id}: ${fitError}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
owner.name === "collectionActions" &&
|
|
206
|
+
Array.isArray(control.defaultValue) &&
|
|
207
|
+
control.defaultValue.some((item) => !isPlainRecord(item))
|
|
208
|
+
) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Toolcraft control validation [collection-item-value] for target "${control.target}": every initial compound item must be a plain record.`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
85
215
|
function assertSegmentedControlFit(control: ToolcraftControlSchema): void {
|
|
86
216
|
const fitError = getToolcraftSegmentedStaticFitError(control);
|
|
87
217
|
|
|
@@ -92,13 +222,29 @@ function assertSegmentedControlFit(control: ToolcraftControlSchema): void {
|
|
|
92
222
|
}
|
|
93
223
|
}
|
|
94
224
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
225
|
+
function assertCollectionItemControl(control: ToolcraftControlSchema): void {
|
|
226
|
+
if (
|
|
227
|
+
control.type !== "collectionActions" &&
|
|
228
|
+
control.type !== "sourceCollection"
|
|
229
|
+
) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!control.itemControl) {
|
|
234
|
+
if (control.type === "sourceCollection") {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Toolcraft control validation [source-collection-item-control] for target "${control.target}": itemControl is required.`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
99
239
|
return;
|
|
100
240
|
}
|
|
101
241
|
|
|
242
|
+
if (!isToolcraftCollectionItemControlType(control.itemControl.type)) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Toolcraft control validation [collection-item-control] for target "${control.target}": unsupported collection item control "${String(control.itemControl.type)}".`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
102
248
|
const fitError = getToolcraftSegmentedStaticFitError(control.itemControl);
|
|
103
249
|
|
|
104
250
|
if (fitError) {
|
|
@@ -108,11 +254,15 @@ function assertCollectionItemSegmentedControlFit(
|
|
|
108
254
|
}
|
|
109
255
|
}
|
|
110
256
|
|
|
111
|
-
function isSliderLikeControl(
|
|
257
|
+
function isSliderLikeControl(
|
|
258
|
+
control: ControlWithResolvedApplicability,
|
|
259
|
+
): boolean {
|
|
112
260
|
return control.type === "slider" || control.type === "rangeSlider";
|
|
113
261
|
}
|
|
114
262
|
|
|
115
|
-
function getStepMarkerCount(
|
|
263
|
+
function getStepMarkerCount(
|
|
264
|
+
control: ControlWithResolvedApplicability,
|
|
265
|
+
): number | undefined {
|
|
116
266
|
if (
|
|
117
267
|
typeof control.step !== "number" ||
|
|
118
268
|
typeof control.min !== "number" ||
|
|
@@ -137,8 +287,8 @@ function getStepMarkerCount(control: ToolcraftControlSchema): number | undefined
|
|
|
137
287
|
}
|
|
138
288
|
|
|
139
289
|
function normalizeSliderControlSchema(
|
|
140
|
-
control:
|
|
141
|
-
):
|
|
290
|
+
control: ControlWithResolvedApplicability,
|
|
291
|
+
): ControlWithResolvedApplicability {
|
|
142
292
|
if (
|
|
143
293
|
!isSliderLikeControl(control) ||
|
|
144
294
|
typeof control.step !== "number" ||
|
|
@@ -155,7 +305,7 @@ function normalizeSliderControlSchema(
|
|
|
155
305
|
}
|
|
156
306
|
|
|
157
307
|
function normalizeFileDropControlSchema(
|
|
158
|
-
control:
|
|
308
|
+
control: ControlWithResolvedApplicability,
|
|
159
309
|
): ResolvedToolcraftControlSchema {
|
|
160
310
|
if (control.assetKind === "model") {
|
|
161
311
|
return normalizeToolcraftModelFileDrop(control);
|
|
@@ -180,10 +330,26 @@ function normalizeControlSchema(
|
|
|
180
330
|
assertRequiredControlStringField(controlSnapshot, "target");
|
|
181
331
|
assertControlRuntimeBoundary(controlSnapshot);
|
|
182
332
|
assertFileDropHardMaxItems(controlSnapshot);
|
|
333
|
+
assertFileDropVariant(controlSnapshot);
|
|
334
|
+
assertItemControls(controlSnapshot);
|
|
183
335
|
assertSegmentedControlFit(controlSnapshot);
|
|
184
|
-
|
|
336
|
+
assertCollectionItemControl(controlSnapshot);
|
|
337
|
+
|
|
338
|
+
const {
|
|
339
|
+
applicability: authoredApplicability,
|
|
340
|
+
visibleWhen,
|
|
341
|
+
...controlWithoutApplicability
|
|
342
|
+
} = controlSnapshot;
|
|
343
|
+
const applicability = resolveToolcraftControlApplicability({
|
|
344
|
+
applicability: authoredApplicability,
|
|
345
|
+
visibleWhen,
|
|
346
|
+
});
|
|
347
|
+
|
|
185
348
|
return normalizeFileDropControlSchema(
|
|
186
|
-
normalizeSliderControlSchema(
|
|
349
|
+
normalizeSliderControlSchema({
|
|
350
|
+
...controlWithoutApplicability,
|
|
351
|
+
applicability,
|
|
352
|
+
}),
|
|
187
353
|
);
|
|
188
354
|
}
|
|
189
355
|
|
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
import type {
|
|
8
8
|
ResolvedToolcraftControlSchema,
|
|
9
9
|
ResolvedToolcraftControlSectionSchema,
|
|
10
|
+
ResolvedToolcraftNonModelControlSchema,
|
|
10
11
|
ToolcraftControlSchema,
|
|
11
|
-
ToolcraftNonModelControlSchema,
|
|
12
12
|
} from "./types";
|
|
13
13
|
|
|
14
14
|
type ToolcraftControlActionSchema = NonNullable<
|
|
15
15
|
ToolcraftControlSchema["actions"]
|
|
16
16
|
>[number];
|
|
17
17
|
|
|
18
|
-
type ToolcraftPanelActionsControl =
|
|
18
|
+
type ToolcraftPanelActionsControl = ResolvedToolcraftNonModelControlSchema & {
|
|
19
19
|
type: "panelActions";
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -140,7 +140,7 @@ describe("Toolcraft control section identity", () => {
|
|
|
140
140
|
)?.id,
|
|
141
141
|
).toBe(legacyId);
|
|
142
142
|
expect(
|
|
143
|
-
defineToolcraft(resolved as ToolcraftAppSchema).panels.controls?.sections.find(
|
|
143
|
+
defineToolcraft(resolved as unknown as ToolcraftAppSchema).panels.controls?.sections.find(
|
|
144
144
|
(section) => section.id.startsWith("legacy."),
|
|
145
145
|
)?.id,
|
|
146
146
|
).toBe(legacyId);
|
|
@@ -9,14 +9,16 @@ import {
|
|
|
9
9
|
registerToolcraftInternalControlSection,
|
|
10
10
|
} from "./controls-panel-section-id";
|
|
11
11
|
import { withImplicitStandaloneSectionTitle } from "./controls-panel-section-titles";
|
|
12
|
+
import {
|
|
13
|
+
getToolcraftApplicabilityTargets,
|
|
14
|
+
} from "./control-applicability";
|
|
12
15
|
import type {
|
|
13
16
|
ResolvedToolcraftControlSchema,
|
|
14
17
|
ResolvedToolcraftControlSectionSchema,
|
|
15
|
-
ToolcraftControlSchema,
|
|
16
18
|
} from "./types";
|
|
17
19
|
|
|
18
20
|
function getControlDefaultSectionLayout(
|
|
19
|
-
control:
|
|
21
|
+
control: ResolvedToolcraftControlSchema,
|
|
20
22
|
): "grouped" | "standalone" {
|
|
21
23
|
const contract = (
|
|
22
24
|
TOOLCRAFT_COMPONENT_CONTRACTS as Record<
|
|
@@ -31,12 +33,15 @@ function getControlDefaultSectionLayout(
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
function isControlGatedBySameSectionControl(
|
|
34
|
-
control:
|
|
35
|
-
entries: readonly [string,
|
|
36
|
+
control: ResolvedToolcraftControlSchema,
|
|
37
|
+
entries: readonly [string, ResolvedToolcraftControlSchema][],
|
|
36
38
|
): boolean {
|
|
37
|
-
const gateTargets = [
|
|
38
|
-
(
|
|
39
|
-
|
|
39
|
+
const gateTargets = [
|
|
40
|
+
...getToolcraftApplicabilityTargets(
|
|
41
|
+
control.applicability,
|
|
42
|
+
),
|
|
43
|
+
...(control.disabledWhen ? [control.disabledWhen.target] : []),
|
|
44
|
+
];
|
|
40
45
|
|
|
41
46
|
if (gateTargets.length === 0) {
|
|
42
47
|
return false;
|
|
@@ -49,8 +54,8 @@ function isControlGatedBySameSectionControl(
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
function getControlSectionLayout(
|
|
52
|
-
control:
|
|
53
|
-
entries: readonly [string,
|
|
57
|
+
control: ResolvedToolcraftControlSchema,
|
|
58
|
+
entries: readonly [string, ResolvedToolcraftControlSchema][],
|
|
54
59
|
): "grouped" | "standalone" {
|
|
55
60
|
if (isControlGatedBySameSectionControl(control, entries)) {
|
|
56
61
|
return "grouped";
|
|
@@ -13,6 +13,65 @@ const opacityControl = {
|
|
|
13
13
|
} as const;
|
|
14
14
|
|
|
15
15
|
describe("Toolcraft control section identity roundtrips", () => {
|
|
16
|
+
it("preserves explicit, legacy, and implicit applicability origins", () => {
|
|
17
|
+
const resolved = defineToolcraft({
|
|
18
|
+
canvas: { enabled: true },
|
|
19
|
+
panels: {
|
|
20
|
+
controls: {
|
|
21
|
+
sections: [
|
|
22
|
+
{
|
|
23
|
+
controls: {
|
|
24
|
+
explicit: {
|
|
25
|
+
applicability: { mode: "always" },
|
|
26
|
+
target: "appearance.explicit",
|
|
27
|
+
type: "switch",
|
|
28
|
+
},
|
|
29
|
+
implicit: {
|
|
30
|
+
target: "appearance.implicit",
|
|
31
|
+
type: "switch",
|
|
32
|
+
},
|
|
33
|
+
legacy: {
|
|
34
|
+
target: "appearance.legacy",
|
|
35
|
+
type: "slider",
|
|
36
|
+
visibleWhen: {
|
|
37
|
+
equals: true,
|
|
38
|
+
target: "appearance.explicit",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
id: "appearance",
|
|
43
|
+
title: "Appearance",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
title: "Controls",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const roundTripped = defineToolcraft(
|
|
51
|
+
resolved as unknown as ToolcraftAppSchema,
|
|
52
|
+
);
|
|
53
|
+
const readApplicability = (schema: typeof resolved) =>
|
|
54
|
+
Object.fromEntries(
|
|
55
|
+
schema.panels.controls?.sections
|
|
56
|
+
.filter((section) => section.id.startsWith("appearance"))
|
|
57
|
+
.flatMap((section) => Object.entries(section.controls))
|
|
58
|
+
.map(([id, control]) => [id, control.applicability]) ?? [],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(readApplicability(resolved)).toEqual({
|
|
62
|
+
explicit: { mode: "always", origin: "explicit" },
|
|
63
|
+
implicit: { mode: "always", origin: "implicit" },
|
|
64
|
+
legacy: {
|
|
65
|
+
all: [{ equals: true, target: "appearance.explicit" }],
|
|
66
|
+
mode: "conditional",
|
|
67
|
+
origin: "legacy",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
expect(readApplicability(roundTripped)).toEqual(
|
|
71
|
+
readApplicability(resolved),
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
16
75
|
it("round-trips resolved legacy and runtime section ids without duplicating runtime sections", () => {
|
|
17
76
|
const resolved = defineToolcraft({
|
|
18
77
|
canvas: { enabled: true },
|
|
@@ -38,7 +97,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
38
97
|
},
|
|
39
98
|
},
|
|
40
99
|
});
|
|
41
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
100
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
42
101
|
const resolvedIds = resolved.panels.controls?.sections.map((section) => section.id);
|
|
43
102
|
const roundTrippedIds = roundTripped.panels.controls?.sections.map(
|
|
44
103
|
(section) => section.id,
|
|
@@ -101,7 +160,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
101
160
|
const productIds = resolved.panels.controls?.sections
|
|
102
161
|
.map((section) => section.id)
|
|
103
162
|
.filter((id) => !id.startsWith("runtime."));
|
|
104
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
163
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
105
164
|
const roundTrippedProductIds = roundTripped.panels.controls?.sections
|
|
106
165
|
.map((section) => section.id)
|
|
107
166
|
.filter((id) => !id.startsWith("runtime."));
|
|
@@ -147,7 +206,7 @@ describe("Toolcraft control section identity roundtrips", () => {
|
|
|
147
206
|
productIds?.every((id) => (id.match(/\.part-/gu) ?? []).length === 2),
|
|
148
207
|
).toBe(true);
|
|
149
208
|
|
|
150
|
-
const roundTripped = defineToolcraft(resolved as ToolcraftAppSchema);
|
|
209
|
+
const roundTripped = defineToolcraft(resolved as unknown as ToolcraftAppSchema);
|
|
151
210
|
const roundTrippedProductIds = roundTripped.panels.controls?.sections
|
|
152
211
|
.map((section) => section.id)
|
|
153
212
|
.filter((id) => !id.startsWith("runtime."));
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { defineToolcraft } from "./define-toolcraft";
|
|
4
|
+
import type { ToolcraftControlSchema } from "./types";
|
|
5
|
+
|
|
6
|
+
function createSchema(control: ToolcraftControlSchema) {
|
|
7
|
+
return {
|
|
8
|
+
canvas: { enabled: false },
|
|
9
|
+
panels: {
|
|
10
|
+
controls: {
|
|
11
|
+
sections: [{ controls: { layers: control }, title: "Layers" }],
|
|
12
|
+
title: "Controls",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function compoundCollection(
|
|
19
|
+
overrides: Record<string, unknown> = {},
|
|
20
|
+
): ToolcraftControlSchema {
|
|
21
|
+
return {
|
|
22
|
+
defaultValue: [{ invert: false, strength: 0.4 }],
|
|
23
|
+
itemControls: {
|
|
24
|
+
strength: {
|
|
25
|
+
defaultValue: 0.5,
|
|
26
|
+
label: "Strength",
|
|
27
|
+
max: 1,
|
|
28
|
+
min: 0,
|
|
29
|
+
step: 0.01,
|
|
30
|
+
type: "slider",
|
|
31
|
+
},
|
|
32
|
+
invert: {
|
|
33
|
+
defaultValue: false,
|
|
34
|
+
label: "Invert",
|
|
35
|
+
type: "switch",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
itemLabel: "Layer",
|
|
39
|
+
minItems: 1,
|
|
40
|
+
target: "surface.layers",
|
|
41
|
+
type: "collectionActions",
|
|
42
|
+
...overrides,
|
|
43
|
+
} as ToolcraftControlSchema;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("defineToolcraft compound collectionActions items", () => {
|
|
47
|
+
it("accepts one logical record made from several built-in fields", () => {
|
|
48
|
+
const app = defineToolcraft(createSchema(compoundCollection()));
|
|
49
|
+
const control = app.panels.controls?.sections.find(
|
|
50
|
+
(section) => section.title === "Layers",
|
|
51
|
+
)?.controls.layers;
|
|
52
|
+
|
|
53
|
+
expect(Object.keys(control?.itemControls ?? {})).toEqual([
|
|
54
|
+
"strength",
|
|
55
|
+
"invert",
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("rejects competing scalar and record templates", () => {
|
|
60
|
+
expect(() =>
|
|
61
|
+
defineToolcraft(
|
|
62
|
+
createSchema(
|
|
63
|
+
compoundCollection({
|
|
64
|
+
itemControl: { defaultValue: 0, type: "slider" },
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
).toThrow(/collection-item-template.*itemControl.*itemControls/u);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("requires at least two fields in a compound collection item", () => {
|
|
72
|
+
expect(() =>
|
|
73
|
+
defineToolcraft(
|
|
74
|
+
createSchema(
|
|
75
|
+
compoundCollection({
|
|
76
|
+
itemControls: {
|
|
77
|
+
strength: { defaultValue: 0.5, type: "slider" },
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
).toThrow(/collection-item-controls.*at least two/u);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("requires one default source per compound field", () => {
|
|
86
|
+
expect(() =>
|
|
87
|
+
defineToolcraft(
|
|
88
|
+
createSchema(
|
|
89
|
+
compoundCollection({
|
|
90
|
+
itemControls: {
|
|
91
|
+
invert: { defaultValue: false, type: "switch" },
|
|
92
|
+
strength: { type: "slider" },
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
).toThrow(/itemControls\.strength.*defaultValue/u);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("rejects a competing itemDefaultValue", () => {
|
|
101
|
+
expect(() =>
|
|
102
|
+
defineToolcraft(
|
|
103
|
+
createSchema(
|
|
104
|
+
compoundCollection({
|
|
105
|
+
itemDefaultValue: { invert: true, strength: 1 },
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
).toThrow(/collection-item-default.*itemDefaultValue/u);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("rejects non-record initial compound items", () => {
|
|
113
|
+
expect(() =>
|
|
114
|
+
defineToolcraft(
|
|
115
|
+
createSchema(compoundCollection({ defaultValue: [new Date()] })),
|
|
116
|
+
),
|
|
117
|
+
).toThrow(/collection-item-value.*plain record/u);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps sourceCollection single-field only", () => {
|
|
121
|
+
expect(() =>
|
|
122
|
+
defineToolcraft(
|
|
123
|
+
createSchema(compoundCollection({ type: "sourceCollection" })),
|
|
124
|
+
),
|
|
125
|
+
).toThrow(/collection-item-controls.*collectionActions.*fileDrop/u);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -4,7 +4,7 @@ import { defineToolcraft } from "./define-toolcraft";
|
|
|
4
4
|
import { getProductSections } from "./define-toolcraft-test-utils";
|
|
5
5
|
|
|
6
6
|
describe("defineToolcraft control section semantics", () => {
|
|
7
|
-
it("preserves
|
|
7
|
+
it("preserves section visibility and resolves legacy control visibility", () => {
|
|
8
8
|
const app = defineToolcraft({
|
|
9
9
|
canvas: { enabled: true },
|
|
10
10
|
panels: {
|
|
@@ -38,9 +38,15 @@ describe("defineToolcraft control section semantics", () => {
|
|
|
38
38
|
equals: "co-brand",
|
|
39
39
|
target: "cover.templateId",
|
|
40
40
|
});
|
|
41
|
-
expect(section?.controls.partnerLogo?.
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
expect(section?.controls.partnerLogo?.applicability).toEqual({
|
|
42
|
+
all: [
|
|
43
|
+
{
|
|
44
|
+
equals: "logo",
|
|
45
|
+
target: "coBrand.identityMode",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
mode: "conditional",
|
|
49
|
+
origin: "legacy",
|
|
44
50
|
});
|
|
45
51
|
});
|
|
46
52
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { defineToolcraft } from "./define-toolcraft";
|
|
4
|
+
import type { ToolcraftControlSchema } from "./types";
|
|
5
|
+
|
|
6
|
+
function createAppSchema(control: ToolcraftControlSchema) {
|
|
7
|
+
return {
|
|
8
|
+
canvas: { enabled: false },
|
|
9
|
+
panels: {
|
|
10
|
+
controls: {
|
|
11
|
+
sections: [
|
|
12
|
+
{
|
|
13
|
+
controls: { bumpMaps: control },
|
|
14
|
+
title: "Bump Maps",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
title: "Controls",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createFileDrop(
|
|
24
|
+
overrides: Record<string, unknown> = {},
|
|
25
|
+
): ToolcraftControlSchema {
|
|
26
|
+
return {
|
|
27
|
+
assetKind: "file",
|
|
28
|
+
defaultValue: [],
|
|
29
|
+
itemControls: {
|
|
30
|
+
depth: { defaultValue: 0.2, max: 1, min: 0, type: "slider" },
|
|
31
|
+
levels: {
|
|
32
|
+
defaultValue: [0, 1],
|
|
33
|
+
max: 1,
|
|
34
|
+
min: 0,
|
|
35
|
+
step: 0.01,
|
|
36
|
+
type: "rangeSlider",
|
|
37
|
+
},
|
|
38
|
+
invert: { defaultValue: false, type: "switch" },
|
|
39
|
+
offset: {
|
|
40
|
+
coordinateMode: "cartesian",
|
|
41
|
+
defaultValue: { x: "0", y: "0" },
|
|
42
|
+
type: "vector",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
multiple: true,
|
|
46
|
+
target: "media.bumpMaps",
|
|
47
|
+
type: "fileDrop",
|
|
48
|
+
variant: "collection-actions",
|
|
49
|
+
...overrides,
|
|
50
|
+
} as ToolcraftControlSchema;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("defineToolcraft fileDrop item controls", () => {
|
|
54
|
+
it("accepts built-in settings owned by each attached file", () => {
|
|
55
|
+
const app = defineToolcraft(createAppSchema(createFileDrop()));
|
|
56
|
+
const control = app.panels.controls?.sections.find(
|
|
57
|
+
(section) => section.title === "Bump Maps",
|
|
58
|
+
)?.controls.bumpMaps;
|
|
59
|
+
|
|
60
|
+
expect(control?.itemControls?.depth?.defaultValue).toBe(0.2);
|
|
61
|
+
expect(control?.itemControls?.levels?.type).toBe("rangeSlider");
|
|
62
|
+
expect(control?.itemControls?.offset?.type).toBe("vector");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("requires the file collection-actions variant", () => {
|
|
66
|
+
expect(() =>
|
|
67
|
+
defineToolcraft(
|
|
68
|
+
createAppSchema(createFileDrop({ variant: undefined })),
|
|
69
|
+
),
|
|
70
|
+
).toThrow(/collection-item-controls.*collectionActions.*fileDrop/u);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("requires every nested setting to declare a reset default", () => {
|
|
74
|
+
expect(() =>
|
|
75
|
+
defineToolcraft(
|
|
76
|
+
createAppSchema(
|
|
77
|
+
createFileDrop({ itemControls: { depth: { type: "slider" } } }),
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
).toThrow(/itemControls\.depth.*defaultValue/u);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("rejects an empty nested settings declaration", () => {
|
|
84
|
+
expect(() =>
|
|
85
|
+
defineToolcraft(createAppSchema(createFileDrop({ itemControls: {} }))),
|
|
86
|
+
).toThrow(/itemControls require at least one built-in field/u);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("rejects itemDefaultValue beside per-file field defaults", () => {
|
|
90
|
+
expect(() =>
|
|
91
|
+
defineToolcraft(
|
|
92
|
+
createAppSchema(
|
|
93
|
+
createFileDrop({ itemDefaultValue: { depth: 1 } }),
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
).toThrow(/collection-item-default.*itemDefaultValue/u);
|
|
97
|
+
});
|
|
98
|
+
});
|