@pixel-point/toolcraft 0.0.6 → 0.0.8
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.test.mjs +1 -1
- package/templates/runtime/contracts/component-contracts.test.ts +123 -15
- package/templates/runtime/contracts/component-contracts.ts +93 -19
- package/templates/runtime/contracts/decision-contracts.test.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.ts +2 -2
- 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 +590 -9
- package/templates/runtime/react/controls-panel.tsx +472 -10
- package/templates/runtime/react/media-file.ts +19 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +2 -0
- package/templates/runtime/schema/define-toolcraft.ts +11 -3
- package/templates/runtime/schema/types.ts +27 -0
- package/templates/runtime/state/reducer.test.ts +357 -0
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/types.ts +12 -2
- package/templates/runtime/testing/performance.test.ts +1282 -48
- package/templates/runtime/testing/performance.ts +676 -39
- package/templates/starter/AGENTS.md +13 -12
- package/templates/starter/docs/toolcraft/README.md +4 -3
- package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -7
- package/templates/starter/docs/toolcraft/assembly-workflow.md +9 -7
- package/templates/starter/docs/toolcraft/component-rules.md +20 -7
- package/templates/starter/docs/toolcraft/custom-controls.md +9 -5
- package/templates/starter/docs/toolcraft/performance.md +51 -13
- package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +11 -6
- package/templates/starter/docs/toolcraft/workflow.md +7 -10
- package/templates/starter/e2e/app-performance.spec.ts +136 -3
- package/templates/starter/e2e/performance-helpers.ts +197 -0
- package/templates/starter/package.json +4 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +529 -19
- package/templates/starter/src/app/starter-acceptance.ts +269 -12
- package/templates/starter/src/app/starter-performance.test.ts +67 -6
- package/templates/ui/components/controls/actions/actions-control.tsx +3 -5
- package/templates/ui/components/controls/collection-actions/collection-actions-control.tsx +60 -0
- package/templates/ui/components/controls/collection-actions/index.ts +4 -0
- 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/font-picker/font-picker-control.tsx +1 -6
- package/templates/ui/components/controls/index.ts +9 -0
- package/templates/ui/components/panel/panel-section.tsx +53 -1
- package/templates/ui/index.ts +1 -0
|
@@ -538,7 +538,7 @@ function makeBackgroundSection() {
|
|
|
538
538
|
includeBackground: {
|
|
539
539
|
defaultValue: true,
|
|
540
540
|
description:
|
|
541
|
-
"Controls PNG background
|
|
541
|
+
"Controls preview and PNG background visibility while video keeps the background.",
|
|
542
542
|
label: "Include",
|
|
543
543
|
target: "export.includeBackground",
|
|
544
544
|
type: "switch",
|
|
@@ -734,13 +734,6 @@ function getProductRuntimeImplementationSource(): string {
|
|
|
734
734
|
)}`;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
-
function removePngExportCanvasHelperCalls(source: string): string {
|
|
738
|
-
return source.replace(
|
|
739
|
-
/\bcreateToolcraftPngExportCanvas\s*\(\s*\{[\s\S]*?\}\s*\)/g,
|
|
740
|
-
"createToolcraftPngExportCanvas({})",
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
737
|
function getSchemaBackgroundControlTargets(
|
|
745
738
|
controlTypes: ReadonlySet<string>,
|
|
746
739
|
): string[] {
|
|
@@ -813,6 +806,101 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
813
806
|
expect(validateToolcraftAcceptanceCoverage(starterSchema, starterAcceptance)).toEqual([]);
|
|
814
807
|
});
|
|
815
808
|
|
|
809
|
+
it("requires fileDrop acceptance to prove upload, clear, and reset lifecycle", () => {
|
|
810
|
+
const fileDropSchema = defineToolcraft({
|
|
811
|
+
canvas: { enabled: true },
|
|
812
|
+
panels: {
|
|
813
|
+
controls: {
|
|
814
|
+
sections: [
|
|
815
|
+
{
|
|
816
|
+
controls: {
|
|
817
|
+
source: {
|
|
818
|
+
defaultValue: null,
|
|
819
|
+
label: "Source image",
|
|
820
|
+
target: "media.source",
|
|
821
|
+
type: "fileDrop",
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
title: "Source",
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
title: "Controls",
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
expect(
|
|
833
|
+
validateToolcraftAcceptanceCoverage(fileDropSchema, [
|
|
834
|
+
{
|
|
835
|
+
automated: true,
|
|
836
|
+
automatedTestName: "source image upload and clear update media",
|
|
837
|
+
browser: true,
|
|
838
|
+
browserTestName: "browser: source image upload and clear update media",
|
|
839
|
+
componentType: "fileDrop",
|
|
840
|
+
evidence: "media-lifecycle",
|
|
841
|
+
expectedObservable:
|
|
842
|
+
"Uploading a source image changes the media preview and Clear removes it.",
|
|
843
|
+
fixture: "source image fixture",
|
|
844
|
+
id: "media.source",
|
|
845
|
+
kind: "control",
|
|
846
|
+
target: "media.source",
|
|
847
|
+
userAction: "Upload a source image, then clear the preview.",
|
|
848
|
+
},
|
|
849
|
+
]),
|
|
850
|
+
).toEqual(
|
|
851
|
+
expect.arrayContaining([
|
|
852
|
+
"Source / source (media.source) fileDrop acceptance must prove upload/import, clear/remove, and section or global reset remove source media.",
|
|
853
|
+
]),
|
|
854
|
+
);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("accepts fileDrop lifecycle coverage that includes reset", () => {
|
|
858
|
+
const fileDropSchema = defineToolcraft({
|
|
859
|
+
canvas: { enabled: true },
|
|
860
|
+
panels: {
|
|
861
|
+
controls: {
|
|
862
|
+
sections: [
|
|
863
|
+
{
|
|
864
|
+
controls: {
|
|
865
|
+
source: {
|
|
866
|
+
defaultValue: null,
|
|
867
|
+
label: "Source image",
|
|
868
|
+
target: "media.source",
|
|
869
|
+
type: "fileDrop",
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
title: "Source",
|
|
873
|
+
},
|
|
874
|
+
],
|
|
875
|
+
title: "Controls",
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
const errors = validateToolcraftAcceptanceCoverage(fileDropSchema, [
|
|
881
|
+
{
|
|
882
|
+
automated: true,
|
|
883
|
+
automatedTestName: "source image upload clear and reset update media",
|
|
884
|
+
browser: true,
|
|
885
|
+
browserTestName: "browser: source image upload clear and reset update media",
|
|
886
|
+
componentType: "fileDrop",
|
|
887
|
+
evidence: "media-lifecycle",
|
|
888
|
+
expectedObservable:
|
|
889
|
+
"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.",
|
|
890
|
+
fixture: "source image fixture",
|
|
891
|
+
id: "media.source",
|
|
892
|
+
kind: "control",
|
|
893
|
+
target: "media.source",
|
|
894
|
+
userAction:
|
|
895
|
+
"Upload a source image, clear it, upload again, then use section reset and Reset controls.",
|
|
896
|
+
},
|
|
897
|
+
]);
|
|
898
|
+
|
|
899
|
+
expect(
|
|
900
|
+
errors.filter((error) => error.includes("fileDrop acceptance")),
|
|
901
|
+
).toEqual([]);
|
|
902
|
+
});
|
|
903
|
+
|
|
816
904
|
it("rejects fake video duration browser coverage that falls back to expected duration", () => {
|
|
817
905
|
const fakeCoverage = `
|
|
818
906
|
async function getVideoDuration(page, buffer, mimeType) {
|
|
@@ -1629,9 +1717,259 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1629
1717
|
).toEqual([
|
|
1630
1718
|
"Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.checkedBuiltIns contains unknown built-in controls: imaginaryPicker.",
|
|
1631
1719
|
'Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.closestBuiltIn must be one of the checked built-ins or "none".',
|
|
1720
|
+
"Glyphs / glyphRamp (glyph.ramp) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1721
|
+
"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
1722
|
]);
|
|
1633
1723
|
});
|
|
1634
1724
|
|
|
1725
|
+
it("requires collection-like custom controls to compare actions and collectionActions", () => {
|
|
1726
|
+
const schema = defineToolcraft({
|
|
1727
|
+
canvas: { enabled: true },
|
|
1728
|
+
panels: {
|
|
1729
|
+
controls: {
|
|
1730
|
+
sections: [
|
|
1731
|
+
{
|
|
1732
|
+
controls: {
|
|
1733
|
+
maskEditor: {
|
|
1734
|
+
defaultValue: { items: [], selectedId: null },
|
|
1735
|
+
label: "Mask shapes",
|
|
1736
|
+
orderRole: "spatial",
|
|
1737
|
+
target: "masks",
|
|
1738
|
+
type: "maskEditor",
|
|
1739
|
+
} as never,
|
|
1740
|
+
},
|
|
1741
|
+
title: "Masks",
|
|
1742
|
+
},
|
|
1743
|
+
],
|
|
1744
|
+
title: "Controls",
|
|
1745
|
+
},
|
|
1746
|
+
},
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
expect(
|
|
1750
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
1751
|
+
{
|
|
1752
|
+
automated: true,
|
|
1753
|
+
automatedTestName: "mask editor changes output",
|
|
1754
|
+
browser: true,
|
|
1755
|
+
browserTestName: "browser: mask editor changes output",
|
|
1756
|
+
builtInFitCheck: {
|
|
1757
|
+
checkedBuiltIns: ["select", "vector"],
|
|
1758
|
+
closestBuiltIn: "vector",
|
|
1759
|
+
productObservable:
|
|
1760
|
+
"Adding, selecting, deleting, dragging, and resizing masks changes the composited output.",
|
|
1761
|
+
whyInsufficient:
|
|
1762
|
+
"Vector can edit one point, but this product needs multiple shape commands, selected item state, deletion, drag, and resize handles.",
|
|
1763
|
+
},
|
|
1764
|
+
componentType: "maskEditor",
|
|
1765
|
+
customControlCoverage: [
|
|
1766
|
+
"built-in-gap",
|
|
1767
|
+
"kit-primitives",
|
|
1768
|
+
"minimal-ui",
|
|
1769
|
+
"product-output",
|
|
1770
|
+
"runtime-state",
|
|
1771
|
+
],
|
|
1772
|
+
evidence: "product-output",
|
|
1773
|
+
expectedObservable:
|
|
1774
|
+
"Adding rectangle, circle, and triangle masks creates selected editable shapes.",
|
|
1775
|
+
fixture: "uploaded image with masks",
|
|
1776
|
+
id: "masks",
|
|
1777
|
+
kind: "control",
|
|
1778
|
+
target: "masks",
|
|
1779
|
+
userAction:
|
|
1780
|
+
"Add each mask shape, select a mask in the list, delete one mask, drag a canvas mask handle, and resize a selected mask.",
|
|
1781
|
+
},
|
|
1782
|
+
]),
|
|
1783
|
+
).toEqual([
|
|
1784
|
+
"Masks / maskEditor (masks) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1785
|
+
"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.",
|
|
1786
|
+
]);
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
it("detects collection-like custom controls from the value model instead of entity names", () => {
|
|
1790
|
+
const schema = defineToolcraft({
|
|
1791
|
+
canvas: { enabled: true },
|
|
1792
|
+
panels: {
|
|
1793
|
+
controls: {
|
|
1794
|
+
sections: [
|
|
1795
|
+
{
|
|
1796
|
+
controls: {
|
|
1797
|
+
patternSet: {
|
|
1798
|
+
defaultValue: { entries: [], selectedId: null },
|
|
1799
|
+
label: "Pattern set",
|
|
1800
|
+
orderRole: "style",
|
|
1801
|
+
target: "pattern.set",
|
|
1802
|
+
type: "patternSetEditor",
|
|
1803
|
+
} as never,
|
|
1804
|
+
},
|
|
1805
|
+
title: "Pattern",
|
|
1806
|
+
},
|
|
1807
|
+
],
|
|
1808
|
+
title: "Controls",
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
expect(
|
|
1814
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
1815
|
+
{
|
|
1816
|
+
automated: true,
|
|
1817
|
+
automatedTestName: "pattern set changes output",
|
|
1818
|
+
browser: true,
|
|
1819
|
+
browserTestName: "browser: pattern set changes output",
|
|
1820
|
+
builtInFitCheck: {
|
|
1821
|
+
checkedBuiltIns: ["select", "vector"],
|
|
1822
|
+
closestBuiltIn: "select",
|
|
1823
|
+
productObservable:
|
|
1824
|
+
"Editing the chosen pattern entry changes the rendered pattern output.",
|
|
1825
|
+
whyInsufficient:
|
|
1826
|
+
"Select can choose one preset, but this product needs runtime entry state with selected item editing.",
|
|
1827
|
+
},
|
|
1828
|
+
componentType: "patternSetEditor",
|
|
1829
|
+
customControlCoverage: [
|
|
1830
|
+
"built-in-gap",
|
|
1831
|
+
"kit-primitives",
|
|
1832
|
+
"minimal-ui",
|
|
1833
|
+
"product-output",
|
|
1834
|
+
"runtime-state",
|
|
1835
|
+
],
|
|
1836
|
+
evidence: "product-output",
|
|
1837
|
+
expectedObservable:
|
|
1838
|
+
"Editing the pattern set changes the rendered product output.",
|
|
1839
|
+
fixture: "pattern set fixture",
|
|
1840
|
+
id: "pattern.set",
|
|
1841
|
+
kind: "control",
|
|
1842
|
+
target: "pattern.set",
|
|
1843
|
+
userAction: "Edit the selected entry.",
|
|
1844
|
+
},
|
|
1845
|
+
]),
|
|
1846
|
+
).toEqual([
|
|
1847
|
+
"Pattern / patternSet (pattern.set) builtInFitCheck.checkedBuiltIns must include collectionActions when the custom control owns a growable, removable, selectable, or reorderable runtime item set.",
|
|
1848
|
+
]);
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
it("rejects custom controls justified only by visual chrome", () => {
|
|
1852
|
+
const schema = defineToolcraft({
|
|
1853
|
+
canvas: { enabled: true },
|
|
1854
|
+
panels: {
|
|
1855
|
+
controls: {
|
|
1856
|
+
sections: [
|
|
1857
|
+
{
|
|
1858
|
+
controls: {
|
|
1859
|
+
shapeButtons: {
|
|
1860
|
+
defaultValue: "rect",
|
|
1861
|
+
label: "Shape",
|
|
1862
|
+
orderRole: "style",
|
|
1863
|
+
target: "shape.kind",
|
|
1864
|
+
type: "shapeButtons",
|
|
1865
|
+
} as never,
|
|
1866
|
+
},
|
|
1867
|
+
title: "Shape",
|
|
1868
|
+
},
|
|
1869
|
+
],
|
|
1870
|
+
title: "Controls",
|
|
1871
|
+
},
|
|
1872
|
+
},
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
expect(
|
|
1876
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
1877
|
+
{
|
|
1878
|
+
automated: true,
|
|
1879
|
+
automatedTestName: "shape buttons change output",
|
|
1880
|
+
browser: true,
|
|
1881
|
+
browserTestName: "browser: shape buttons change output",
|
|
1882
|
+
builtInFitCheck: {
|
|
1883
|
+
checkedBuiltIns: ["actions", "segmented", "select"],
|
|
1884
|
+
closestBuiltIn: "segmented",
|
|
1885
|
+
productObservable:
|
|
1886
|
+
"Choosing a shape changes the rendered product geometry.",
|
|
1887
|
+
whyInsufficient:
|
|
1888
|
+
"The custom control uses icon buttons and a compact visual layout.",
|
|
1889
|
+
},
|
|
1890
|
+
componentType: "shapeButtons",
|
|
1891
|
+
customControlCoverage: [
|
|
1892
|
+
"built-in-gap",
|
|
1893
|
+
"kit-primitives",
|
|
1894
|
+
"minimal-ui",
|
|
1895
|
+
"product-output",
|
|
1896
|
+
"runtime-state",
|
|
1897
|
+
],
|
|
1898
|
+
evidence: "product-output",
|
|
1899
|
+
expectedObservable:
|
|
1900
|
+
"Choosing rectangle, circle, or triangle changes the rendered output.",
|
|
1901
|
+
fixture: "shape fixture",
|
|
1902
|
+
id: "shape.kind",
|
|
1903
|
+
kind: "control",
|
|
1904
|
+
target: "shape.kind",
|
|
1905
|
+
userAction: "Choose each shape icon button.",
|
|
1906
|
+
},
|
|
1907
|
+
]),
|
|
1908
|
+
).toEqual([
|
|
1909
|
+
"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.",
|
|
1910
|
+
]);
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
it("does not treat numeric tuple custom values as collection owners", () => {
|
|
1914
|
+
const schema = defineToolcraft({
|
|
1915
|
+
canvas: { enabled: true },
|
|
1916
|
+
panels: {
|
|
1917
|
+
controls: {
|
|
1918
|
+
sections: [
|
|
1919
|
+
{
|
|
1920
|
+
controls: {
|
|
1921
|
+
focalPoint: {
|
|
1922
|
+
defaultValue: [0.5, 0.5],
|
|
1923
|
+
label: "Focal point",
|
|
1924
|
+
orderRole: "spatial",
|
|
1925
|
+
target: "focal.point",
|
|
1926
|
+
type: "focalPointPad",
|
|
1927
|
+
} as never,
|
|
1928
|
+
},
|
|
1929
|
+
title: "Focus",
|
|
1930
|
+
},
|
|
1931
|
+
],
|
|
1932
|
+
title: "Controls",
|
|
1933
|
+
},
|
|
1934
|
+
},
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
expect(
|
|
1938
|
+
validateToolcraftAcceptanceCoverage(schema, [
|
|
1939
|
+
{
|
|
1940
|
+
automated: true,
|
|
1941
|
+
automatedTestName: "focal point changes output",
|
|
1942
|
+
browser: true,
|
|
1943
|
+
browserTestName: "browser: focal point changes output",
|
|
1944
|
+
builtInFitCheck: {
|
|
1945
|
+
checkedBuiltIns: ["vector"],
|
|
1946
|
+
closestBuiltIn: "vector",
|
|
1947
|
+
productObservable:
|
|
1948
|
+
"Dragging the focal point changes the rendered focus position.",
|
|
1949
|
+
whyInsufficient:
|
|
1950
|
+
"Vector edits x/y, but this product needs a canvas hit target with validation tied to the rendered focal point.",
|
|
1951
|
+
},
|
|
1952
|
+
componentType: "focalPointPad",
|
|
1953
|
+
customControlCoverage: [
|
|
1954
|
+
"built-in-gap",
|
|
1955
|
+
"kit-primitives",
|
|
1956
|
+
"minimal-ui",
|
|
1957
|
+
"product-output",
|
|
1958
|
+
"runtime-state",
|
|
1959
|
+
],
|
|
1960
|
+
evidence: "product-output",
|
|
1961
|
+
expectedObservable:
|
|
1962
|
+
"Dragging the focal point changes the rendered product output.",
|
|
1963
|
+
fixture: "focus fixture",
|
|
1964
|
+
id: "focal.point",
|
|
1965
|
+
kind: "control",
|
|
1966
|
+
target: "focal.point",
|
|
1967
|
+
userAction: "Drag the focal point.",
|
|
1968
|
+
},
|
|
1969
|
+
]),
|
|
1970
|
+
).toEqual([]);
|
|
1971
|
+
});
|
|
1972
|
+
|
|
1635
1973
|
it("accepts custom controls with explicit custom control coverage", () => {
|
|
1636
1974
|
const schema = defineToolcraft({
|
|
1637
1975
|
canvas: { enabled: true },
|
|
@@ -1664,7 +2002,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
1664
2002
|
browser: true,
|
|
1665
2003
|
browserTestName: "browser: glyph ramp changes output",
|
|
1666
2004
|
builtInFitCheck: {
|
|
1667
|
-
checkedBuiltIns: ["fileDrop", "imagePicker", "select"],
|
|
2005
|
+
checkedBuiltIns: ["actions", "collectionActions", "fileDrop", "imagePicker", "select"],
|
|
1668
2006
|
closestBuiltIn: "fileDrop",
|
|
1669
2007
|
productObservable:
|
|
1670
2008
|
"Ordering uploaded glyphs changes the rendered glyph ramp output.",
|
|
@@ -2334,9 +2672,9 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2334
2672
|
"PNG export must pass includeBackground from runtime state to createToolcraftPngExportCanvas; do not hardcode PNG transparency or background inclusion in schema only.",
|
|
2335
2673
|
).toMatch(/\bcreateToolcraftPngExportCanvas\s*\(\s*\{[\s\S]*\bincludeBackground\s*:/);
|
|
2336
2674
|
expect(
|
|
2337
|
-
|
|
2338
|
-
"
|
|
2339
|
-
).
|
|
2675
|
+
productRuntimeImplementationSource,
|
|
2676
|
+
"Live preview must read include-background state through shouldIncludeToolcraftPreviewBackground(state) so turning Include off hides the product preview background without affecting video export.",
|
|
2677
|
+
).toMatch(/\bshouldIncludeToolcraftPreviewBackground\b/);
|
|
2340
2678
|
expect(
|
|
2341
2679
|
backgroundColorTargets.length,
|
|
2342
2680
|
"Every product app with Export PNG must expose a user-facing background color control.",
|
|
@@ -2346,13 +2684,18 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2346
2684
|
'Every product app with Export PNG must expose export.includeBackground in the required "Background" section as a Switch labeled "Include".',
|
|
2347
2685
|
).toBeGreaterThan(0);
|
|
2348
2686
|
|
|
2349
|
-
for (const target of
|
|
2687
|
+
for (const target of backgroundColorTargets) {
|
|
2350
2688
|
expect(
|
|
2351
2689
|
productRuntimeImplementationSource,
|
|
2352
2690
|
`Runtime renderer/export code must read ${target}; declaring the control in schema is not enough.`,
|
|
2353
2691
|
).toContain(target);
|
|
2354
2692
|
}
|
|
2355
2693
|
|
|
2694
|
+
expect(
|
|
2695
|
+
productRuntimeImplementationSource,
|
|
2696
|
+
`Runtime renderer/export code must read ${backgroundToggleTargets.join(", ")} through shouldIncludeToolcraftPreviewBackground(state); declaring the control in schema is not enough.`,
|
|
2697
|
+
).toMatch(/\bshouldIncludeToolcraftPreviewBackground\b|\bexport\.includeBackground\b/);
|
|
2698
|
+
|
|
2356
2699
|
const imageExportSection = getSchemaImageExportSection();
|
|
2357
2700
|
const imageFormatControl = getSectionControlByTarget(
|
|
2358
2701
|
imageExportSection,
|
|
@@ -2788,9 +3131,9 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2788
3131
|
{
|
|
2789
3132
|
...makeControlAcceptance("export.includeBackground", "switch"),
|
|
2790
3133
|
expectedObservable:
|
|
2791
|
-
"Turning Include
|
|
3134
|
+
"Turning Include off makes PNG output transparent, hides the live preview product background, and video output keeps the product background.",
|
|
2792
3135
|
userAction:
|
|
2793
|
-
"Toggle Include
|
|
3136
|
+
"Toggle Include off, verify preview has no product background, export PNG with alpha, and verify video export keeps the background.",
|
|
2794
3137
|
},
|
|
2795
3138
|
{
|
|
2796
3139
|
actionCoverage: ["export.png"],
|
|
@@ -2818,7 +3161,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2818
3161
|
);
|
|
2819
3162
|
});
|
|
2820
3163
|
|
|
2821
|
-
it("requires include-background acceptance to
|
|
3164
|
+
it("requires include-background acceptance to hide preview background and keep video background", () => {
|
|
2822
3165
|
const schemaWithBackgroundControls = defineToolcraft({
|
|
2823
3166
|
canvas: {
|
|
2824
3167
|
enabled: true,
|
|
@@ -2875,7 +3218,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2875
3218
|
).toEqual(
|
|
2876
3219
|
expect.arrayContaining([
|
|
2877
3220
|
expect.stringContaining(
|
|
2878
|
-
"controls
|
|
3221
|
+
"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
3222
|
),
|
|
2880
3223
|
]),
|
|
2881
3224
|
);
|
|
@@ -2933,7 +3276,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
2933
3276
|
kind: "control",
|
|
2934
3277
|
target: "actions.output",
|
|
2935
3278
|
userAction:
|
|
2936
|
-
"Toggle Include
|
|
3279
|
+
"Toggle Include off, verify preview has no product background, export PNG with alpha, and verify video export keeps the background.",
|
|
2937
3280
|
},
|
|
2938
3281
|
]),
|
|
2939
3282
|
).toEqual(
|
|
@@ -3540,7 +3883,7 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
3540
3883
|
includeBackground: {
|
|
3541
3884
|
defaultValue: true,
|
|
3542
3885
|
description:
|
|
3543
|
-
"Controls PNG background
|
|
3886
|
+
"Controls preview and PNG background visibility while video keeps the background.",
|
|
3544
3887
|
label: "Include",
|
|
3545
3888
|
target: "export.includeBackground",
|
|
3546
3889
|
type: "switch",
|
|
@@ -6052,6 +6395,173 @@ describe("Toolcraft template app acceptance coverage", () => {
|
|
|
6052
6395
|
);
|
|
6053
6396
|
});
|
|
6054
6397
|
|
|
6398
|
+
it("rejects visible labels for palette variation color banks", () => {
|
|
6399
|
+
const schemaWithLabeledPaletteBank = defineToolcraft({
|
|
6400
|
+
canvas: { enabled: true },
|
|
6401
|
+
panels: {
|
|
6402
|
+
controls: {
|
|
6403
|
+
sections: [
|
|
6404
|
+
{
|
|
6405
|
+
controls: {
|
|
6406
|
+
accent1: {
|
|
6407
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
6408
|
+
label: "Color 1",
|
|
6409
|
+
target: "palette.accent1",
|
|
6410
|
+
type: "color",
|
|
6411
|
+
},
|
|
6412
|
+
accent2: {
|
|
6413
|
+
defaultValue: { hex: "#FF7A90" },
|
|
6414
|
+
label: "Color 2",
|
|
6415
|
+
target: "palette.accent2",
|
|
6416
|
+
type: "color",
|
|
6417
|
+
},
|
|
6418
|
+
spread: {
|
|
6419
|
+
defaultValue: 34,
|
|
6420
|
+
label: "Spread",
|
|
6421
|
+
max: 100,
|
|
6422
|
+
min: 0,
|
|
6423
|
+
target: "palette.spread",
|
|
6424
|
+
type: "slider",
|
|
6425
|
+
unit: "%",
|
|
6426
|
+
},
|
|
6427
|
+
},
|
|
6428
|
+
title: "Accent Shades",
|
|
6429
|
+
},
|
|
6430
|
+
],
|
|
6431
|
+
title: "Controls",
|
|
6432
|
+
},
|
|
6433
|
+
},
|
|
6434
|
+
});
|
|
6435
|
+
|
|
6436
|
+
expect(
|
|
6437
|
+
validateToolcraftAcceptanceCoverage(schemaWithLabeledPaletteBank, [
|
|
6438
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
6439
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
6440
|
+
makeControlAcceptance("palette.spread", "slider"),
|
|
6441
|
+
]),
|
|
6442
|
+
).toEqual(
|
|
6443
|
+
expect.arrayContaining([
|
|
6444
|
+
'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.',
|
|
6445
|
+
'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.',
|
|
6446
|
+
]),
|
|
6447
|
+
);
|
|
6448
|
+
});
|
|
6449
|
+
|
|
6450
|
+
it("rejects mixed label visibility inside one palette variation color group", () => {
|
|
6451
|
+
const schemaWithMixedPaletteBankLabels = defineToolcraft({
|
|
6452
|
+
canvas: { enabled: true },
|
|
6453
|
+
panels: {
|
|
6454
|
+
controls: {
|
|
6455
|
+
sections: [
|
|
6456
|
+
{
|
|
6457
|
+
controls: {
|
|
6458
|
+
accent1: {
|
|
6459
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
6460
|
+
label: false,
|
|
6461
|
+
target: "palette.accent1",
|
|
6462
|
+
type: "color",
|
|
6463
|
+
},
|
|
6464
|
+
accent2: {
|
|
6465
|
+
defaultValue: { hex: "#FF7A90" },
|
|
6466
|
+
label: "Color 2",
|
|
6467
|
+
target: "palette.accent2",
|
|
6468
|
+
type: "color",
|
|
6469
|
+
},
|
|
6470
|
+
accent3: {
|
|
6471
|
+
defaultValue: { hex: "#FFD166" },
|
|
6472
|
+
label: false,
|
|
6473
|
+
target: "palette.accent3",
|
|
6474
|
+
type: "color",
|
|
6475
|
+
},
|
|
6476
|
+
},
|
|
6477
|
+
title: "Accent Shades",
|
|
6478
|
+
},
|
|
6479
|
+
],
|
|
6480
|
+
title: "Controls",
|
|
6481
|
+
},
|
|
6482
|
+
},
|
|
6483
|
+
});
|
|
6484
|
+
|
|
6485
|
+
expect(
|
|
6486
|
+
validateToolcraftAcceptanceCoverage(schemaWithMixedPaletteBankLabels, [
|
|
6487
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
6488
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
6489
|
+
makeControlAcceptance("palette.accent3", "color"),
|
|
6490
|
+
]),
|
|
6491
|
+
).toEqual(
|
|
6492
|
+
expect.arrayContaining([
|
|
6493
|
+
"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.",
|
|
6494
|
+
'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.',
|
|
6495
|
+
]),
|
|
6496
|
+
);
|
|
6497
|
+
});
|
|
6498
|
+
|
|
6499
|
+
it("allows unlabeled palette variation colors and labeled distinct color roles", () => {
|
|
6500
|
+
const schemaWithUsefulColorLabels = defineToolcraft({
|
|
6501
|
+
canvas: { enabled: true },
|
|
6502
|
+
panels: {
|
|
6503
|
+
controls: {
|
|
6504
|
+
sections: [
|
|
6505
|
+
{
|
|
6506
|
+
controls: {
|
|
6507
|
+
accent1: {
|
|
6508
|
+
defaultValue: { hex: "#9CE6FF" },
|
|
6509
|
+
label: false,
|
|
6510
|
+
target: "palette.accent1",
|
|
6511
|
+
type: "color",
|
|
6512
|
+
},
|
|
6513
|
+
accent2: {
|
|
6514
|
+
defaultValue: { hex: "#FF7A90" },
|
|
6515
|
+
label: false,
|
|
6516
|
+
target: "palette.accent2",
|
|
6517
|
+
type: "color",
|
|
6518
|
+
},
|
|
6519
|
+
spread: {
|
|
6520
|
+
defaultValue: 34,
|
|
6521
|
+
label: "Spread",
|
|
6522
|
+
max: 100,
|
|
6523
|
+
min: 0,
|
|
6524
|
+
target: "palette.spread",
|
|
6525
|
+
type: "slider",
|
|
6526
|
+
unit: "%",
|
|
6527
|
+
},
|
|
6528
|
+
},
|
|
6529
|
+
title: "Accent Shades",
|
|
6530
|
+
},
|
|
6531
|
+
{
|
|
6532
|
+
controls: {
|
|
6533
|
+
fill: {
|
|
6534
|
+
defaultValue: { hex: "#FFFFFF" },
|
|
6535
|
+
label: "Fill",
|
|
6536
|
+
target: "object.fill",
|
|
6537
|
+
type: "color",
|
|
6538
|
+
},
|
|
6539
|
+
stroke: {
|
|
6540
|
+
defaultValue: { hex: "#111111" },
|
|
6541
|
+
label: "Stroke",
|
|
6542
|
+
target: "object.stroke",
|
|
6543
|
+
type: "color",
|
|
6544
|
+
},
|
|
6545
|
+
},
|
|
6546
|
+
title: "Object Colors",
|
|
6547
|
+
},
|
|
6548
|
+
],
|
|
6549
|
+
title: "Controls",
|
|
6550
|
+
},
|
|
6551
|
+
},
|
|
6552
|
+
});
|
|
6553
|
+
|
|
6554
|
+
expect(
|
|
6555
|
+
validateToolcraftAcceptanceCoverage(schemaWithUsefulColorLabels, [
|
|
6556
|
+
makeControlAcceptance("palette.accent1", "color"),
|
|
6557
|
+
makeControlAcceptance("palette.accent2", "color"),
|
|
6558
|
+
makeControlAcceptance("palette.spread", "slider"),
|
|
6559
|
+
makeControlAcceptance("object.fill", "color"),
|
|
6560
|
+
makeControlAcceptance("object.stroke", "color"),
|
|
6561
|
+
]),
|
|
6562
|
+
).toEqual([]);
|
|
6563
|
+
});
|
|
6564
|
+
|
|
6055
6565
|
it("requires mode selectors to appear before dependent controls", () => {
|
|
6056
6566
|
const schemaWithLateModeSelector = {
|
|
6057
6567
|
...starterSchema,
|