@pixel-point/toolcraft 0.0.7 → 0.0.9
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.mjs +34 -5
- package/src/generate.test.mjs +12 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +185 -23
- package/templates/runtime/contracts/component-contracts.ts +91 -36
- package/templates/runtime/contracts/decision-contracts.test.ts +5 -0
- package/templates/runtime/contracts/decision-contracts.ts +4 -4
- package/templates/runtime/export/export.test.ts +31 -0
- package/templates/runtime/export/export.ts +41 -0
- package/templates/runtime/react/canvas-shell.test.tsx +77 -1
- package/templates/runtime/react/canvas-shell.tsx +178 -23
- package/templates/runtime/react/control-conditions.ts +166 -0
- package/templates/runtime/react/controls-panel-filedrop-reorder.test.tsx +176 -0
- package/templates/runtime/react/controls-panel.test.tsx +774 -17
- package/templates/runtime/react/controls-panel.tsx +155 -8
- package/templates/runtime/react/media-file.ts +19 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +46 -1
- package/templates/runtime/schema/define-toolcraft.ts +29 -3
- package/templates/runtime/schema/types.ts +7 -0
- package/templates/runtime/state/reducer.test.ts +304 -0
- package/templates/runtime/state/reducer.ts +148 -9
- package/templates/runtime/state/types.ts +10 -2
- package/templates/runtime/testing/performance.test.ts +1424 -56
- package/templates/runtime/testing/performance.ts +710 -43
- package/templates/starter/AGENTS.md +10 -9
- package/templates/starter/docs/toolcraft/README.md +1 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +16 -8
- package/templates/starter/docs/toolcraft/assembly-workflow.md +13 -7
- package/templates/starter/docs/toolcraft/component-rules.md +46 -16
- package/templates/starter/docs/toolcraft/custom-controls.md +8 -4
- package/templates/starter/docs/toolcraft/performance.md +54 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +48 -19
- package/templates/starter/docs/toolcraft/workflow.md +2 -2
- package/templates/starter/e2e/app-performance.spec.ts +136 -3
- package/templates/starter/e2e/performance-helpers.ts +197 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +5 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +39 -4
- package/templates/starter/scripts/toolcraft-port.mjs +102 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +60 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +1288 -105
- package/templates/starter/src/app/starter-acceptance.ts +740 -24
- package/templates/starter/src/app/starter-performance.test.ts +66 -5
- package/templates/ui/components/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +10 -4
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +340 -44
- package/templates/ui/components/controls/file-drop/index.ts +1 -1
- package/templates/ui/components/controls/index.ts +3 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/select/select-control.tsx +9 -4
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +84 -8
- package/templates/ui/components/panel/panel-section.tsx +82 -6
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getToolcraftControlOrderTargets,
|
|
14
14
|
type ToolcraftComponentAcceptance,
|
|
15
15
|
starterAcceptance,
|
|
16
|
+
starterControlSectionInventory,
|
|
16
17
|
starterProductReadiness,
|
|
17
18
|
starterTransferMode,
|
|
18
19
|
validateToolcraftAcceptanceCoverage,
|
|
@@ -538,7 +539,7 @@ function makeBackgroundSection() {
|
|
|
538
539
|
includeBackground: {
|
|
539
540
|
defaultValue: true,
|
|
540
541
|
description:
|
|
541
|
-
"Controls PNG background
|
|
542
|
+
"Controls preview and PNG background visibility while video keeps the background.",
|
|
542
543
|
label: "Include",
|
|
543
544
|
target: "export.includeBackground",
|
|
544
545
|
type: "switch",
|
|
@@ -734,13 +735,6 @@ function getProductRuntimeImplementationSource(): string {
|
|
|
734
735
|
)}`;
|
|
735
736
|
}
|
|
736
737
|
|
|
737
|
-
function removePngExportCanvasHelperCalls(source: string): string {
|
|
738
|
-
return source.replace(
|
|
739
|
-
/\bcreateToolcraftPngExportCanvas\s*\(\s*\{[\s\S]*?\}\s*\)/g,
|
|
740
|
-
"createToolcraftPngExportCanvas({})",
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
738
|
function getSchemaBackgroundControlTargets(
|
|
745
739
|
controlTypes: ReadonlySet<string>,
|
|
746
740
|
): string[] {
|
|
@@ -813,6 +807,121 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
813
807
|
expect(validateToolcraftAcceptanceCoverage(starterSchema, starterAcceptance)).toEqual([]);
|
|
814
808
|
});
|
|
815
809
|
|
|
810
|
+
it("requires generated product apps to publish a control section inventory", () => {
|
|
811
|
+
if (!schemaHasProductSurface()) {
|
|
812
|
+
expect(starterControlSectionInventory).toEqual([]);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
expect(
|
|
817
|
+
starterControlSectionInventory.length,
|
|
818
|
+
"Product apps must export starterControlSectionInventory so section grouping decisions are machine-checkable.",
|
|
819
|
+
).toBeGreaterThan(0);
|
|
820
|
+
expect(
|
|
821
|
+
validateToolcraftAcceptanceCoverage(
|
|
822
|
+
starterSchema,
|
|
823
|
+
starterAcceptance,
|
|
824
|
+
starterTransferMode,
|
|
825
|
+
starterControlSectionInventory,
|
|
826
|
+
),
|
|
827
|
+
).toEqual([]);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it("requires fileDrop acceptance to prove upload, clear, and reset lifecycle", () => {
|
|
831
|
+
const fileDropSchema = defineToolcraft({
|
|
832
|
+
canvas: { enabled: true },
|
|
833
|
+
panels: {
|
|
834
|
+
controls: {
|
|
835
|
+
sections: [
|
|
836
|
+
{
|
|
837
|
+
controls: {
|
|
838
|
+
source: {
|
|
839
|
+
defaultValue: null,
|
|
840
|
+
label: "Source image",
|
|
841
|
+
target: "media.source",
|
|
842
|
+
type: "fileDrop",
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
title: "Source",
|
|
846
|
+
},
|
|
847
|
+
],
|
|
848
|
+
title: "Controls",
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
expect(
|
|
854
|
+
validateToolcraftAcceptanceCoverage(fileDropSchema, [
|
|
855
|
+
{
|
|
856
|
+
automated: true,
|
|
857
|
+
automatedTestName: "source image upload and clear update media",
|
|
858
|
+
browser: true,
|
|
859
|
+
browserTestName: "browser: source image upload and clear update media",
|
|
860
|
+
componentType: "fileDrop",
|
|
861
|
+
evidence: "media-lifecycle",
|
|
862
|
+
expectedObservable:
|
|
863
|
+
"Uploading a source image changes the media preview and Clear removes it.",
|
|
864
|
+
fixture: "source image fixture",
|
|
865
|
+
id: "media.source",
|
|
866
|
+
kind: "control",
|
|
867
|
+
target: "media.source",
|
|
868
|
+
userAction: "Upload a source image, then clear the preview.",
|
|
869
|
+
},
|
|
870
|
+
]),
|
|
871
|
+
).toEqual(
|
|
872
|
+
expect.arrayContaining([
|
|
873
|
+
"Source / source (media.source) fileDrop acceptance must prove upload/import, clear/remove, and section or global reset remove source media.",
|
|
874
|
+
]),
|
|
875
|
+
);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it("accepts fileDrop lifecycle coverage that includes reset", () => {
|
|
879
|
+
const fileDropSchema = defineToolcraft({
|
|
880
|
+
canvas: { enabled: true },
|
|
881
|
+
panels: {
|
|
882
|
+
controls: {
|
|
883
|
+
sections: [
|
|
884
|
+
{
|
|
885
|
+
controls: {
|
|
886
|
+
source: {
|
|
887
|
+
defaultValue: null,
|
|
888
|
+
label: "Source image",
|
|
889
|
+
target: "media.source",
|
|
890
|
+
type: "fileDrop",
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
title: "Source",
|
|
894
|
+
},
|
|
895
|
+
],
|
|
896
|
+
title: "Controls",
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
const errors = validateToolcraftAcceptanceCoverage(fileDropSchema, [
|
|
902
|
+
{
|
|
903
|
+
automated: true,
|
|
904
|
+
automatedTestName: "source image upload clear and reset update media",
|
|
905
|
+
browser: true,
|
|
906
|
+
browserTestName: "browser: source image upload clear and reset update media",
|
|
907
|
+
componentType: "fileDrop",
|
|
908
|
+
evidence: "media-lifecycle",
|
|
909
|
+
expectedObservable:
|
|
910
|
+
"Uploading a source image changes the media preview; Clear, section reset, and global Reset controls remove the source media and restore the empty upload state.",
|
|
911
|
+
fixture: "source image fixture",
|
|
912
|
+
id: "media.source",
|
|
913
|
+
kind: "control",
|
|
914
|
+
target: "media.source",
|
|
915
|
+
userAction:
|
|
916
|
+
"Upload a source image, clear it, upload again, then use section reset and Reset controls.",
|
|
917
|
+
},
|
|
918
|
+
]);
|
|
919
|
+
|
|
920
|
+
expect(
|
|
921
|
+
errors.filter((error) => error.includes("fileDrop acceptance")),
|
|
922
|
+
).toEqual([]);
|
|
923
|
+
});
|
|
924
|
+
|
|
816
925
|
it("rejects fake video duration browser coverage that falls back to expected duration", () => {
|
|
817
926
|
const fakeCoverage = `
|
|
818
927
|
async function getVideoDuration(page, buffer, mimeType) {
|
|
@@ -906,7 +1015,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
906
1015
|
|
|
907
1016
|
expect(validateToolcraftAcceptanceCoverage(fixedOutputSchema, starterAcceptance)).toEqual(
|
|
908
1017
|
expect.arrayContaining([
|
|
909
|
-
'canvas.sizing mode "fixed-output" requires a runtime acceptance entry with canvasSizingCoverage "fixed-output-size" explaining why width and height are intentionally non-editable.
|
|
1018
|
+
'canvas.sizing mode "fixed-output" requires a runtime acceptance entry with canvasSizingCoverage "fixed-output-size" explaining why width and height are intentionally non-editable. Product/output apps must use "editable-output"; user-provided, reference, fixed-format, or base/default sizes belong in canvas.size as editable initial values.',
|
|
910
1019
|
]),
|
|
911
1020
|
);
|
|
912
1021
|
});
|
|
@@ -959,7 +1068,118 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
959
1068
|
]),
|
|
960
1069
|
).toEqual(
|
|
961
1070
|
expect.arrayContaining([
|
|
962
|
-
'canvas.sizing canvasSizingCoverage "fixed-output-size" must explain why the
|
|
1071
|
+
'canvas.sizing canvasSizingCoverage "fixed-output-size" must explain why the output dimensions are intentionally fixed for a non-product/internal fixture, not merely initialized from a default size.',
|
|
1072
|
+
]),
|
|
1073
|
+
);
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it("rejects fixed canvas sizing justified only by a missing reference size editor", () => {
|
|
1077
|
+
const fixedOutputSchema = defineToolcraft({
|
|
1078
|
+
canvas: {
|
|
1079
|
+
enabled: true,
|
|
1080
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
1081
|
+
sizing: { mode: "fixed-output" },
|
|
1082
|
+
},
|
|
1083
|
+
panels: {
|
|
1084
|
+
controls: {
|
|
1085
|
+
sections: [
|
|
1086
|
+
{
|
|
1087
|
+
controls: {
|
|
1088
|
+
prompt: {
|
|
1089
|
+
defaultValue: "Describe the effect",
|
|
1090
|
+
label: "Prompt",
|
|
1091
|
+
orderRole: "input",
|
|
1092
|
+
target: "generation.prompt",
|
|
1093
|
+
type: "text",
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
title: "Generation",
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
title: "Controls",
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
expect(
|
|
1105
|
+
validateToolcraftAcceptanceCoverage(fixedOutputSchema, [
|
|
1106
|
+
...starterAcceptance,
|
|
1107
|
+
{
|
|
1108
|
+
automated: true,
|
|
1109
|
+
automatedTestName: "canvas output remains fixed",
|
|
1110
|
+
browser: true,
|
|
1111
|
+
browserTestName: "browser: canvas output remains fixed",
|
|
1112
|
+
canvasSizingCoverage: "fixed-output-size",
|
|
1113
|
+
componentType: "fixed-output canvas",
|
|
1114
|
+
evidence: "product-output",
|
|
1115
|
+
expectedObservable:
|
|
1116
|
+
"The WebGL output remains fixed at 1920x1080 because the reference app has no user-facing output size editor.",
|
|
1117
|
+
fixture: "default canvas fixture",
|
|
1118
|
+
id: "canvas.sizing",
|
|
1119
|
+
kind: "runtime",
|
|
1120
|
+
userAction: "Open the app.",
|
|
1121
|
+
},
|
|
1122
|
+
]),
|
|
1123
|
+
).toEqual(
|
|
1124
|
+
expect.arrayContaining([
|
|
1125
|
+
expect.stringContaining("cannot be justified by the reference or previous app lacking a size editor"),
|
|
1126
|
+
]),
|
|
1127
|
+
);
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
it("rejects fixed canvas sizing for exportable product apps even when reference dimensions are fixed", () => {
|
|
1131
|
+
const fixedExportSchema = defineToolcraft({
|
|
1132
|
+
canvas: {
|
|
1133
|
+
enabled: true,
|
|
1134
|
+
size: { height: 900, unit: "px", width: 1440 },
|
|
1135
|
+
sizing: { mode: "fixed-output" },
|
|
1136
|
+
},
|
|
1137
|
+
panels: {
|
|
1138
|
+
controls: {
|
|
1139
|
+
sections: [
|
|
1140
|
+
{
|
|
1141
|
+
controls: {
|
|
1142
|
+
outputActions: {
|
|
1143
|
+
actions: [
|
|
1144
|
+
{
|
|
1145
|
+
icon: "export",
|
|
1146
|
+
label: "Export PNG",
|
|
1147
|
+
value: "export.png",
|
|
1148
|
+
},
|
|
1149
|
+
],
|
|
1150
|
+
target: "actions.output",
|
|
1151
|
+
type: "panelActions",
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
title: "Controls",
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
expect(
|
|
1162
|
+
validateToolcraftAcceptanceCoverage(fixedExportSchema, [
|
|
1163
|
+
...starterAcceptance,
|
|
1164
|
+
{
|
|
1165
|
+
automated: true,
|
|
1166
|
+
automatedTestName: "fixed reference size is preserved",
|
|
1167
|
+
browser: true,
|
|
1168
|
+
browserTestName: "browser: fixed reference size is preserved",
|
|
1169
|
+
canvasSizingCoverage: "fixed-output-size",
|
|
1170
|
+
componentType: "fixed-output canvas",
|
|
1171
|
+
evidence: "product-output",
|
|
1172
|
+
expectedObservable:
|
|
1173
|
+
"The exported shader canvas remains reference-defined and fixed at 1440x900.",
|
|
1174
|
+
fixture: "shader reference fixture",
|
|
1175
|
+
id: "canvas.sizing",
|
|
1176
|
+
kind: "runtime",
|
|
1177
|
+
userAction: "Open the app.",
|
|
1178
|
+
},
|
|
1179
|
+
]),
|
|
1180
|
+
).toEqual(
|
|
1181
|
+
expect.arrayContaining([
|
|
1182
|
+
'Product/output apps with export actions must use canvas.sizing mode "editable-output" so Aspect ratio, Canvas width, and Canvas height are always available. Put reference, fixed-format, or user-requested dimensions in canvas.size as the initial value instead of hiding size controls with "fixed-output".',
|
|
963
1183
|
]),
|
|
964
1184
|
);
|
|
965
1185
|
});
|
|
@@ -1629,10 +1849,12 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1629
1849
|
).toEqual([
|
|
1630
1850
|
"Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.checkedBuiltIns contains unknown built-in controls: imaginaryPicker.",
|
|
1631
1851
|
'Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.closestBuiltIn must be one of the checked built-ins or "none".',
|
|
1852
|
+
"Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1853
|
+
"Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.checkedBuiltIns must include actions when the custom control exposes local command buttons such as add, remove, delete, duplicate, sort, normalize, or clear.",
|
|
1632
1854
|
]);
|
|
1633
1855
|
});
|
|
1634
1856
|
|
|
1635
|
-
it("
|
|
1857
|
+
it("requires collection-like custom controls to compare actions and collectionActions", () => {
|
|
1636
1858
|
const schema = defineToolcraft({
|
|
1637
1859
|
canvas: { enabled: true },
|
|
1638
1860
|
panels: {
|
|
@@ -1640,15 +1862,15 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1640
1862
|
sections: [
|
|
1641
1863
|
{
|
|
1642
1864
|
controls: {
|
|
1643
|
-
|
|
1644
|
-
defaultValue: [],
|
|
1645
|
-
label: "
|
|
1646
|
-
orderRole: "
|
|
1647
|
-
target: "
|
|
1648
|
-
type: "
|
|
1865
|
+
maskEditor: {
|
|
1866
|
+
defaultValue: { items: [], selectedId: null },
|
|
1867
|
+
label: "Mask shapes",
|
|
1868
|
+
orderRole: "spatial",
|
|
1869
|
+
target: "masks",
|
|
1870
|
+
type: "maskEditor",
|
|
1649
1871
|
} as never,
|
|
1650
1872
|
},
|
|
1651
|
-
title: "
|
|
1873
|
+
title: "Masks",
|
|
1652
1874
|
},
|
|
1653
1875
|
],
|
|
1654
1876
|
title: "Controls",
|
|
@@ -1660,18 +1882,18 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1660
1882
|
validateToolcraftAcceptanceCoverage(schema, [
|
|
1661
1883
|
{
|
|
1662
1884
|
automated: true,
|
|
1663
|
-
automatedTestName: "
|
|
1885
|
+
automatedTestName: "mask editor changes output",
|
|
1664
1886
|
browser: true,
|
|
1665
|
-
browserTestName: "browser:
|
|
1887
|
+
browserTestName: "browser: mask editor changes output",
|
|
1666
1888
|
builtInFitCheck: {
|
|
1667
|
-
checkedBuiltIns: ["
|
|
1668
|
-
closestBuiltIn: "
|
|
1889
|
+
checkedBuiltIns: ["select", "vector"],
|
|
1890
|
+
closestBuiltIn: "vector",
|
|
1669
1891
|
productObservable:
|
|
1670
|
-
"
|
|
1892
|
+
"Adding, selecting, deleting, dragging, and resizing masks changes the composited output.",
|
|
1671
1893
|
whyInsufficient:
|
|
1672
|
-
"
|
|
1894
|
+
"Vector can edit one point, but this product needs multiple shape commands, selected item state, deletion, drag, and resize handles.",
|
|
1673
1895
|
},
|
|
1674
|
-
componentType: "
|
|
1896
|
+
componentType: "maskEditor",
|
|
1675
1897
|
customControlCoverage: [
|
|
1676
1898
|
"built-in-gap",
|
|
1677
1899
|
"kit-primitives",
|
|
@@ -1681,18 +1903,22 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1681
1903
|
],
|
|
1682
1904
|
evidence: "product-output",
|
|
1683
1905
|
expectedObservable:
|
|
1684
|
-
"
|
|
1685
|
-
fixture: "
|
|
1686
|
-
id: "
|
|
1906
|
+
"Adding rectangle, circle, and triangle masks creates selected editable shapes.",
|
|
1907
|
+
fixture: "uploaded image with masks",
|
|
1908
|
+
id: "masks",
|
|
1687
1909
|
kind: "control",
|
|
1688
|
-
target: "
|
|
1689
|
-
userAction:
|
|
1910
|
+
target: "masks",
|
|
1911
|
+
userAction:
|
|
1912
|
+
"Add each mask shape, select a mask in the list, delete one mask, drag a canvas mask handle, and resize a selected mask.",
|
|
1690
1913
|
},
|
|
1691
1914
|
]),
|
|
1692
|
-
).toEqual([
|
|
1915
|
+
).toEqual([
|
|
1916
|
+
"Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1917
|
+
"Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include actions when the custom control exposes local command buttons such as add, remove, delete, duplicate, sort, normalize, or clear.",
|
|
1918
|
+
]);
|
|
1693
1919
|
});
|
|
1694
1920
|
|
|
1695
|
-
it("
|
|
1921
|
+
it("detects collection-like custom controls from the value model instead of entity names", () => {
|
|
1696
1922
|
const schema = defineToolcraft({
|
|
1697
1923
|
canvas: { enabled: true },
|
|
1698
1924
|
panels: {
|
|
@@ -1700,18 +1926,15 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1700
1926
|
sections: [
|
|
1701
1927
|
{
|
|
1702
1928
|
controls: {
|
|
1703
|
-
|
|
1704
|
-
defaultValue: [
|
|
1705
|
-
label: "
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
type: "rangeSlider",
|
|
1711
|
-
unit: "%",
|
|
1712
|
-
},
|
|
1929
|
+
patternSet: {
|
|
1930
|
+
defaultValue: { entries: [], selectedId: null },
|
|
1931
|
+
label: "Pattern set",
|
|
1932
|
+
orderRole: "style",
|
|
1933
|
+
target: "pattern.set",
|
|
1934
|
+
type: "patternSetEditor",
|
|
1935
|
+
} as never,
|
|
1713
1936
|
},
|
|
1714
|
-
title: "
|
|
1937
|
+
title: "Pattern",
|
|
1715
1938
|
},
|
|
1716
1939
|
],
|
|
1717
1940
|
title: "Controls",
|
|
@@ -1723,29 +1946,41 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1723
1946
|
validateToolcraftAcceptanceCoverage(schema, [
|
|
1724
1947
|
{
|
|
1725
1948
|
automated: true,
|
|
1726
|
-
automatedTestName: "
|
|
1949
|
+
automatedTestName: "pattern set changes output",
|
|
1727
1950
|
browser: true,
|
|
1728
|
-
browserTestName: "browser:
|
|
1729
|
-
|
|
1730
|
-
|
|
1951
|
+
browserTestName: "browser: pattern set changes output",
|
|
1952
|
+
builtInFitCheck: {
|
|
1953
|
+
checkedBuiltIns: ["select", "vector"],
|
|
1954
|
+
closestBuiltIn: "select",
|
|
1955
|
+
productObservable:
|
|
1956
|
+
"Editing the chosen pattern entry changes the rendered pattern output.",
|
|
1957
|
+
whyInsufficient:
|
|
1958
|
+
"Select can choose one preset, but this product needs runtime entry state with selected item editing.",
|
|
1959
|
+
},
|
|
1960
|
+
componentType: "patternSetEditor",
|
|
1961
|
+
customControlCoverage: [
|
|
1962
|
+
"built-in-gap",
|
|
1963
|
+
"kit-primitives",
|
|
1964
|
+
"minimal-ui",
|
|
1965
|
+
"product-output",
|
|
1966
|
+
"runtime-state",
|
|
1967
|
+
],
|
|
1731
1968
|
evidence: "product-output",
|
|
1732
1969
|
expectedObservable:
|
|
1733
|
-
"
|
|
1734
|
-
fixture: "
|
|
1735
|
-
id: "
|
|
1970
|
+
"Editing the pattern set changes the rendered product output.",
|
|
1971
|
+
fixture: "pattern set fixture",
|
|
1972
|
+
id: "pattern.set",
|
|
1736
1973
|
kind: "control",
|
|
1737
|
-
target: "
|
|
1738
|
-
userAction: "
|
|
1974
|
+
target: "pattern.set",
|
|
1975
|
+
userAction: "Edit the selected entry.",
|
|
1739
1976
|
},
|
|
1740
1977
|
]),
|
|
1741
|
-
).toEqual(
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
]),
|
|
1745
|
-
);
|
|
1978
|
+
).toEqual([
|
|
1979
|
+
"Pattern / patternSet (pattern.set) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1980
|
+
]);
|
|
1746
1981
|
});
|
|
1747
1982
|
|
|
1748
|
-
it("rejects
|
|
1983
|
+
it("rejects custom controls justified only by visual chrome", () => {
|
|
1749
1984
|
const schema = defineToolcraft({
|
|
1750
1985
|
canvas: { enabled: true },
|
|
1751
1986
|
panels: {
|
|
@@ -1753,29 +1988,263 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1753
1988
|
sections: [
|
|
1754
1989
|
{
|
|
1755
1990
|
controls: {
|
|
1756
|
-
|
|
1757
|
-
defaultValue:
|
|
1758
|
-
label: "
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
type: "rangeSlider",
|
|
1764
|
-
unit: "%",
|
|
1765
|
-
},
|
|
1766
|
-
speed: {
|
|
1767
|
-
defaultValue: 1,
|
|
1768
|
-
label: "Speed",
|
|
1769
|
-
max: 5,
|
|
1770
|
-
min: 0,
|
|
1771
|
-
step: 0.1,
|
|
1772
|
-
target: "field.speed",
|
|
1773
|
-
type: "slider",
|
|
1774
|
-
unit: "x",
|
|
1775
|
-
},
|
|
1991
|
+
shapeButtons: {
|
|
1992
|
+
defaultValue: "rect",
|
|
1993
|
+
label: "Shape",
|
|
1994
|
+
orderRole: "style",
|
|
1995
|
+
target: "shape.kind",
|
|
1996
|
+
type: "shapeButtons",
|
|
1997
|
+
} as never,
|
|
1776
1998
|
},
|
|
1777
|
-
|
|
1778
|
-
|
|
1999
|
+
title: "Shape",
|
|
2000
|
+
},
|
|
2001
|
+
],
|
|
2002
|
+
title: "Controls",
|
|
2003
|
+
},
|
|
2004
|
+
},
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
expect(
|
|
2008
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
2009
|
+
{
|
|
2010
|
+
automated: true,
|
|
2011
|
+
automatedTestName: "shape buttons change output",
|
|
2012
|
+
browser: true,
|
|
2013
|
+
browserTestName: "browser: shape buttons change output",
|
|
2014
|
+
builtInFitCheck: {
|
|
2015
|
+
checkedBuiltIns: ["actions", "segmented", "select"],
|
|
2016
|
+
closestBuiltIn: "segmented",
|
|
2017
|
+
productObservable:
|
|
2018
|
+
"Choosing a shape changes the rendered product geometry.",
|
|
2019
|
+
whyInsufficient:
|
|
2020
|
+
"The custom control uses icon buttons and a compact visual layout.",
|
|
2021
|
+
},
|
|
2022
|
+
componentType: "shapeButtons",
|
|
2023
|
+
customControlCoverage: [
|
|
2024
|
+
"built-in-gap",
|
|
2025
|
+
"kit-primitives",
|
|
2026
|
+
"minimal-ui",
|
|
2027
|
+
"product-output",
|
|
2028
|
+
"runtime-state",
|
|
2029
|
+
],
|
|
2030
|
+
evidence: "product-output",
|
|
2031
|
+
expectedObservable:
|
|
2032
|
+
"Choosing rectangle, circle, or triangle changes the rendered output.",
|
|
2033
|
+
fixture: "shape fixture",
|
|
2034
|
+
id: "shape.kind",
|
|
2035
|
+
kind: "control",
|
|
2036
|
+
target: "shape.kind",
|
|
2037
|
+
userAction: "Choose each shape icon button.",
|
|
2038
|
+
},
|
|
2039
|
+
]),
|
|
2040
|
+
).toEqual([
|
|
2041
|
+
"Shape / shapeButtons (shape.kind) builtInFitCheck.whyInsufficient cannot justify a custom control only with icons, layout, styling, or custom buttons; name the product interaction or value model that built-ins cannot express.",
|
|
2042
|
+
]);
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
it("does not treat numeric tuple custom values as collection owners", () => {
|
|
2046
|
+
const schema = defineToolcraft({
|
|
2047
|
+
canvas: { enabled: true },
|
|
2048
|
+
panels: {
|
|
2049
|
+
controls: {
|
|
2050
|
+
sections: [
|
|
2051
|
+
{
|
|
2052
|
+
controls: {
|
|
2053
|
+
focalPoint: {
|
|
2054
|
+
defaultValue: [0.5, 0.5],
|
|
2055
|
+
label: "Focal point",
|
|
2056
|
+
orderRole: "spatial",
|
|
2057
|
+
target: "focal.point",
|
|
2058
|
+
type: "focalPointPad",
|
|
2059
|
+
} as never,
|
|
2060
|
+
},
|
|
2061
|
+
title: "Focus",
|
|
2062
|
+
},
|
|
2063
|
+
],
|
|
2064
|
+
title: "Controls",
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
expect(
|
|
2070
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
2071
|
+
{
|
|
2072
|
+
automated: true,
|
|
2073
|
+
automatedTestName: "focal point changes output",
|
|
2074
|
+
browser: true,
|
|
2075
|
+
browserTestName: "browser: focal point changes output",
|
|
2076
|
+
builtInFitCheck: {
|
|
2077
|
+
checkedBuiltIns: ["vector"],
|
|
2078
|
+
closestBuiltIn: "vector",
|
|
2079
|
+
productObservable:
|
|
2080
|
+
"Dragging the focal point changes the rendered focus position.",
|
|
2081
|
+
whyInsufficient:
|
|
2082
|
+
"Vector edits x/y, but this product needs a canvas hit target with validation tied to the rendered focal point.",
|
|
2083
|
+
},
|
|
2084
|
+
componentType: "focalPointPad",
|
|
2085
|
+
customControlCoverage: [
|
|
2086
|
+
"built-in-gap",
|
|
2087
|
+
"kit-primitives",
|
|
2088
|
+
"minimal-ui",
|
|
2089
|
+
"product-output",
|
|
2090
|
+
"runtime-state",
|
|
2091
|
+
],
|
|
2092
|
+
evidence: "product-output",
|
|
2093
|
+
expectedObservable:
|
|
2094
|
+
"Dragging the focal point changes the rendered product output.",
|
|
2095
|
+
fixture: "focus fixture",
|
|
2096
|
+
id: "focal.point",
|
|
2097
|
+
kind: "control",
|
|
2098
|
+
target: "focal.point",
|
|
2099
|
+
userAction: "Drag the focal point.",
|
|
2100
|
+
},
|
|
2101
|
+
]),
|
|
2102
|
+
).toEqual([]);
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
it("accepts custom controls with explicit custom control coverage", () => {
|
|
2106
|
+
const schema = defineToolcraft({
|
|
2107
|
+
canvas: { enabled: true },
|
|
2108
|
+
panels: {
|
|
2109
|
+
controls: {
|
|
2110
|
+
sections: [
|
|
2111
|
+
{
|
|
2112
|
+
controls: {
|
|
2113
|
+
glyphRamp: {
|
|
2114
|
+
defaultValue: [],
|
|
2115
|
+
label: "Glyph ramp",
|
|
2116
|
+
orderRole: "input",
|
|
2117
|
+
target: "glyph.ramp",
|
|
2118
|
+
type: "glyphRamp",
|
|
2119
|
+
} as never,
|
|
2120
|
+
},
|
|
2121
|
+
title: "Glyphs",
|
|
2122
|
+
},
|
|
2123
|
+
],
|
|
2124
|
+
title: "Controls",
|
|
2125
|
+
},
|
|
2126
|
+
},
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
expect(
|
|
2130
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
2131
|
+
{
|
|
2132
|
+
automated: true,
|
|
2133
|
+
automatedTestName: "glyph ramp changes output",
|
|
2134
|
+
browser: true,
|
|
2135
|
+
browserTestName: "browser: glyph ramp changes output",
|
|
2136
|
+
builtInFitCheck: {
|
|
2137
|
+
checkedBuiltIns: ["actions", "collectionActions", "fileDrop", "imagePicker", "select"],
|
|
2138
|
+
closestBuiltIn: "fileDrop",
|
|
2139
|
+
productObservable:
|
|
2140
|
+
"Ordering uploaded glyphs changes the rendered glyph ramp output.",
|
|
2141
|
+
whyInsufficient:
|
|
2142
|
+
"FileDrop imports source files, but it does not provide density ordering, per-glyph preview, reorder, and remove behavior in one runtime value.",
|
|
2143
|
+
},
|
|
2144
|
+
componentType: "glyphRamp",
|
|
2145
|
+
customControlCoverage: [
|
|
2146
|
+
"built-in-gap",
|
|
2147
|
+
"kit-primitives",
|
|
2148
|
+
"minimal-ui",
|
|
2149
|
+
"product-output",
|
|
2150
|
+
"runtime-state",
|
|
2151
|
+
],
|
|
2152
|
+
evidence: "product-output",
|
|
2153
|
+
expectedObservable:
|
|
2154
|
+
"Choosing and ordering glyphs changes the rendered product output.",
|
|
2155
|
+
fixture: "glyph ramp fixture",
|
|
2156
|
+
id: "glyph.ramp",
|
|
2157
|
+
kind: "control",
|
|
2158
|
+
target: "glyph.ramp",
|
|
2159
|
+
userAction: "Upload, reorder, and remove glyphs.",
|
|
2160
|
+
},
|
|
2161
|
+
]),
|
|
2162
|
+
).toEqual([]);
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
it("rejects range slider defaults where lower and upper start equal", () => {
|
|
2166
|
+
const schema = defineToolcraft({
|
|
2167
|
+
canvas: { enabled: true },
|
|
2168
|
+
panels: {
|
|
2169
|
+
controls: {
|
|
2170
|
+
sections: [
|
|
2171
|
+
{
|
|
2172
|
+
controls: {
|
|
2173
|
+
opacityRange: {
|
|
2174
|
+
defaultValue: [0, 0],
|
|
2175
|
+
label: "Opacity",
|
|
2176
|
+
max: 100,
|
|
2177
|
+
min: 0,
|
|
2178
|
+
step: 1,
|
|
2179
|
+
target: "field.opacityRange",
|
|
2180
|
+
type: "rangeSlider",
|
|
2181
|
+
unit: "%",
|
|
2182
|
+
},
|
|
2183
|
+
},
|
|
2184
|
+
title: "Field",
|
|
2185
|
+
},
|
|
2186
|
+
],
|
|
2187
|
+
title: "Controls",
|
|
2188
|
+
},
|
|
2189
|
+
},
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
expect(
|
|
2193
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
2194
|
+
{
|
|
2195
|
+
automated: true,
|
|
2196
|
+
automatedTestName: "opacity range lower and upper change field output",
|
|
2197
|
+
browser: true,
|
|
2198
|
+
browserTestName: "browser: opacity range lower and upper change field output",
|
|
2199
|
+
componentType: "rangeSlider",
|
|
2200
|
+
controlPartCoverage: ["rangeSlider.lower", "rangeSlider.upper"],
|
|
2201
|
+
evidence: "product-output",
|
|
2202
|
+
expectedObservable:
|
|
2203
|
+
"Changing the lower and upper opacity handles changes field alpha output.",
|
|
2204
|
+
fixture: "field opacity range fixture",
|
|
2205
|
+
id: "field.opacityRange",
|
|
2206
|
+
kind: "control",
|
|
2207
|
+
target: "field.opacityRange",
|
|
2208
|
+
userAction: "Drag both Opacity handles.",
|
|
2209
|
+
},
|
|
2210
|
+
]),
|
|
2211
|
+
).toEqual(
|
|
2212
|
+
expect.arrayContaining([
|
|
2213
|
+
"Field / opacityRange (field.opacityRange) rangeSlider defaultValue must start with different lower and upper values so the two-thumb control does not collapse into a single-value slider.",
|
|
2214
|
+
]),
|
|
2215
|
+
);
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
it("rejects inline layout groups that include range sliders", () => {
|
|
2219
|
+
const schema = defineToolcraft({
|
|
2220
|
+
canvas: { enabled: true },
|
|
2221
|
+
panels: {
|
|
2222
|
+
controls: {
|
|
2223
|
+
sections: [
|
|
2224
|
+
{
|
|
2225
|
+
controls: {
|
|
2226
|
+
opacityRange: {
|
|
2227
|
+
defaultValue: [10, 80],
|
|
2228
|
+
label: "Opacity",
|
|
2229
|
+
max: 100,
|
|
2230
|
+
min: 0,
|
|
2231
|
+
step: 1,
|
|
2232
|
+
target: "field.opacityRange",
|
|
2233
|
+
type: "rangeSlider",
|
|
2234
|
+
unit: "%",
|
|
2235
|
+
},
|
|
2236
|
+
speed: {
|
|
2237
|
+
defaultValue: 1,
|
|
2238
|
+
label: "Speed",
|
|
2239
|
+
max: 5,
|
|
2240
|
+
min: 0,
|
|
2241
|
+
step: 0.1,
|
|
2242
|
+
target: "field.speed",
|
|
2243
|
+
type: "slider",
|
|
2244
|
+
},
|
|
2245
|
+
},
|
|
2246
|
+
layoutGroups: [
|
|
2247
|
+
{
|
|
1779
2248
|
columns: 2,
|
|
1780
2249
|
controls: ["opacityRange", "speed"],
|
|
1781
2250
|
layout: "inline",
|
|
@@ -1829,6 +2298,57 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1829
2298
|
);
|
|
1830
2299
|
});
|
|
1831
2300
|
|
|
2301
|
+
it("rejects x units on slider value labels", () => {
|
|
2302
|
+
const schema = defineToolcraft({
|
|
2303
|
+
canvas: { enabled: true },
|
|
2304
|
+
panels: {
|
|
2305
|
+
controls: {
|
|
2306
|
+
sections: [
|
|
2307
|
+
{
|
|
2308
|
+
controls: {
|
|
2309
|
+
strength: {
|
|
2310
|
+
defaultValue: 0.4,
|
|
2311
|
+
label: "Strength",
|
|
2312
|
+
max: 1,
|
|
2313
|
+
min: 0,
|
|
2314
|
+
step: 0.01,
|
|
2315
|
+
target: "glass.strength",
|
|
2316
|
+
type: "slider",
|
|
2317
|
+
unit: "x",
|
|
2318
|
+
},
|
|
2319
|
+
},
|
|
2320
|
+
title: "Glass",
|
|
2321
|
+
},
|
|
2322
|
+
],
|
|
2323
|
+
title: "Controls",
|
|
2324
|
+
},
|
|
2325
|
+
},
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
expect(
|
|
2329
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
2330
|
+
{
|
|
2331
|
+
automated: true,
|
|
2332
|
+
automatedTestName: "strength changes glass output",
|
|
2333
|
+
browser: true,
|
|
2334
|
+
browserTestName: "browser: strength slider changes glass output",
|
|
2335
|
+
componentType: "slider",
|
|
2336
|
+
evidence: "rendered-pixels",
|
|
2337
|
+
expectedObservable: "Changing Strength changes the rendered glass strength.",
|
|
2338
|
+
fixture: "glass strength fixture",
|
|
2339
|
+
id: "glass.strength",
|
|
2340
|
+
kind: "control",
|
|
2341
|
+
target: "glass.strength",
|
|
2342
|
+
userAction: "Drag the Strength slider.",
|
|
2343
|
+
},
|
|
2344
|
+
]),
|
|
2345
|
+
).toEqual(
|
|
2346
|
+
expect.arrayContaining([
|
|
2347
|
+
'Glass / strength (glass.strength) uses unit "x", but Toolcraft slider values do not use x suffixes. Omit unit for scale, multiplier, intensity, opacity, strength, depth, and shader amount values unless a real measurement unit applies.',
|
|
2348
|
+
]),
|
|
2349
|
+
);
|
|
2350
|
+
});
|
|
2351
|
+
|
|
1832
2352
|
it("accepts compound controls only when every semantic value part is declared", () => {
|
|
1833
2353
|
const gradientSchema = defineToolcraft({
|
|
1834
2354
|
canvas: { enabled: true },
|
|
@@ -2334,9 +2854,9 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2334
2854
|
"PNG export must pass includeBackground from runtime state to createToolcraftPngExportCanvas; do not hardcode PNG transparency or background inclusion in schema only.",
|
|
2335
2855
|
).toMatch(/\bcreateToolcraftPngExportCanvas\s*\(\s*\{[\s\S]*\bincludeBackground\s*:/);
|
|
2336
2856
|
expect(
|
|
2337
|
-
|
|
2338
|
-
"
|
|
2339
|
-
).
|
|
2857
|
+
productRuntimeImplementationSource,
|
|
2858
|
+
"Live preview must read include-background state through shouldIncludeToolcraftPreviewBackground(state) so turning Include off hides the product preview background without affecting video export.",
|
|
2859
|
+
).toMatch(/\bshouldIncludeToolcraftPreviewBackground\b/);
|
|
2340
2860
|
expect(
|
|
2341
2861
|
backgroundColorTargets.length,
|
|
2342
2862
|
"Every product app with Export PNG must expose a user-facing background color control.",
|
|
@@ -2346,14 +2866,19 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2346
2866
|
'Every product app with Export PNG must expose export.includeBackground in the required "Background" section as a Switch labeled "Include".',
|
|
2347
2867
|
).toBeGreaterThan(0);
|
|
2348
2868
|
|
|
2349
|
-
for (const target of
|
|
2869
|
+
for (const target of backgroundColorTargets) {
|
|
2350
2870
|
expect(
|
|
2351
2871
|
productRuntimeImplementationSource,
|
|
2352
2872
|
`Runtime renderer/export code must read ${target}; declaring the control in schema is not enough.`,
|
|
2353
2873
|
).toContain(target);
|
|
2354
2874
|
}
|
|
2355
2875
|
|
|
2356
|
-
|
|
2876
|
+
expect(
|
|
2877
|
+
productRuntimeImplementationSource,
|
|
2878
|
+
`Runtime renderer/export code must read ${backgroundToggleTargets.join(", ")} through shouldIncludeToolcraftPreviewBackground(state); declaring the control in schema is not enough.`,
|
|
2879
|
+
).toMatch(/\bshouldIncludeToolcraftPreviewBackground\b|\bexport\.includeBackground\b/);
|
|
2880
|
+
|
|
2881
|
+
const imageExportSection = getSchemaImageExportSection();
|
|
2357
2882
|
const imageFormatControl = getSectionControlByTarget(
|
|
2358
2883
|
imageExportSection,
|
|
2359
2884
|
"export.image.format",
|
|
@@ -2788,9 +3313,9 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2788
3313
|
{
|
|
2789
3314
|
...makeControlAcceptance("export.includeBackground", "switch"),
|
|
2790
3315
|
expectedObservable:
|
|
2791
|
-
"Turning Include
|
|
3316
|
+
"Turning Include off makes PNG output transparent, hides the live preview product background, and video output keeps the product background.",
|
|
2792
3317
|
userAction:
|
|
2793
|
-
"Toggle Include
|
|
3318
|
+
"Toggle Include off, verify preview has no product background, export PNG with alpha, and verify video export keeps the background.",
|
|
2794
3319
|
},
|
|
2795
3320
|
{
|
|
2796
3321
|
actionCoverage: ["export.png"],
|
|
@@ -2818,7 +3343,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2818
3343
|
);
|
|
2819
3344
|
});
|
|
2820
3345
|
|
|
2821
|
-
it("requires include-background acceptance to
|
|
3346
|
+
it("requires include-background acceptance to hide preview background and keep video background", () => {
|
|
2822
3347
|
const schemaWithBackgroundControls = defineToolcraft({
|
|
2823
3348
|
canvas: {
|
|
2824
3349
|
enabled: true,
|
|
@@ -2875,7 +3400,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2875
3400
|
).toEqual(
|
|
2876
3401
|
expect.arrayContaining([
|
|
2877
3402
|
expect.stringContaining(
|
|
2878
|
-
"controls
|
|
3403
|
+
"controls background inclusion and acceptance must prove disabling it makes PNG output transparent, hides the live preview product background, and keeps video output with the product background.",
|
|
2879
3404
|
),
|
|
2880
3405
|
]),
|
|
2881
3406
|
);
|
|
@@ -2933,7 +3458,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2933
3458
|
kind: "control",
|
|
2934
3459
|
target: "actions.output",
|
|
2935
3460
|
userAction:
|
|
2936
|
-
"Toggle Include
|
|
3461
|
+
"Toggle Include off, verify preview has no product background, export PNG with alpha, and verify video export keeps the background.",
|
|
2937
3462
|
},
|
|
2938
3463
|
]),
|
|
2939
3464
|
).toEqual(
|
|
@@ -3529,6 +4054,78 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
3529
4054
|
);
|
|
3530
4055
|
});
|
|
3531
4056
|
|
|
4057
|
+
it("rejects single Actions controls that duplicate their only button label", () => {
|
|
4058
|
+
const schemaWithDuplicateActionLabel = defineToolcraft({
|
|
4059
|
+
canvas: { enabled: true },
|
|
4060
|
+
panels: {
|
|
4061
|
+
controls: {
|
|
4062
|
+
sections: [
|
|
4063
|
+
{
|
|
4064
|
+
controls: {
|
|
4065
|
+
wash: {
|
|
4066
|
+
actions: [{ label: "Wash", value: "wash" }],
|
|
4067
|
+
defaultValue: null,
|
|
4068
|
+
label: "Wash",
|
|
4069
|
+
target: "flow.washSignal",
|
|
4070
|
+
type: "actions",
|
|
4071
|
+
},
|
|
4072
|
+
},
|
|
4073
|
+
title: "Flow",
|
|
4074
|
+
},
|
|
4075
|
+
],
|
|
4076
|
+
title: "Controls",
|
|
4077
|
+
},
|
|
4078
|
+
},
|
|
4079
|
+
});
|
|
4080
|
+
|
|
4081
|
+
expect(
|
|
4082
|
+
validateToolcraftAcceptanceCoverage(schemaWithDuplicateActionLabel, [
|
|
4083
|
+
makeControlAcceptance("flow.washSignal", "actions"),
|
|
4084
|
+
]),
|
|
4085
|
+
).toEqual(
|
|
4086
|
+
expect.arrayContaining([
|
|
4087
|
+
expect.stringContaining(
|
|
4088
|
+
'flow.washSignal) single Actions control label "Wash" duplicates its only button label "Wash". Keep the button as the command and use a short context label such as "Ink wash", "Palette action", or "Current layer".',
|
|
4089
|
+
),
|
|
4090
|
+
]),
|
|
4091
|
+
);
|
|
4092
|
+
});
|
|
4093
|
+
|
|
4094
|
+
it("allows single Actions controls with a concise context label", () => {
|
|
4095
|
+
const schemaWithContextActionLabel = defineToolcraft({
|
|
4096
|
+
canvas: { enabled: true },
|
|
4097
|
+
panels: {
|
|
4098
|
+
controls: {
|
|
4099
|
+
sections: [
|
|
4100
|
+
{
|
|
4101
|
+
controls: {
|
|
4102
|
+
wash: {
|
|
4103
|
+
actions: [{ label: "Wash", value: "wash" }],
|
|
4104
|
+
defaultValue: null,
|
|
4105
|
+
label: "Ink wash",
|
|
4106
|
+
target: "flow.washSignal",
|
|
4107
|
+
type: "actions",
|
|
4108
|
+
},
|
|
4109
|
+
},
|
|
4110
|
+
title: "Flow",
|
|
4111
|
+
},
|
|
4112
|
+
],
|
|
4113
|
+
title: "Controls",
|
|
4114
|
+
},
|
|
4115
|
+
},
|
|
4116
|
+
});
|
|
4117
|
+
|
|
4118
|
+
const errors = validateToolcraftAcceptanceCoverage(schemaWithContextActionLabel, [
|
|
4119
|
+
makeControlAcceptance("flow.washSignal", "actions"),
|
|
4120
|
+
]);
|
|
4121
|
+
|
|
4122
|
+
expect(errors).not.toEqual(
|
|
4123
|
+
expect.arrayContaining([
|
|
4124
|
+
expect.stringContaining("single Actions control label"),
|
|
4125
|
+
]),
|
|
4126
|
+
);
|
|
4127
|
+
});
|
|
4128
|
+
|
|
3532
4129
|
it("allows short Include toggle plus an unlabeled background color in the required row", () => {
|
|
3533
4130
|
const schema = defineToolcraft({
|
|
3534
4131
|
canvas: { enabled: true },
|
|
@@ -3540,7 +4137,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
3540
4137
|
includeBackground: {
|
|
3541
4138
|
defaultValue: true,
|
|
3542
4139
|
description:
|
|
3543
|
-
"Controls PNG background
|
|
4140
|
+
"Controls preview and PNG background visibility while video keeps the background.",
|
|
3544
4141
|
label: "Include",
|
|
3545
4142
|
target: "export.includeBackground",
|
|
3546
4143
|
type: "switch",
|
|
@@ -3598,7 +4195,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
3598
4195
|
},
|
|
3599
4196
|
duration: {
|
|
3600
4197
|
defaultValue: "8",
|
|
3601
|
-
label:
|
|
4198
|
+
label: false,
|
|
3602
4199
|
target: "animation.duration",
|
|
3603
4200
|
type: "text",
|
|
3604
4201
|
},
|
|
@@ -3628,6 +4225,180 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
3628
4225
|
);
|
|
3629
4226
|
});
|
|
3630
4227
|
|
|
4228
|
+
it("rejects CodeTextarea for short single-line text content", () => {
|
|
4229
|
+
const schema = defineToolcraft({
|
|
4230
|
+
canvas: { enabled: true },
|
|
4231
|
+
panels: {
|
|
4232
|
+
controls: {
|
|
4233
|
+
sections: [
|
|
4234
|
+
{
|
|
4235
|
+
controls: {
|
|
4236
|
+
buttonText: {
|
|
4237
|
+
defaultValue: "Glass",
|
|
4238
|
+
label: "Text",
|
|
4239
|
+
target: "button.text",
|
|
4240
|
+
type: "code",
|
|
4241
|
+
},
|
|
4242
|
+
},
|
|
4243
|
+
title: "Button",
|
|
4244
|
+
},
|
|
4245
|
+
],
|
|
4246
|
+
title: "Controls",
|
|
4247
|
+
},
|
|
4248
|
+
},
|
|
4249
|
+
});
|
|
4250
|
+
|
|
4251
|
+
expect(
|
|
4252
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
4253
|
+
makeControlAcceptance("button.text", "code"),
|
|
4254
|
+
]),
|
|
4255
|
+
).toEqual(
|
|
4256
|
+
expect.arrayContaining([
|
|
4257
|
+
expect.stringContaining("uses CodeTextarea for short single-line text"),
|
|
4258
|
+
]),
|
|
4259
|
+
);
|
|
4260
|
+
});
|
|
4261
|
+
|
|
4262
|
+
it("allows CodeTextarea when a short default documents long multiline intent", () => {
|
|
4263
|
+
const schema = defineToolcraft({
|
|
4264
|
+
canvas: { enabled: true },
|
|
4265
|
+
panels: {
|
|
4266
|
+
controls: {
|
|
4267
|
+
sections: [
|
|
4268
|
+
{
|
|
4269
|
+
controls: {
|
|
4270
|
+
prompt: {
|
|
4271
|
+
defaultValue: "Describe the scene",
|
|
4272
|
+
description:
|
|
4273
|
+
"Long multiline prompt content for generated output.",
|
|
4274
|
+
label: "Prompt",
|
|
4275
|
+
target: "generation.prompt",
|
|
4276
|
+
type: "code",
|
|
4277
|
+
},
|
|
4278
|
+
},
|
|
4279
|
+
title: "Generation",
|
|
4280
|
+
},
|
|
4281
|
+
],
|
|
4282
|
+
title: "Controls",
|
|
4283
|
+
},
|
|
4284
|
+
},
|
|
4285
|
+
});
|
|
4286
|
+
|
|
4287
|
+
expect(
|
|
4288
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
4289
|
+
makeControlAcceptance("generation.prompt", "code"),
|
|
4290
|
+
]),
|
|
4291
|
+
).not.toEqual(
|
|
4292
|
+
expect.arrayContaining([
|
|
4293
|
+
expect.stringContaining("uses CodeTextarea for short single-line text"),
|
|
4294
|
+
]),
|
|
4295
|
+
);
|
|
4296
|
+
});
|
|
4297
|
+
|
|
4298
|
+
it("rejects visible parameter labels beside one related toggle", () => {
|
|
4299
|
+
const schema = defineToolcraft({
|
|
4300
|
+
canvas: { enabled: true },
|
|
4301
|
+
panels: {
|
|
4302
|
+
controls: {
|
|
4303
|
+
sections: [
|
|
4304
|
+
{
|
|
4305
|
+
controls: {
|
|
4306
|
+
loop: {
|
|
4307
|
+
defaultValue: true,
|
|
4308
|
+
label: "Loop",
|
|
4309
|
+
target: "animation.loop",
|
|
4310
|
+
type: "switch",
|
|
4311
|
+
},
|
|
4312
|
+
duration: {
|
|
4313
|
+
defaultValue: "8",
|
|
4314
|
+
label: "Duration",
|
|
4315
|
+
target: "animation.duration",
|
|
4316
|
+
type: "text",
|
|
4317
|
+
},
|
|
4318
|
+
},
|
|
4319
|
+
layoutGroups: [
|
|
4320
|
+
{
|
|
4321
|
+
columns: 2,
|
|
4322
|
+
controls: ["loop", "duration"],
|
|
4323
|
+
layout: "inline",
|
|
4324
|
+
},
|
|
4325
|
+
],
|
|
4326
|
+
title: "Playback",
|
|
4327
|
+
},
|
|
4328
|
+
],
|
|
4329
|
+
title: "Controls",
|
|
4330
|
+
},
|
|
4331
|
+
},
|
|
4332
|
+
});
|
|
4333
|
+
|
|
4334
|
+
expect(
|
|
4335
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
4336
|
+
makeControlAcceptance("animation.loop", "switch"),
|
|
4337
|
+
makeControlAcceptance("animation.duration", "text"),
|
|
4338
|
+
]),
|
|
4339
|
+
).toEqual(
|
|
4340
|
+
expect.arrayContaining([
|
|
4341
|
+
expect.stringContaining(
|
|
4342
|
+
'Playback layoutGroups inline row "loop, duration" pairs a toggle with parameter labels duration "Duration". In toggle-plus-parameter rows, the non-toggle parameter must use label false; if that label is needed, stack the controls instead.',
|
|
4343
|
+
),
|
|
4344
|
+
]),
|
|
4345
|
+
);
|
|
4346
|
+
});
|
|
4347
|
+
|
|
4348
|
+
it("rejects segmented controls in inline half-width layout rows", () => {
|
|
4349
|
+
const schema = defineToolcraft({
|
|
4350
|
+
canvas: { enabled: true },
|
|
4351
|
+
panels: {
|
|
4352
|
+
controls: {
|
|
4353
|
+
sections: [
|
|
4354
|
+
{
|
|
4355
|
+
controls: {
|
|
4356
|
+
includeText: {
|
|
4357
|
+
defaultValue: true,
|
|
4358
|
+
label: "Include",
|
|
4359
|
+
target: "text.enabled",
|
|
4360
|
+
type: "switch",
|
|
4361
|
+
},
|
|
4362
|
+
dragTarget: {
|
|
4363
|
+
defaultValue: "glass",
|
|
4364
|
+
label: "Drag",
|
|
4365
|
+
options: [
|
|
4366
|
+
{ label: "Glass", value: "glass" },
|
|
4367
|
+
{ label: "Text", value: "text" },
|
|
4368
|
+
],
|
|
4369
|
+
target: "text.dragTarget",
|
|
4370
|
+
type: "segmented",
|
|
4371
|
+
},
|
|
4372
|
+
},
|
|
4373
|
+
layoutGroups: [
|
|
4374
|
+
{
|
|
4375
|
+
columns: 2,
|
|
4376
|
+
controls: ["includeText", "dragTarget"],
|
|
4377
|
+
layout: "inline",
|
|
4378
|
+
},
|
|
4379
|
+
],
|
|
4380
|
+
title: "Glass Text",
|
|
4381
|
+
},
|
|
4382
|
+
],
|
|
4383
|
+
title: "Controls",
|
|
4384
|
+
},
|
|
4385
|
+
},
|
|
4386
|
+
});
|
|
4387
|
+
|
|
4388
|
+
expect(
|
|
4389
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
4390
|
+
makeControlAcceptance("text.enabled", "switch"),
|
|
4391
|
+
makeControlAcceptance("text.dragTarget", "segmented"),
|
|
4392
|
+
]),
|
|
4393
|
+
).toEqual(
|
|
4394
|
+
expect.arrayContaining([
|
|
4395
|
+
expect.stringContaining(
|
|
4396
|
+
'Glass Text layoutGroups inline row "includeText, dragTarget" includes segmented control dragTarget. Segmented is full-width and must not share a two-column or half-width row; use Select when a finite choice must fit beside another control.',
|
|
4397
|
+
),
|
|
4398
|
+
]),
|
|
4399
|
+
);
|
|
4400
|
+
});
|
|
4401
|
+
|
|
3631
4402
|
it("rejects long visible toggle labels beside one related parameter", () => {
|
|
3632
4403
|
const schema = defineToolcraft({
|
|
3633
4404
|
canvas: { enabled: true },
|
|
@@ -4909,7 +5680,6 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
4909
5680
|
step: 0.1,
|
|
4910
5681
|
target: "animation.speed",
|
|
4911
5682
|
type: "slider",
|
|
4912
|
-
unit: "x",
|
|
4913
5683
|
variant: "continuous",
|
|
4914
5684
|
},
|
|
4915
5685
|
depth: {
|
|
@@ -5802,7 +6572,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
5802
6572
|
]),
|
|
5803
6573
|
).toEqual(
|
|
5804
6574
|
expect.arrayContaining([
|
|
5805
|
-
'Controls for product entity "squares.right" are split across sections: Square 1 (Right), Appearance. Keep controls for the same product entity in one semantic section unless the
|
|
6575
|
+
'Controls for product entity "squares.right" are split across sections: Square 1 (Right), Appearance. Keep controls for the same product entity in one semantic section unless the Control Section Inventory declares workflowStage and splitReason for every split section.',
|
|
5806
6576
|
]),
|
|
5807
6577
|
);
|
|
5808
6578
|
});
|
|
@@ -5858,6 +6628,252 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
5858
6628
|
).toEqual([]);
|
|
5859
6629
|
});
|
|
5860
6630
|
|
|
6631
|
+
it("rejects stale control section inventory entries", () => {
|
|
6632
|
+
const schemaWithInventoryMismatch = defineToolcraft({
|
|
6633
|
+
canvas: { enabled: true },
|
|
6634
|
+
panels: {
|
|
6635
|
+
controls: {
|
|
6636
|
+
sections: [
|
|
6637
|
+
{
|
|
6638
|
+
controls: {
|
|
6639
|
+
kind: {
|
|
6640
|
+
defaultValue: "soft",
|
|
6641
|
+
label: "Kind",
|
|
6642
|
+
options: [
|
|
6643
|
+
{ label: "Soft", value: "soft" },
|
|
6644
|
+
{ label: "Sharp", value: "sharp" },
|
|
6645
|
+
],
|
|
6646
|
+
orderRole: "mode",
|
|
6647
|
+
target: "shape.kind",
|
|
6648
|
+
type: "select",
|
|
6649
|
+
},
|
|
6650
|
+
count: {
|
|
6651
|
+
defaultValue: 12,
|
|
6652
|
+
label: "Count",
|
|
6653
|
+
max: 40,
|
|
6654
|
+
min: 1,
|
|
6655
|
+
orderRole: "detail",
|
|
6656
|
+
target: "shape.count",
|
|
6657
|
+
type: "slider",
|
|
6658
|
+
variant: "continuous",
|
|
6659
|
+
},
|
|
6660
|
+
},
|
|
6661
|
+
title: "Shape",
|
|
6662
|
+
},
|
|
6663
|
+
],
|
|
6664
|
+
title: "Controls",
|
|
6665
|
+
},
|
|
6666
|
+
},
|
|
6667
|
+
});
|
|
6668
|
+
|
|
6669
|
+
expect(
|
|
6670
|
+
validateToolcraftAcceptanceCoverage(
|
|
6671
|
+
schemaWithInventoryMismatch,
|
|
6672
|
+
[
|
|
6673
|
+
makeControlAcceptance("shape.kind", "select"),
|
|
6674
|
+
makeControlAcceptance("shape.count", "slider"),
|
|
6675
|
+
],
|
|
6676
|
+
starterTransferMode,
|
|
6677
|
+
[
|
|
6678
|
+
{
|
|
6679
|
+
entity: "Shape",
|
|
6680
|
+
groupingReason: "Shape setup",
|
|
6681
|
+
targets: ["shape.kind"],
|
|
6682
|
+
title: "Shape",
|
|
6683
|
+
},
|
|
6684
|
+
],
|
|
6685
|
+
),
|
|
6686
|
+
).toEqual(
|
|
6687
|
+
expect.arrayContaining([
|
|
6688
|
+
'Control Section Inventory entry "Shape" must include a concrete groupingReason explaining why these controls belong together.',
|
|
6689
|
+
'Control Section Inventory entry "Shape" is missing rendered target "shape.count". The inventory must cover every product control in the section.',
|
|
6690
|
+
]),
|
|
6691
|
+
);
|
|
6692
|
+
});
|
|
6693
|
+
|
|
6694
|
+
it("allows explicit workflow split evidence for one target entity", () => {
|
|
6695
|
+
const schemaWithWorkflowSplit = defineToolcraft({
|
|
6696
|
+
canvas: { enabled: true },
|
|
6697
|
+
panels: {
|
|
6698
|
+
controls: {
|
|
6699
|
+
sections: [
|
|
6700
|
+
{
|
|
6701
|
+
controls: {
|
|
6702
|
+
size: {
|
|
6703
|
+
defaultValue: 64,
|
|
6704
|
+
label: "Size",
|
|
6705
|
+
max: 128,
|
|
6706
|
+
min: 16,
|
|
6707
|
+
orderRole: "primary",
|
|
6708
|
+
target: "object.shape.size",
|
|
6709
|
+
type: "slider",
|
|
6710
|
+
unit: "px",
|
|
6711
|
+
variant: "continuous",
|
|
6712
|
+
},
|
|
6713
|
+
},
|
|
6714
|
+
title: "Shape Structure",
|
|
6715
|
+
},
|
|
6716
|
+
{
|
|
6717
|
+
controls: {
|
|
6718
|
+
count: {
|
|
6719
|
+
defaultValue: 12,
|
|
6720
|
+
label: "Count",
|
|
6721
|
+
max: 40,
|
|
6722
|
+
min: 1,
|
|
6723
|
+
orderRole: "detail",
|
|
6724
|
+
target: "object.shape.count",
|
|
6725
|
+
type: "slider",
|
|
6726
|
+
variant: "continuous",
|
|
6727
|
+
},
|
|
6728
|
+
},
|
|
6729
|
+
title: "Shape Density",
|
|
6730
|
+
},
|
|
6731
|
+
],
|
|
6732
|
+
title: "Controls",
|
|
6733
|
+
},
|
|
6734
|
+
},
|
|
6735
|
+
});
|
|
6736
|
+
|
|
6737
|
+
expect(
|
|
6738
|
+
validateToolcraftAcceptanceCoverage(
|
|
6739
|
+
schemaWithWorkflowSplit,
|
|
6740
|
+
[
|
|
6741
|
+
makeControlAcceptance("object.shape.size", "slider"),
|
|
6742
|
+
makeControlAcceptance("object.shape.count", "slider"),
|
|
6743
|
+
],
|
|
6744
|
+
starterTransferMode,
|
|
6745
|
+
[
|
|
6746
|
+
{
|
|
6747
|
+
entity: "Object shape",
|
|
6748
|
+
groupingReason: "Structure controls tune the physical footprint of the object.",
|
|
6749
|
+
splitReason:
|
|
6750
|
+
"Structure and density are separate workflow stages in this editor.",
|
|
6751
|
+
targets: ["object.shape.size"],
|
|
6752
|
+
title: "Shape Structure",
|
|
6753
|
+
workflowStage: "structure",
|
|
6754
|
+
},
|
|
6755
|
+
{
|
|
6756
|
+
entity: "Object shape",
|
|
6757
|
+
groupingReason: "Density controls tune how many objects appear after structure is set.",
|
|
6758
|
+
splitReason:
|
|
6759
|
+
"Density is separated from structure because users set count after the shape footprint.",
|
|
6760
|
+
targets: ["object.shape.count"],
|
|
6761
|
+
title: "Shape Density",
|
|
6762
|
+
workflowStage: "density",
|
|
6763
|
+
},
|
|
6764
|
+
],
|
|
6765
|
+
),
|
|
6766
|
+
).toEqual([]);
|
|
6767
|
+
});
|
|
6768
|
+
|
|
6769
|
+
it("rejects mode-gated controls split away from their selector section", () => {
|
|
6770
|
+
const schemaWithSplitModeBranch = defineToolcraft({
|
|
6771
|
+
canvas: { enabled: true },
|
|
6772
|
+
panels: {
|
|
6773
|
+
controls: {
|
|
6774
|
+
sections: [
|
|
6775
|
+
{
|
|
6776
|
+
controls: {
|
|
6777
|
+
sourceMode: {
|
|
6778
|
+
defaultValue: "preset",
|
|
6779
|
+
label: "Source",
|
|
6780
|
+
options: [
|
|
6781
|
+
{ label: "Preset", value: "preset" },
|
|
6782
|
+
{ label: "Image", value: "image" },
|
|
6783
|
+
],
|
|
6784
|
+
orderRole: "mode",
|
|
6785
|
+
target: "source.mode",
|
|
6786
|
+
type: "segmented",
|
|
6787
|
+
},
|
|
6788
|
+
},
|
|
6789
|
+
title: "Source",
|
|
6790
|
+
},
|
|
6791
|
+
{
|
|
6792
|
+
controls: {
|
|
6793
|
+
sourceUpload: {
|
|
6794
|
+
accept: "image/*",
|
|
6795
|
+
assetKind: "image",
|
|
6796
|
+
defaultValue: null,
|
|
6797
|
+
label: "Image",
|
|
6798
|
+
orderRole: "input",
|
|
6799
|
+
target: "source.upload",
|
|
6800
|
+
type: "fileDrop",
|
|
6801
|
+
visibleWhen: { equals: "image", target: "source.mode" },
|
|
6802
|
+
},
|
|
6803
|
+
},
|
|
6804
|
+
title: "Image",
|
|
6805
|
+
},
|
|
6806
|
+
],
|
|
6807
|
+
title: "Controls",
|
|
6808
|
+
},
|
|
6809
|
+
},
|
|
6810
|
+
});
|
|
6811
|
+
|
|
6812
|
+
expect(
|
|
6813
|
+
validateToolcraftAcceptanceCoverage(schemaWithSplitModeBranch, [
|
|
6814
|
+
makeControlAcceptance("source.mode", "segmented"),
|
|
6815
|
+
makeControlAcceptance("source.upload", "fileDrop"),
|
|
6816
|
+
]),
|
|
6817
|
+
).toEqual(
|
|
6818
|
+
expect.arrayContaining([
|
|
6819
|
+
'Image / sourceUpload is gated by visibleWhen target "source.mode" in Source, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use visibleWhen/disabledWhen inside that section instead of splitting branch controls into their own section.',
|
|
6820
|
+
]),
|
|
6821
|
+
);
|
|
6822
|
+
});
|
|
6823
|
+
|
|
6824
|
+
it("rejects selector branch sections even when targets do not share a prefix", () => {
|
|
6825
|
+
const schemaWithSplitOptionBranch = defineToolcraft({
|
|
6826
|
+
canvas: { enabled: true },
|
|
6827
|
+
panels: {
|
|
6828
|
+
controls: {
|
|
6829
|
+
sections: [
|
|
6830
|
+
{
|
|
6831
|
+
controls: {
|
|
6832
|
+
shapeKind: {
|
|
6833
|
+
defaultValue: "generated",
|
|
6834
|
+
label: "Shape",
|
|
6835
|
+
options: [
|
|
6836
|
+
{ label: "Generated", value: "generated" },
|
|
6837
|
+
{ label: "Library", value: "library" },
|
|
6838
|
+
],
|
|
6839
|
+
orderRole: "mode",
|
|
6840
|
+
target: "shape.kind",
|
|
6841
|
+
type: "segmented",
|
|
6842
|
+
},
|
|
6843
|
+
},
|
|
6844
|
+
title: "Shape",
|
|
6845
|
+
},
|
|
6846
|
+
{
|
|
6847
|
+
controls: {
|
|
6848
|
+
libraryAsset: {
|
|
6849
|
+
defaultValue: null,
|
|
6850
|
+
label: "Library",
|
|
6851
|
+
orderRole: "input",
|
|
6852
|
+
target: "asset.upload",
|
|
6853
|
+
type: "fileDrop",
|
|
6854
|
+
visibleWhen: { equals: "library", target: "shape.kind" },
|
|
6855
|
+
},
|
|
6856
|
+
},
|
|
6857
|
+
title: "Library",
|
|
6858
|
+
},
|
|
6859
|
+
],
|
|
6860
|
+
title: "Controls",
|
|
6861
|
+
},
|
|
6862
|
+
},
|
|
6863
|
+
});
|
|
6864
|
+
|
|
6865
|
+
expect(
|
|
6866
|
+
validateToolcraftAcceptanceCoverage(schemaWithSplitOptionBranch, [
|
|
6867
|
+
makeControlAcceptance("shape.kind", "segmented"),
|
|
6868
|
+
makeControlAcceptance("asset.upload", "fileDrop"),
|
|
6869
|
+
]),
|
|
6870
|
+
).toEqual(
|
|
6871
|
+
expect.arrayContaining([
|
|
6872
|
+
'Library / libraryAsset is gated by visibleWhen target "shape.kind" in Shape, but it belongs to the same dependency group. Keep selectors and their dependent controls in one semantic section when they describe one product entity or branch; use visibleWhen/disabledWhen inside that section instead of splitting branch controls into their own section.',
|
|
6873
|
+
]),
|
|
6874
|
+
);
|
|
6875
|
+
});
|
|
6876
|
+
|
|
5861
6877
|
it("rejects splitting FontPicker-owned typography into sibling controls", () => {
|
|
5862
6878
|
const schemaWithSplitTypographyBlock = defineToolcraft({
|
|
5863
6879
|
canvas: { enabled: true },
|
|
@@ -6052,6 +7068,173 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
6052
7068
|
);
|
|
6053
7069
|
});
|
|
6054
7070
|
|
|
7071
|
+
it("rejects visible labels for palette variation color banks", () => {
|
|
7072
|
+
const schemaWithLabeledPaletteBank = defineToolcraft({
|
|
7073
|
+
canvas: { enabled: true },
|
|
7074
|
+
panels: {
|
|
7075
|
+
controls: {
|
|
7076
|
+
sections: [
|
|
7077
|
+
{
|
|
7078
|
+
controls: {
|
|
7079
|
+
accent1: {
|
|
7080
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
7081
|
+
label: "Color 1",
|
|
7082
|
+
target: "palette.accent1",
|
|
7083
|
+
type: "color",
|
|
7084
|
+
},
|
|
7085
|
+
accent2: {
|
|
7086
|
+
defaultValue: { hex: "#FF7A90" },
|
|
7087
|
+
label: "Color 2",
|
|
7088
|
+
target: "palette.accent2",
|
|
7089
|
+
type: "color",
|
|
7090
|
+
},
|
|
7091
|
+
spread: {
|
|
7092
|
+
defaultValue: 34,
|
|
7093
|
+
label: "Spread",
|
|
7094
|
+
max: 100,
|
|
7095
|
+
min: 0,
|
|
7096
|
+
target: "palette.spread",
|
|
7097
|
+
type: "slider",
|
|
7098
|
+
unit: "%",
|
|
7099
|
+
},
|
|
7100
|
+
},
|
|
7101
|
+
title: "Accent Shades",
|
|
7102
|
+
},
|
|
7103
|
+
],
|
|
7104
|
+
title: "Controls",
|
|
7105
|
+
},
|
|
7106
|
+
},
|
|
7107
|
+
});
|
|
7108
|
+
|
|
7109
|
+
expect(
|
|
7110
|
+
validateToolcraftAcceptanceCoverage(schemaWithLabeledPaletteBank, [
|
|
7111
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
7112
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
7113
|
+
makeControlAcceptance("palette.spread", "slider"),
|
|
7114
|
+
]),
|
|
7115
|
+
).toEqual(
|
|
7116
|
+
expect.arrayContaining([
|
|
7117
|
+
'Accent Shades / accent1 uses visible label "Color 1" for a palette variation color. When colors only add variety to one shared palette, set label: false or use collectionActions with unlabeled items. Keep visible labels only when each color edits a distinct user-facing entity such as Fill, Stroke, Background, Connector, or Object color.',
|
|
7118
|
+
'Accent Shades / accent2 uses visible label "Color 2" for a palette variation color. When colors only add variety to one shared palette, set label: false or use collectionActions with unlabeled items. Keep visible labels only when each color edits a distinct user-facing entity such as Fill, Stroke, Background, Connector, or Object color.',
|
|
7119
|
+
]),
|
|
7120
|
+
);
|
|
7121
|
+
});
|
|
7122
|
+
|
|
7123
|
+
it("rejects mixed label visibility inside one palette variation color group", () => {
|
|
7124
|
+
const schemaWithMixedPaletteBankLabels = defineToolcraft({
|
|
7125
|
+
canvas: { enabled: true },
|
|
7126
|
+
panels: {
|
|
7127
|
+
controls: {
|
|
7128
|
+
sections: [
|
|
7129
|
+
{
|
|
7130
|
+
controls: {
|
|
7131
|
+
accent1: {
|
|
7132
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
7133
|
+
label: false,
|
|
7134
|
+
target: "palette.accent1",
|
|
7135
|
+
type: "color",
|
|
7136
|
+
},
|
|
7137
|
+
accent2: {
|
|
7138
|
+
defaultValue: { hex: "#FF7A90" },
|
|
7139
|
+
label: "Color 2",
|
|
7140
|
+
target: "palette.accent2",
|
|
7141
|
+
type: "color",
|
|
7142
|
+
},
|
|
7143
|
+
accent3: {
|
|
7144
|
+
defaultValue: { hex: "#FFD166" },
|
|
7145
|
+
label: false,
|
|
7146
|
+
target: "palette.accent3",
|
|
7147
|
+
type: "color",
|
|
7148
|
+
},
|
|
7149
|
+
},
|
|
7150
|
+
title: "Accent Shades",
|
|
7151
|
+
},
|
|
7152
|
+
],
|
|
7153
|
+
title: "Controls",
|
|
7154
|
+
},
|
|
7155
|
+
},
|
|
7156
|
+
});
|
|
7157
|
+
|
|
7158
|
+
expect(
|
|
7159
|
+
validateToolcraftAcceptanceCoverage(schemaWithMixedPaletteBankLabels, [
|
|
7160
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
7161
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
7162
|
+
makeControlAcceptance("palette.accent3", "color"),
|
|
7163
|
+
]),
|
|
7164
|
+
).toEqual(
|
|
7165
|
+
expect.arrayContaining([
|
|
7166
|
+
"Accent Shades mixes labeled and unlabeled color items in one palette variation group. Decide label visibility for the whole group: omit all per-item labels when colors only add variety, or label every item only when each color has a distinct user-facing role.",
|
|
7167
|
+
'Accent Shades / accent2 uses visible label "Color 2" for a palette variation color. When colors only add variety to one shared palette, set label: false or use collectionActions with unlabeled items. Keep visible labels only when each color edits a distinct user-facing entity such as Fill, Stroke, Background, Connector, or Object color.',
|
|
7168
|
+
]),
|
|
7169
|
+
);
|
|
7170
|
+
});
|
|
7171
|
+
|
|
7172
|
+
it("allows unlabeled palette variation colors and labeled distinct color roles", () => {
|
|
7173
|
+
const schemaWithUsefulColorLabels = defineToolcraft({
|
|
7174
|
+
canvas: { enabled: true },
|
|
7175
|
+
panels: {
|
|
7176
|
+
controls: {
|
|
7177
|
+
sections: [
|
|
7178
|
+
{
|
|
7179
|
+
controls: {
|
|
7180
|
+
accent1: {
|
|
7181
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
7182
|
+
label: false,
|
|
7183
|
+
target: "palette.accent1",
|
|
7184
|
+
type: "color",
|
|
7185
|
+
},
|
|
7186
|
+
accent2: {
|
|
7187
|
+
defaultValue: { hex: "#FF7A90" },
|
|
7188
|
+
label: false,
|
|
7189
|
+
target: "palette.accent2",
|
|
7190
|
+
type: "color",
|
|
7191
|
+
},
|
|
7192
|
+
spread: {
|
|
7193
|
+
defaultValue: 34,
|
|
7194
|
+
label: "Spread",
|
|
7195
|
+
max: 100,
|
|
7196
|
+
min: 0,
|
|
7197
|
+
target: "palette.spread",
|
|
7198
|
+
type: "slider",
|
|
7199
|
+
unit: "%",
|
|
7200
|
+
},
|
|
7201
|
+
},
|
|
7202
|
+
title: "Accent Shades",
|
|
7203
|
+
},
|
|
7204
|
+
{
|
|
7205
|
+
controls: {
|
|
7206
|
+
fill: {
|
|
7207
|
+
defaultValue: { hex: "#FFFFFF" },
|
|
7208
|
+
label: "Fill",
|
|
7209
|
+
target: "object.fill",
|
|
7210
|
+
type: "color",
|
|
7211
|
+
},
|
|
7212
|
+
stroke: {
|
|
7213
|
+
defaultValue: { hex: "#111111" },
|
|
7214
|
+
label: "Stroke",
|
|
7215
|
+
target: "object.stroke",
|
|
7216
|
+
type: "color",
|
|
7217
|
+
},
|
|
7218
|
+
},
|
|
7219
|
+
title: "Object Colors",
|
|
7220
|
+
},
|
|
7221
|
+
],
|
|
7222
|
+
title: "Controls",
|
|
7223
|
+
},
|
|
7224
|
+
},
|
|
7225
|
+
});
|
|
7226
|
+
|
|
7227
|
+
expect(
|
|
7228
|
+
validateToolcraftAcceptanceCoverage(schemaWithUsefulColorLabels, [
|
|
7229
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
7230
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
7231
|
+
makeControlAcceptance("palette.spread", "slider"),
|
|
7232
|
+
makeControlAcceptance("object.fill", "color"),
|
|
7233
|
+
makeControlAcceptance("object.stroke", "color"),
|
|
7234
|
+
]),
|
|
7235
|
+
).toEqual([]);
|
|
7236
|
+
});
|
|
7237
|
+
|
|
6055
7238
|
it("requires mode selectors to appear before dependent controls", () => {
|
|
6056
7239
|
const schemaWithLateModeSelector = {
|
|
6057
7240
|
...starterSchema,
|
|
@@ -6070,15 +7253,15 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
6070
7253
|
type: "slider",
|
|
6071
7254
|
variant: "continuous",
|
|
6072
7255
|
},
|
|
6073
|
-
|
|
7256
|
+
mode: {
|
|
6074
7257
|
defaultValue: "liquid",
|
|
6075
|
-
label: "
|
|
7258
|
+
label: "Mode",
|
|
6076
7259
|
options: [
|
|
6077
7260
|
{ label: "Silk", value: "silk" },
|
|
6078
7261
|
{ label: "Liquid", value: "liquid" },
|
|
6079
7262
|
{ label: "Crystal", value: "crystal" },
|
|
6080
7263
|
],
|
|
6081
|
-
target: "shader.
|
|
7264
|
+
target: "shader.mode",
|
|
6082
7265
|
type: "segmented",
|
|
6083
7266
|
},
|
|
6084
7267
|
},
|
|
@@ -6108,23 +7291,23 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
6108
7291
|
},
|
|
6109
7292
|
{
|
|
6110
7293
|
automated: true,
|
|
6111
|
-
automatedTestName: "
|
|
7294
|
+
automatedTestName: "mode changes rendered output",
|
|
6112
7295
|
browser: true,
|
|
6113
|
-
browserTestName: "browser:
|
|
7296
|
+
browserTestName: "browser: mode selector changes rendered output",
|
|
6114
7297
|
componentType: "segmented",
|
|
6115
7298
|
evidence: "rendered-pixels",
|
|
6116
|
-
expectedObservable: "Changing
|
|
6117
|
-
fixture: "
|
|
6118
|
-
id: "shader.
|
|
7299
|
+
expectedObservable: "Changing Mode switches shader pattern.",
|
|
7300
|
+
fixture: "mode fixture",
|
|
7301
|
+
id: "shader.mode",
|
|
6119
7302
|
kind: "control",
|
|
6120
7303
|
optionCoverage: "each-visible-item",
|
|
6121
|
-
target: "shader.
|
|
6122
|
-
userAction: "Select each
|
|
7304
|
+
target: "shader.mode",
|
|
7305
|
+
userAction: "Select each Mode option.",
|
|
6123
7306
|
},
|
|
6124
7307
|
]),
|
|
6125
7308
|
).toEqual(
|
|
6126
7309
|
expect.arrayContaining([
|
|
6127
|
-
'Volume /
|
|
7310
|
+
'Volume / mode (shader.mode) has orderRole "mode" after depth (shader.depth) with orderRole "strength". Move mode/input/primary controls before dependent strength/detail/advanced controls or split them into an earlier section.',
|
|
6128
7311
|
]),
|
|
6129
7312
|
);
|
|
6130
7313
|
});
|