@pixel-point/toolcraft 0.0.2
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/LICENSE.md +98 -0
- package/README.md +41 -0
- package/bin/create-toolcraft-app.mjs +8 -0
- package/bin/toolcraft.mjs +8 -0
- package/package.json +24 -0
- package/scripts/prepare-pack.mjs +29 -0
- package/src/cli.mjs +392 -0
- package/src/cli.test.mjs +284 -0
- package/src/copy-recursive.mjs +86 -0
- package/src/generate.mjs +212 -0
- package/src/generate.test.mjs +322 -0
- package/src/import-map.mjs +14 -0
- package/src/package-json.mjs +80 -0
- package/src/package-json.test.mjs +67 -0
- package/src/rewrite-imports.mjs +85 -0
- package/src/rewrite-imports.test.mjs +58 -0
- package/templates/runtime/contracts/component-contracts.test.ts +1165 -0
- package/templates/runtime/contracts/component-contracts.ts +1340 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +206 -0
- package/templates/runtime/contracts/decision-contracts.ts +283 -0
- package/templates/runtime/contracts/index.test.ts +14 -0
- package/templates/runtime/contracts/index.ts +3 -0
- package/templates/runtime/contracts/types.ts +56 -0
- package/templates/runtime/export/export.test.ts +203 -0
- package/templates/runtime/export/export.ts +132 -0
- package/templates/runtime/export/index.ts +1 -0
- package/templates/runtime/index.ts +14 -0
- package/templates/runtime/react/canvas-shell.test.tsx +424 -0
- package/templates/runtime/react/canvas-shell.tsx +408 -0
- package/templates/runtime/react/control-renderers.ts +31 -0
- package/templates/runtime/react/controls-panel.test.tsx +3736 -0
- package/templates/runtime/react/controls-panel.tsx +2327 -0
- package/templates/runtime/react/curve-geometry.test.ts +70 -0
- package/templates/runtime/react/index.ts +15 -0
- package/templates/runtime/react/layer-tree.ts +96 -0
- package/templates/runtime/react/layers-panel.test.tsx +487 -0
- package/templates/runtime/react/layers-panel.tsx +1348 -0
- package/templates/runtime/react/media-file.ts +82 -0
- package/templates/runtime/react/panel-host-config.ts +80 -0
- package/templates/runtime/react/panel-host-geometry.test.ts +66 -0
- package/templates/runtime/react/panel-host-geometry.ts +109 -0
- package/templates/runtime/react/panel-host-types.ts +74 -0
- package/templates/runtime/react/panel-host.test.tsx +102 -0
- package/templates/runtime/react/panel-host.tsx +353 -0
- package/templates/runtime/react/runtime-public-api.test.tsx +132 -0
- package/templates/runtime/react/settings-transfer.test.ts +150 -0
- package/templates/runtime/react/settings-transfer.ts +279 -0
- package/templates/runtime/react/storage-key-migration.ts +48 -0
- package/templates/runtime/react/theme-runtime.tsx +177 -0
- package/templates/runtime/react/timeline-panel.test.tsx +668 -0
- package/templates/runtime/react/timeline-panel.tsx +2953 -0
- package/templates/runtime/react/toolbar-panel.test.tsx +212 -0
- package/templates/runtime/react/toolbar-panel.tsx +205 -0
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +350 -0
- package/templates/runtime/react/toolcraft-app.test.tsx +339 -0
- package/templates/runtime/react/toolcraft-app.tsx +81 -0
- package/templates/runtime/react/toolcraft-root.test.tsx +347 -0
- package/templates/runtime/react/toolcraft-root.tsx +203 -0
- package/templates/runtime/react/use-toolcraft.ts +41 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +1524 -0
- package/templates/runtime/schema/define-toolcraft.ts +1442 -0
- package/templates/runtime/schema/keyframe-capability.test.ts +90 -0
- package/templates/runtime/schema/keyframe-capability.ts +51 -0
- package/templates/runtime/schema/runtime-targets.ts +40 -0
- package/templates/runtime/schema/types.ts +370 -0
- package/templates/runtime/state/canvas-zoom.ts +8 -0
- package/templates/runtime/state/create-template-state.test.ts +242 -0
- package/templates/runtime/state/create-template-state.ts +95 -0
- package/templates/runtime/state/keyframe-evaluation.test.ts +141 -0
- package/templates/runtime/state/keyframe-evaluation.ts +203 -0
- package/templates/runtime/state/persistence.test.ts +217 -0
- package/templates/runtime/state/persistence.ts +511 -0
- package/templates/runtime/state/reducer.test.ts +937 -0
- package/templates/runtime/state/reducer.ts +1212 -0
- package/templates/runtime/state/timeline-readiness.ts +43 -0
- package/templates/runtime/state/types.ts +242 -0
- package/templates/runtime/styles.css +125 -0
- package/templates/runtime/testing/performance.test.ts +1058 -0
- package/templates/runtime/testing/performance.ts +1078 -0
- package/templates/starter/AGENTS.md +186 -0
- package/templates/starter/LICENSE.md +98 -0
- package/templates/starter/NOTICE.md +8 -0
- package/templates/starter/docs/toolcraft/README.md +41 -0
- package/templates/starter/docs/toolcraft/acceptance-testing.md +205 -0
- package/templates/starter/docs/toolcraft/agent-worklog.md +81 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +206 -0
- package/templates/starter/docs/toolcraft/component-rules.md +299 -0
- package/templates/starter/docs/toolcraft/custom-controls.md +71 -0
- package/templates/starter/docs/toolcraft/decision-contract.md +71 -0
- package/templates/starter/docs/toolcraft/performance.md +112 -0
- package/templates/starter/docs/toolcraft/renderer-technique.md +48 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +265 -0
- package/templates/starter/docs/toolcraft/workflow.md +87 -0
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +785 -0
- package/templates/starter/e2e/app-controls.spec.ts +41 -0
- package/templates/starter/e2e/app-performance.spec.ts +326 -0
- package/templates/starter/e2e/canvas-handle-helpers.ts +244 -0
- package/templates/starter/e2e/performance-helpers.ts +612 -0
- package/templates/starter/e2e/product-observable-helpers.ts +170 -0
- package/templates/starter/index.html +12 -0
- package/templates/starter/package.json +52 -0
- package/templates/starter/playwright.config.ts +43 -0
- package/templates/starter/scripts/check-ai-skills.mjs +95 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +159 -0
- package/templates/starter/scripts/check-toolcraft-integrity.mjs +232 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +48 -0
- package/templates/starter/scripts/toolcraft-port.mjs +54 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +73 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +5959 -0
- package/templates/starter/src/app/starter-acceptance.ts +2646 -0
- package/templates/starter/src/app/starter-performance.test.ts +1390 -0
- package/templates/starter/src/app/starter-performance.ts +12 -0
- package/templates/starter/src/app/starter-schema.test.ts +70 -0
- package/templates/starter/src/app/starter-schema.ts +15 -0
- package/templates/starter/src/main.tsx +18 -0
- package/templates/starter/src/router.tsx +16 -0
- package/templates/starter/src/routes/index.tsx +7 -0
- package/templates/starter/src/routes/root.tsx +19 -0
- package/templates/starter/src/styles.css +120 -0
- package/templates/starter/tsconfig.json +11 -0
- package/templates/starter/vite.config.ts +13 -0
- package/templates/ui/components/composites/accordion.tsx +73 -0
- package/templates/ui/components/composites/alert-dialog.tsx +190 -0
- package/templates/ui/components/composites/alert.tsx +74 -0
- package/templates/ui/components/composites/aspect-ratio.tsx +22 -0
- package/templates/ui/components/composites/avatar.tsx +98 -0
- package/templates/ui/components/composites/badge.tsx +69 -0
- package/templates/ui/components/composites/breadcrumb.tsx +106 -0
- package/templates/ui/components/composites/card.tsx +91 -0
- package/templates/ui/components/composites/combobox.tsx +486 -0
- package/templates/ui/components/composites/command.tsx +296 -0
- package/templates/ui/components/composites/context-menu.tsx +247 -0
- package/templates/ui/components/composites/dialog.tsx +282 -0
- package/templates/ui/components/composites/dropdown-menu.tsx +299 -0
- package/templates/ui/components/composites/empty.tsx +110 -0
- package/templates/ui/components/composites/hover-card.tsx +44 -0
- package/templates/ui/components/composites/index.ts +30 -0
- package/templates/ui/components/composites/menubar.tsx +214 -0
- package/templates/ui/components/composites/navigation-menu.tsx +167 -0
- package/templates/ui/components/composites/pagination.tsx +131 -0
- package/templates/ui/components/composites/progress.tsx +72 -0
- package/templates/ui/components/composites/radio-group.tsx +84 -0
- package/templates/ui/components/composites/resizable.tsx +42 -0
- package/templates/ui/components/composites/sheet.tsx +153 -0
- package/templates/ui/components/composites/sidebar-structural.tsx +310 -0
- package/templates/ui/components/composites/sidebar.tsx +431 -0
- package/templates/ui/components/composites/sonner.tsx +35 -0
- package/templates/ui/components/composites/spinner.tsx +43 -0
- package/templates/ui/components/composites/table.tsx +108 -0
- package/templates/ui/components/composites/tabs.tsx +83 -0
- package/templates/ui/components/control-layout/index.tsx +437 -0
- package/templates/ui/components/controls/actions/actions-control.tsx +139 -0
- package/templates/ui/components/controls/actions/index.ts +9 -0
- package/templates/ui/components/controls/anchor-grid/anchor-grid-control.tsx +107 -0
- package/templates/ui/components/controls/anchor-grid/index.ts +4 -0
- package/templates/ui/components/controls/boolean/boolean-controls.tsx +79 -0
- package/templates/ui/components/controls/boolean/index.ts +4 -0
- package/templates/ui/components/controls/channel-mixer/channel-mixer-control.tsx +95 -0
- package/templates/ui/components/controls/channel-mixer/index.ts +4 -0
- package/templates/ui/components/controls/channel-tabs/channel-tabs.tsx +42 -0
- package/templates/ui/components/controls/channel-tabs/index.ts +6 -0
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +90 -0
- package/templates/ui/components/controls/code-textarea/index.ts +4 -0
- package/templates/ui/components/controls/color/color-control.tsx +571 -0
- package/templates/ui/components/controls/color/color-picker-popover.tsx +104 -0
- package/templates/ui/components/controls/color/index.ts +41 -0
- package/templates/ui/components/controls/color/palette-control-data.ts +436 -0
- package/templates/ui/components/controls/color/palette-control.tsx +535 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-channel-utils.ts +162 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +190 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +485 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +710 -0
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +503 -0
- package/templates/ui/components/controls/control-types.ts +43 -0
- package/templates/ui/components/controls/curves/curve-geometry.ts +355 -0
- package/templates/ui/components/controls/curves/curve-graph.tsx +390 -0
- package/templates/ui/components/controls/curves/curves-control.tsx +445 -0
- package/templates/ui/components/controls/curves/index.ts +6 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +191 -0
- package/templates/ui/components/controls/file-drop/index.ts +5 -0
- package/templates/ui/components/controls/font-picker/font-catalog.json +15360 -0
- package/templates/ui/components/controls/font-picker/font-catalog.ts +116 -0
- package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1202 -0
- package/templates/ui/components/controls/font-picker/font-preview-loader.ts +336 -0
- package/templates/ui/components/controls/font-picker/index.ts +24 -0
- package/templates/ui/components/controls/font-picker/use-hover-intent.ts +46 -0
- package/templates/ui/components/controls/gradient/gradient-control-utils.ts +190 -0
- package/templates/ui/components/controls/gradient/gradient-control.tsx +612 -0
- package/templates/ui/components/controls/gradient/gradient-stop-list.tsx +400 -0
- package/templates/ui/components/controls/gradient/gradient-toolbar.tsx +152 -0
- package/templates/ui/components/controls/gradient/index.ts +4 -0
- package/templates/ui/components/controls/image-picker/image-picker-control.tsx +139 -0
- package/templates/ui/components/controls/image-picker/index.ts +7 -0
- package/templates/ui/components/controls/index.ts +192 -0
- package/templates/ui/components/controls/range-input/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +173 -0
- package/templates/ui/components/controls/range-slider/index.ts +4 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +122 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +61 -0
- package/templates/ui/components/controls/segmented/index.ts +8 -0
- package/templates/ui/components/controls/segmented/segmented-control.tsx +94 -0
- package/templates/ui/components/controls/select/index.ts +4 -0
- package/templates/ui/components/controls/select/select-control.tsx +223 -0
- package/templates/ui/components/controls/slider/index.ts +4 -0
- package/templates/ui/components/controls/slider/slider-control.tsx +150 -0
- package/templates/ui/components/controls/slider/slider-value.ts +56 -0
- package/templates/ui/components/controls/text-input/index.ts +4 -0
- package/templates/ui/components/controls/text-input/text-input-control.tsx +158 -0
- package/templates/ui/components/controls/use-measured-element-width.ts +42 -0
- package/templates/ui/components/controls/vector/index.ts +8 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +401 -0
- package/templates/ui/components/panel/index.ts +19 -0
- package/templates/ui/components/panel/panel-actions.tsx +165 -0
- package/templates/ui/components/panel/panel-header.tsx +61 -0
- package/templates/ui/components/panel/panel-icon-button.tsx +96 -0
- package/templates/ui/components/panel/panel-section.tsx +168 -0
- package/templates/ui/components/panel/panel-surface.tsx +206 -0
- package/templates/ui/components/panel/panel.tsx +210 -0
- package/templates/ui/components/primitives/animated-loader.tsx +61 -0
- package/templates/ui/components/primitives/button-group.tsx +134 -0
- package/templates/ui/components/primitives/button.tsx +429 -0
- package/templates/ui/components/primitives/checkbox.tsx +62 -0
- package/templates/ui/components/primitives/editable-slider-value-label.tsx +337 -0
- package/templates/ui/components/primitives/field.tsx +225 -0
- package/templates/ui/components/primitives/index.ts +82 -0
- package/templates/ui/components/primitives/input-group.tsx +298 -0
- package/templates/ui/components/primitives/input.tsx +61 -0
- package/templates/ui/components/primitives/internal/button-loading.tsx +178 -0
- package/templates/ui/components/primitives/label.tsx +16 -0
- package/templates/ui/components/primitives/popover.tsx +126 -0
- package/templates/ui/components/primitives/portal-layer-context.tsx +33 -0
- package/templates/ui/components/primitives/primitive-arrow-icon.tsx +38 -0
- package/templates/ui/components/primitives/scroll-fade-logic.ts +441 -0
- package/templates/ui/components/primitives/scroll-fade-render.tsx +75 -0
- package/templates/ui/components/primitives/scroll-fade-types.ts +41 -0
- package/templates/ui/components/primitives/scroll-fade.tsx +72 -0
- package/templates/ui/components/primitives/select.tsx +408 -0
- package/templates/ui/components/primitives/selection-state.ts +31 -0
- package/templates/ui/components/primitives/separator.tsx +21 -0
- package/templates/ui/components/primitives/slider/index.ts +4 -0
- package/templates/ui/components/primitives/slider/slider-interaction.tsx +96 -0
- package/templates/ui/components/primitives/slider/slider-parts.tsx +303 -0
- package/templates/ui/components/primitives/slider/slider-reset.ts +152 -0
- package/templates/ui/components/primitives/slider/slider-value.ts +114 -0
- package/templates/ui/components/primitives/slider/slider.tsx +511 -0
- package/templates/ui/components/primitives/switch.tsx +35 -0
- package/templates/ui/components/primitives/textarea.tsx +49 -0
- package/templates/ui/components/primitives/toggle-group.tsx +114 -0
- package/templates/ui/components/primitives/toggle.tsx +46 -0
- package/templates/ui/components/primitives/tooltip.tsx +100 -0
- package/templates/ui/hooks/use-mobile.ts +21 -0
- package/templates/ui/index.ts +31 -0
- package/templates/ui/lib/control-outline.ts +3 -0
- package/templates/ui/lib/input-control-style.ts +131 -0
- package/templates/ui/lib/style-guide-color-utils.ts +111 -0
- package/templates/ui/lib/utils.ts +6 -0
- package/templates/ui/styles.css +291 -0
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { defineToolcraft } from "../schema/define-toolcraft";
|
|
4
|
+
import {
|
|
5
|
+
collectToolcraftPerformanceSensitiveControls,
|
|
6
|
+
collectToolcraftUnclassifiedPerformanceControls,
|
|
7
|
+
defineToolcraftPerformance,
|
|
8
|
+
validateToolcraftPerformanceCoverage,
|
|
9
|
+
} from "./performance";
|
|
10
|
+
|
|
11
|
+
const testSchema = defineToolcraft({
|
|
12
|
+
canvas: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
sizing: { mode: "intrinsic-media" },
|
|
15
|
+
},
|
|
16
|
+
panels: {
|
|
17
|
+
controls: {
|
|
18
|
+
sections: [
|
|
19
|
+
{
|
|
20
|
+
controls: {
|
|
21
|
+
density: {
|
|
22
|
+
defaultValue: 4,
|
|
23
|
+
label: "Density",
|
|
24
|
+
max: 12,
|
|
25
|
+
min: 1,
|
|
26
|
+
performanceReason: "Density changes the amount of rendered output.",
|
|
27
|
+
performanceRole: "workload",
|
|
28
|
+
target: "render.density",
|
|
29
|
+
type: "slider",
|
|
30
|
+
},
|
|
31
|
+
mode: {
|
|
32
|
+
defaultValue: "soft",
|
|
33
|
+
label: "Mode",
|
|
34
|
+
options: [
|
|
35
|
+
{ label: "Soft", value: "soft" },
|
|
36
|
+
{ label: "Sharp", value: "sharp" },
|
|
37
|
+
],
|
|
38
|
+
performanceReason: "Mode changes rendering branches but not workload size.",
|
|
39
|
+
performanceRole: "responsiveness",
|
|
40
|
+
target: "render.mode",
|
|
41
|
+
type: "select",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
title: "Runtime Controls",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const ordinarySliderSchema = defineToolcraft({
|
|
52
|
+
canvas: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
sizing: { mode: "intrinsic-media" },
|
|
55
|
+
},
|
|
56
|
+
panels: {
|
|
57
|
+
controls: {
|
|
58
|
+
sections: [
|
|
59
|
+
{
|
|
60
|
+
controls: {
|
|
61
|
+
opacity: {
|
|
62
|
+
defaultValue: 50,
|
|
63
|
+
label: "Opacity",
|
|
64
|
+
max: 100,
|
|
65
|
+
min: 0,
|
|
66
|
+
performanceReason: "Opacity changes a lightweight uniform value.",
|
|
67
|
+
performanceRole: "responsiveness",
|
|
68
|
+
target: "render.opacity",
|
|
69
|
+
type: "slider",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
title: "Runtime Controls",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const largeTextSchema = defineToolcraft({
|
|
80
|
+
canvas: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
sizing: { mode: "intrinsic-media" },
|
|
83
|
+
},
|
|
84
|
+
panels: {
|
|
85
|
+
controls: {
|
|
86
|
+
sections: [
|
|
87
|
+
{
|
|
88
|
+
controls: {
|
|
89
|
+
content: {
|
|
90
|
+
defaultValue: "",
|
|
91
|
+
label: "Content",
|
|
92
|
+
performanceReason: "Content length changes text layout and renderer workload.",
|
|
93
|
+
performanceRole: "workload",
|
|
94
|
+
target: "product.content",
|
|
95
|
+
type: "code",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
title: "Runtime Controls",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
function createViewportZoomStressScenario() {
|
|
106
|
+
return {
|
|
107
|
+
automated: true,
|
|
108
|
+
automatedTestName: "perf: detailed canvas zoom stays smooth",
|
|
109
|
+
browser: true,
|
|
110
|
+
browserTestName: "browser perf: detailed canvas zoom stays smooth",
|
|
111
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500, maxLongTaskMs: 80 },
|
|
112
|
+
expectedObservable:
|
|
113
|
+
"Zooming detailed product output does not shake the canvas or block frames.",
|
|
114
|
+
fixture: "detailed renderer zoom fixture",
|
|
115
|
+
id: "viewport-zoom-stress",
|
|
116
|
+
interaction: "viewport-zoom-stress" as const,
|
|
117
|
+
stress: true,
|
|
118
|
+
target: "canvas.viewport",
|
|
119
|
+
workload: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createMaxValueStressFixture(value: unknown, reason = "Maximum workload value.") {
|
|
124
|
+
return {
|
|
125
|
+
kind: "max-value" as const,
|
|
126
|
+
reason,
|
|
127
|
+
value,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createLargeTextStressValue(): string {
|
|
132
|
+
return Array.from(
|
|
133
|
+
{ length: 1_000 },
|
|
134
|
+
(_, index) =>
|
|
135
|
+
`Line ${String(index + 1).padStart(4, "0")} performance stress text with enough glyphs to exercise layout.`,
|
|
136
|
+
).join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createLargeTextStressFixture() {
|
|
140
|
+
return {
|
|
141
|
+
kind: "large-text" as const,
|
|
142
|
+
minChars: 50_000,
|
|
143
|
+
minLines: 1_000,
|
|
144
|
+
reason: "Long multiline content is the heaviest realistic text workload.",
|
|
145
|
+
value: createLargeTextStressValue(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function createTextRendererScenarios(
|
|
150
|
+
options: { includeStressPreview?: boolean; includeViewportZoomStress?: boolean } = {},
|
|
151
|
+
) {
|
|
152
|
+
const includeStressPreview = options.includeStressPreview ?? true;
|
|
153
|
+
const includeViewportZoomStress = options.includeViewportZoomStress ?? true;
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
{
|
|
157
|
+
automated: true,
|
|
158
|
+
automatedTestName: "perf: text preview render stays under budget",
|
|
159
|
+
browser: true,
|
|
160
|
+
browserTestName: "browser perf: text preview render stays under budget",
|
|
161
|
+
budget: { maxLongTaskMs: 80, maxPreviewMs: 1000 },
|
|
162
|
+
expectedObservable: "Text preview renders crisply without freezing.",
|
|
163
|
+
fixture: "native-resolution glyph output fixture",
|
|
164
|
+
id: "text-preview-render",
|
|
165
|
+
interaction: "preview-render" as const,
|
|
166
|
+
stress: includeStressPreview,
|
|
167
|
+
workload: false,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
automated: true,
|
|
171
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
172
|
+
browser: true,
|
|
173
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
174
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
175
|
+
controlLabel: "Density",
|
|
176
|
+
expectedObservable: "Dragging Density updates text output without blocking the UI.",
|
|
177
|
+
fixture: "runtime density fixture",
|
|
178
|
+
id: "density-drag",
|
|
179
|
+
interaction: "control-drag" as const,
|
|
180
|
+
stressFixture: createMaxValueStressFixture(
|
|
181
|
+
12,
|
|
182
|
+
"Density max is the heaviest glyph output fixture.",
|
|
183
|
+
),
|
|
184
|
+
target: "render.density",
|
|
185
|
+
values: { default: 4, max: 12, min: 1 },
|
|
186
|
+
workload: true,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
automated: true,
|
|
190
|
+
automatedTestName: "perf: mode change stays responsive",
|
|
191
|
+
browser: true,
|
|
192
|
+
browserTestName: "browser perf: mode change stays responsive",
|
|
193
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
194
|
+
controlLabel: "Mode",
|
|
195
|
+
expectedObservable: "Changing Mode updates text output without blocking the UI.",
|
|
196
|
+
fixture: "runtime mode fixture",
|
|
197
|
+
id: "mode-change",
|
|
198
|
+
interaction: "control-change" as const,
|
|
199
|
+
target: "render.mode",
|
|
200
|
+
values: { default: "soft", max: "sharp", min: "soft" },
|
|
201
|
+
workload: false,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
automated: true,
|
|
205
|
+
automatedTestName: "perf: viewport stays stable",
|
|
206
|
+
browser: true,
|
|
207
|
+
browserTestName: "browser perf: viewport stays stable",
|
|
208
|
+
budget: { maxFrameGapMs: 80 },
|
|
209
|
+
expectedObservable: "Viewport remains stable.",
|
|
210
|
+
fixture: "native-resolution glyph output fixture",
|
|
211
|
+
id: "viewport-stability",
|
|
212
|
+
interaction: "viewport-stability" as const,
|
|
213
|
+
workload: false,
|
|
214
|
+
},
|
|
215
|
+
...(includeViewportZoomStress ? [createViewportZoomStressScenario()] : []),
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createAnimationFrameScenario() {
|
|
220
|
+
return {
|
|
221
|
+
automated: true,
|
|
222
|
+
automatedTestName: "perf: animation frame loop stays smooth",
|
|
223
|
+
browser: true,
|
|
224
|
+
browserTestName: "browser perf: animation frame loop stays smooth",
|
|
225
|
+
budget: { maxFrameGapMs: 80, maxLongTaskMs: 80 },
|
|
226
|
+
expectedObservable: "Animated renderer advances without long frame gaps.",
|
|
227
|
+
fixture: "animated renderer fixture",
|
|
228
|
+
id: "animation-frame-loop",
|
|
229
|
+
interaction: "animation-frame" as const,
|
|
230
|
+
stress: true,
|
|
231
|
+
workload: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function createAnimationViewportDragScenario() {
|
|
236
|
+
return {
|
|
237
|
+
automated: true,
|
|
238
|
+
automatedTestName: "perf: animated canvas drag stays smooth",
|
|
239
|
+
browser: true,
|
|
240
|
+
browserTestName: "browser perf: animated canvas drag stays smooth",
|
|
241
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500, maxLongTaskMs: 80 },
|
|
242
|
+
expectedObservable: "Animated renderer stays smooth while the canvas viewport is dragged.",
|
|
243
|
+
fixture: "animated renderer canvas drag fixture",
|
|
244
|
+
id: "animation-viewport-drag",
|
|
245
|
+
interaction: "animation-viewport-drag" as const,
|
|
246
|
+
stress: true,
|
|
247
|
+
target: "canvas.viewport",
|
|
248
|
+
workload: false,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function createAnimatedVectorRendererConfig(options: { includeViewportDrag: boolean }) {
|
|
253
|
+
return defineToolcraftPerformance({
|
|
254
|
+
rendererStrategy: "svg",
|
|
255
|
+
rendererTechnique: {
|
|
256
|
+
exportRenderer: "svg",
|
|
257
|
+
fidelityRisks: ["animated vector output must stay crisp while panning"],
|
|
258
|
+
performanceRisks: ["dense vector animation can jank while the viewport moves"],
|
|
259
|
+
previewRenderer: "svg",
|
|
260
|
+
productRepresentation: "vector",
|
|
261
|
+
rendererStrategy: "svg",
|
|
262
|
+
rendererWorkload: "vector-output",
|
|
263
|
+
sourceRepresentation: "procedural-data",
|
|
264
|
+
whyNotAlternativeStrategies: ["svg preserves semantic vector output for this fixture"],
|
|
265
|
+
},
|
|
266
|
+
rendererWorkload: "vector-output",
|
|
267
|
+
scenarios: [
|
|
268
|
+
...createTextRendererScenarios(),
|
|
269
|
+
createAnimationFrameScenario(),
|
|
270
|
+
...(options.includeViewportDrag ? [createAnimationViewportDragScenario()] : []),
|
|
271
|
+
],
|
|
272
|
+
usesCustomRenderer: true,
|
|
273
|
+
workloadTargets: ["render.density"],
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
describe("Toolcraft template performance contract", () => {
|
|
278
|
+
it("collects performance-sensitive controls from schema", () => {
|
|
279
|
+
expect(collectToolcraftPerformanceSensitiveControls(testSchema)).toEqual([
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
controlId: "density",
|
|
282
|
+
target: "render.density",
|
|
283
|
+
}),
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("does not classify every slider as workload-sensitive", () => {
|
|
288
|
+
expect(collectToolcraftPerformanceSensitiveControls(ordinarySliderSchema)).toEqual([]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("collects explicit workload controls even when labels do not match fallback keywords", () => {
|
|
292
|
+
const explicitWorkloadSchema = defineToolcraft({
|
|
293
|
+
canvas: {
|
|
294
|
+
enabled: true,
|
|
295
|
+
sizing: { mode: "intrinsic-media" },
|
|
296
|
+
},
|
|
297
|
+
panels: {
|
|
298
|
+
controls: {
|
|
299
|
+
sections: [
|
|
300
|
+
{
|
|
301
|
+
controls: {
|
|
302
|
+
complexity: {
|
|
303
|
+
defaultValue: 4,
|
|
304
|
+
label: "Complexity",
|
|
305
|
+
max: 12,
|
|
306
|
+
min: 1,
|
|
307
|
+
performanceReason: "Complexity changes the number of render passes.",
|
|
308
|
+
performanceRole: "workload",
|
|
309
|
+
target: "render.complexity",
|
|
310
|
+
type: "slider",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
title: "Runtime Controls",
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(collectToolcraftPerformanceSensitiveControls(explicitWorkloadSchema)).toEqual([
|
|
321
|
+
expect.objectContaining({
|
|
322
|
+
controlId: "complexity",
|
|
323
|
+
target: "render.complexity",
|
|
324
|
+
}),
|
|
325
|
+
]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("collects visible controls without explicit performance classification", () => {
|
|
329
|
+
const unclassifiedSchema = defineToolcraft({
|
|
330
|
+
canvas: {
|
|
331
|
+
enabled: true,
|
|
332
|
+
sizing: { mode: "intrinsic-media" },
|
|
333
|
+
},
|
|
334
|
+
panels: {
|
|
335
|
+
controls: {
|
|
336
|
+
sections: [
|
|
337
|
+
{
|
|
338
|
+
controls: {
|
|
339
|
+
mode: {
|
|
340
|
+
defaultValue: "soft",
|
|
341
|
+
label: "Mode",
|
|
342
|
+
options: [
|
|
343
|
+
{ label: "Soft", value: "soft" },
|
|
344
|
+
{ label: "Sharp", value: "sharp" },
|
|
345
|
+
],
|
|
346
|
+
target: "render.mode",
|
|
347
|
+
type: "select",
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
title: "Runtime Controls",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
expect(collectToolcraftUnclassifiedPerformanceControls(unclassifiedSchema)).toEqual([
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
controlId: "mode",
|
|
360
|
+
target: "render.mode",
|
|
361
|
+
}),
|
|
362
|
+
]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("allows ordinary sliders to use lightweight responsiveness coverage", () => {
|
|
366
|
+
const config = defineToolcraftPerformance({
|
|
367
|
+
rendererStrategy: "none",
|
|
368
|
+
rendererWorkload: "none",
|
|
369
|
+
scenarios: [
|
|
370
|
+
{
|
|
371
|
+
automated: true,
|
|
372
|
+
automatedTestName: "perf: opacity drag stays responsive",
|
|
373
|
+
browser: true,
|
|
374
|
+
browserTestName: "browser perf: opacity drag stays responsive",
|
|
375
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
376
|
+
controlLabel: "Opacity",
|
|
377
|
+
expectedObservable: "Dragging Opacity stays responsive.",
|
|
378
|
+
fixture: "opacity responsiveness fixture",
|
|
379
|
+
id: "opacity-drag",
|
|
380
|
+
interaction: "control-drag",
|
|
381
|
+
target: "render.opacity",
|
|
382
|
+
workload: false,
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
usesCustomRenderer: false,
|
|
386
|
+
workloadTargets: [],
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(validateToolcraftPerformanceCoverage(ordinarySliderSchema, config)).toEqual([]);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("requires performance-sensitive controls to be listed in workloadTargets", () => {
|
|
393
|
+
const config = defineToolcraftPerformance({
|
|
394
|
+
rendererStrategy: "none",
|
|
395
|
+
rendererWorkload: "none",
|
|
396
|
+
scenarios: [
|
|
397
|
+
{
|
|
398
|
+
automated: true,
|
|
399
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
400
|
+
browser: true,
|
|
401
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
402
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
403
|
+
controlLabel: "Density",
|
|
404
|
+
expectedObservable: "Dragging Density stays responsive.",
|
|
405
|
+
fixture: "runtime density fixture",
|
|
406
|
+
id: "density-drag",
|
|
407
|
+
interaction: "control-drag",
|
|
408
|
+
target: "render.density",
|
|
409
|
+
workload: false,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
automated: true,
|
|
413
|
+
automatedTestName: "perf: mode change stays responsive",
|
|
414
|
+
browser: true,
|
|
415
|
+
browserTestName: "browser perf: mode change stays responsive",
|
|
416
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
417
|
+
controlLabel: "Mode",
|
|
418
|
+
expectedObservable: "Changing Mode updates the product without blocking the UI.",
|
|
419
|
+
fixture: "runtime mode fixture",
|
|
420
|
+
id: "mode-change",
|
|
421
|
+
interaction: "control-change",
|
|
422
|
+
target: "render.mode",
|
|
423
|
+
values: { default: "soft", max: "sharp", min: "soft" },
|
|
424
|
+
workload: false,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
usesCustomRenderer: false,
|
|
428
|
+
workloadTargets: [],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
432
|
+
"render.density is performance-sensitive and must be listed in workloadTargets with min/default/max workload coverage.",
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("rejects responsiveness role on semantically workload controls", () => {
|
|
437
|
+
const misclassifiedSchema = defineToolcraft({
|
|
438
|
+
canvas: {
|
|
439
|
+
enabled: true,
|
|
440
|
+
sizing: { mode: "intrinsic-media" },
|
|
441
|
+
},
|
|
442
|
+
panels: {
|
|
443
|
+
controls: {
|
|
444
|
+
sections: [
|
|
445
|
+
{
|
|
446
|
+
controls: {
|
|
447
|
+
density: {
|
|
448
|
+
defaultValue: 4,
|
|
449
|
+
label: "Density",
|
|
450
|
+
max: 12,
|
|
451
|
+
min: 1,
|
|
452
|
+
performanceReason: "AI incorrectly treated density as lightweight.",
|
|
453
|
+
performanceRole: "responsiveness",
|
|
454
|
+
target: "render.density",
|
|
455
|
+
type: "slider",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
title: "Runtime Controls",
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
const config = defineToolcraftPerformance({
|
|
465
|
+
rendererStrategy: "none",
|
|
466
|
+
rendererWorkload: "none",
|
|
467
|
+
scenarios: [
|
|
468
|
+
{
|
|
469
|
+
automated: true,
|
|
470
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
471
|
+
browser: true,
|
|
472
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
473
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
474
|
+
controlLabel: "Density",
|
|
475
|
+
expectedObservable: "Dragging Density updates the product without blocking the UI.",
|
|
476
|
+
fixture: "runtime density fixture",
|
|
477
|
+
id: "density-drag",
|
|
478
|
+
interaction: "control-drag",
|
|
479
|
+
target: "render.density",
|
|
480
|
+
values: { default: 4, max: 12, min: 1 },
|
|
481
|
+
workload: false,
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
usesCustomRenderer: false,
|
|
485
|
+
workloadTargets: [],
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
expect(validateToolcraftPerformanceCoverage(misclassifiedSchema, config)).toEqual(
|
|
489
|
+
expect.arrayContaining([
|
|
490
|
+
'density (render.density) looks performance-sensitive but declares performanceRole "responsiveness". Use performanceRole "workload" with workloadTargets and min/default/max coverage, or rename/restructure the control if it is truly lightweight.',
|
|
491
|
+
"render.density is performance-sensitive and must be listed in workloadTargets with min/default/max workload coverage.",
|
|
492
|
+
]),
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("validates coverage without app-local helper defaults", () => {
|
|
497
|
+
const config = defineToolcraftPerformance({
|
|
498
|
+
rendererStrategy: "none",
|
|
499
|
+
rendererWorkload: "none",
|
|
500
|
+
scenarios: [
|
|
501
|
+
{
|
|
502
|
+
automated: true,
|
|
503
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
504
|
+
browser: true,
|
|
505
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
506
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
507
|
+
controlLabel: "Density",
|
|
508
|
+
expectedObservable: "Dragging Density updates the product without blocking the UI.",
|
|
509
|
+
fixture: "runtime density fixture",
|
|
510
|
+
id: "density-drag",
|
|
511
|
+
interaction: "control-drag",
|
|
512
|
+
stressFixture: createMaxValueStressFixture(
|
|
513
|
+
12,
|
|
514
|
+
"Density max is the heaviest rendered output fixture.",
|
|
515
|
+
),
|
|
516
|
+
target: "render.density",
|
|
517
|
+
values: { default: 4, max: 12, min: 1 },
|
|
518
|
+
workload: true,
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
automated: true,
|
|
522
|
+
automatedTestName: "perf: mode change stays responsive",
|
|
523
|
+
browser: true,
|
|
524
|
+
browserTestName: "browser perf: mode change stays responsive",
|
|
525
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
526
|
+
controlLabel: "Mode",
|
|
527
|
+
expectedObservable: "Changing Mode updates the product without blocking the UI.",
|
|
528
|
+
fixture: "runtime mode fixture",
|
|
529
|
+
id: "mode-change",
|
|
530
|
+
interaction: "control-change",
|
|
531
|
+
target: "render.mode",
|
|
532
|
+
values: { default: "soft", max: "sharp", min: "soft" },
|
|
533
|
+
workload: false,
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
usesCustomRenderer: false,
|
|
537
|
+
workloadTargets: ["render.density"],
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("requires workload scenarios to declare a reusable stress fixture", () => {
|
|
544
|
+
const config = defineToolcraftPerformance({
|
|
545
|
+
rendererStrategy: "none",
|
|
546
|
+
rendererWorkload: "none",
|
|
547
|
+
scenarios: [
|
|
548
|
+
{
|
|
549
|
+
automated: true,
|
|
550
|
+
automatedTestName: "perf: content input stays responsive",
|
|
551
|
+
browser: true,
|
|
552
|
+
browserTestName: "browser perf: content input stays responsive",
|
|
553
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
554
|
+
controlLabel: "Content",
|
|
555
|
+
expectedObservable: "Large text content updates without blocking the UI.",
|
|
556
|
+
fixture: "large text content fixture",
|
|
557
|
+
id: "content-change",
|
|
558
|
+
interaction: "control-change",
|
|
559
|
+
target: "product.content",
|
|
560
|
+
values: { default: "", max: createLargeTextStressValue(), min: "" },
|
|
561
|
+
workload: true,
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
usesCustomRenderer: false,
|
|
565
|
+
workloadTargets: ["product.content"],
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toContain(
|
|
569
|
+
"content-change workload scenario must declare stressFixture with the real heaviest value used by browser performance tests.",
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("rejects short stress fixtures for large text workload scenarios", () => {
|
|
574
|
+
const config = defineToolcraftPerformance({
|
|
575
|
+
rendererStrategy: "none",
|
|
576
|
+
rendererWorkload: "none",
|
|
577
|
+
scenarios: [
|
|
578
|
+
{
|
|
579
|
+
automated: true,
|
|
580
|
+
automatedTestName: "perf: content input stays responsive",
|
|
581
|
+
browser: true,
|
|
582
|
+
browserTestName: "browser perf: content input stays responsive",
|
|
583
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
584
|
+
controlLabel: "Content",
|
|
585
|
+
expectedObservable: "Large text content updates without blocking the UI.",
|
|
586
|
+
fixture: "large text content fixture",
|
|
587
|
+
id: "content-change",
|
|
588
|
+
interaction: "control-change",
|
|
589
|
+
stressFixture: {
|
|
590
|
+
kind: "large-text",
|
|
591
|
+
minChars: 50_000,
|
|
592
|
+
minLines: 1_000,
|
|
593
|
+
reason: "Long multiline content is the heaviest realistic text workload.",
|
|
594
|
+
value: "short text",
|
|
595
|
+
},
|
|
596
|
+
target: "product.content",
|
|
597
|
+
values: { default: "", max: "short text", min: "" },
|
|
598
|
+
workload: true,
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
usesCustomRenderer: false,
|
|
602
|
+
workloadTargets: ["product.content"],
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual(
|
|
606
|
+
expect.arrayContaining([
|
|
607
|
+
"content-change large-text stressFixture.value must contain at least 50000 characters.",
|
|
608
|
+
"content-change large-text stressFixture.value must contain at least 1000 lines.",
|
|
609
|
+
]),
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("accepts large text workload scenarios with machine-checkable stress fixtures", () => {
|
|
614
|
+
const config = defineToolcraftPerformance({
|
|
615
|
+
rendererStrategy: "none",
|
|
616
|
+
rendererWorkload: "none",
|
|
617
|
+
scenarios: [
|
|
618
|
+
{
|
|
619
|
+
automated: true,
|
|
620
|
+
automatedTestName: "perf: content input stays responsive",
|
|
621
|
+
browser: true,
|
|
622
|
+
browserTestName: "browser perf: content input stays responsive",
|
|
623
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
624
|
+
controlLabel: "Content",
|
|
625
|
+
expectedObservable: "Large text content updates without blocking the UI.",
|
|
626
|
+
fixture: "large text content fixture",
|
|
627
|
+
id: "content-change",
|
|
628
|
+
interaction: "control-change",
|
|
629
|
+
stressFixture: createLargeTextStressFixture(),
|
|
630
|
+
target: "product.content",
|
|
631
|
+
values: { default: "", max: createLargeTextStressValue(), min: "" },
|
|
632
|
+
workload: true,
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
usesCustomRenderer: false,
|
|
636
|
+
workloadTargets: ["product.content"],
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
expect(validateToolcraftPerformanceCoverage(largeTextSchema, config)).toEqual([]);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("rejects visible controls without performance scenarios", () => {
|
|
643
|
+
const config = defineToolcraftPerformance({
|
|
644
|
+
rendererStrategy: "none",
|
|
645
|
+
rendererWorkload: "none",
|
|
646
|
+
scenarios: [
|
|
647
|
+
{
|
|
648
|
+
automated: true,
|
|
649
|
+
automatedTestName: "perf: density drag stays responsive",
|
|
650
|
+
browser: true,
|
|
651
|
+
browserTestName: "browser perf: density drag stays responsive",
|
|
652
|
+
budget: { maxFrameGapMs: 80, maxInteractionMs: 500 },
|
|
653
|
+
controlLabel: "Density",
|
|
654
|
+
expectedObservable: "Dragging Density updates the product without blocking the UI.",
|
|
655
|
+
fixture: "runtime density fixture",
|
|
656
|
+
id: "density-drag",
|
|
657
|
+
interaction: "control-drag",
|
|
658
|
+
target: "render.density",
|
|
659
|
+
values: { default: 4, max: 12, min: 1 },
|
|
660
|
+
workload: true,
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
usesCustomRenderer: false,
|
|
664
|
+
workloadTargets: ["render.density"],
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
668
|
+
"render.mode must have a performance scenario because every visible control can affect app responsiveness.",
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("allows text-output renderers without forcing a GPU strategy", () => {
|
|
673
|
+
const config = defineToolcraftPerformance({
|
|
674
|
+
rendererStrategy: "canvas-2d",
|
|
675
|
+
rendererTechnique: {
|
|
676
|
+
exportRenderer: "canvas-2d",
|
|
677
|
+
fidelityRisks: ["glyph metrics must stay crisp at product output size"],
|
|
678
|
+
performanceRisks: ["large glyph grids can be expensive during drag"],
|
|
679
|
+
previewRenderer: "canvas-2d",
|
|
680
|
+
productRepresentation: "text",
|
|
681
|
+
rendererStrategy: "canvas-2d",
|
|
682
|
+
rendererWorkload: "text-output",
|
|
683
|
+
sourceRepresentation: "dom-text",
|
|
684
|
+
whyNotAlternativeStrategies: ["webgl texture output would rasterize text too early"],
|
|
685
|
+
},
|
|
686
|
+
rendererWorkload: "text-output",
|
|
687
|
+
scenarios: createTextRendererScenarios(),
|
|
688
|
+
usesCustomRenderer: true,
|
|
689
|
+
workloadTargets: ["render.density"],
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("requires animated custom renderers to test viewport dragging during animation", () => {
|
|
696
|
+
const config = createAnimatedVectorRendererConfig({ includeViewportDrag: false });
|
|
697
|
+
|
|
698
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
699
|
+
"Animated custom renderers must include an animation-viewport-drag performance scenario that samples frames while physically moving the canvas viewport.",
|
|
700
|
+
);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("requires detail-heavy custom renderers to test real viewport zoom stress", () => {
|
|
704
|
+
const config = defineToolcraftPerformance({
|
|
705
|
+
rendererStrategy: "canvas-2d",
|
|
706
|
+
rendererTechnique: {
|
|
707
|
+
exportRenderer: "canvas-2d",
|
|
708
|
+
fidelityRisks: ["glyph metrics must stay crisp at product output size"],
|
|
709
|
+
performanceRisks: ["large glyph grids can shake or jank during canvas zoom"],
|
|
710
|
+
previewRenderer: "canvas-2d",
|
|
711
|
+
productRepresentation: "text",
|
|
712
|
+
rendererStrategy: "canvas-2d",
|
|
713
|
+
rendererWorkload: "text-output",
|
|
714
|
+
sourceRepresentation: "dom-text",
|
|
715
|
+
whyNotAlternativeStrategies: ["webgl texture output would rasterize text too early"],
|
|
716
|
+
},
|
|
717
|
+
rendererWorkload: "text-output",
|
|
718
|
+
scenarios: createTextRendererScenarios({ includeViewportZoomStress: false }),
|
|
719
|
+
usesCustomRenderer: true,
|
|
720
|
+
workloadTargets: ["render.density"],
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toContain(
|
|
724
|
+
"Detail-heavy or animated custom renderers must include a viewport-zoom-stress performance scenario that uses real zoom controls while sampling frame gaps and long tasks.",
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("requires detail-heavy custom renderers to prove worst-case preview or animation stress", () => {
|
|
729
|
+
const config = defineToolcraftPerformance({
|
|
730
|
+
rendererStrategy: "canvas-2d",
|
|
731
|
+
rendererTechnique: {
|
|
732
|
+
exportRenderer: "canvas-2d",
|
|
733
|
+
fidelityRisks: ["dense texture output must stay visually stable"],
|
|
734
|
+
layers: [
|
|
735
|
+
{
|
|
736
|
+
content: ["dense-pattern"],
|
|
737
|
+
exportMode: "included",
|
|
738
|
+
id: "dense-pattern",
|
|
739
|
+
kind: "product-foreground",
|
|
740
|
+
primitiveCount: "high",
|
|
741
|
+
renderer: "canvas-2d",
|
|
742
|
+
uiSelector: '[data-toolcraft-renderer-layer="dense-pattern"]',
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
content: ["geometry"],
|
|
746
|
+
exportMode: "included",
|
|
747
|
+
id: "foreground-geometry",
|
|
748
|
+
kind: "product-foreground",
|
|
749
|
+
primitiveCount: "low",
|
|
750
|
+
renderer: "svg",
|
|
751
|
+
uiSelector: '[data-toolcraft-renderer-layer="foreground-geometry"]',
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
performanceRisks: ["maximum density may overload the renderer"],
|
|
755
|
+
previewRenderer: "canvas-2d",
|
|
756
|
+
productRepresentation: "mixed",
|
|
757
|
+
rendererStrategy: "canvas-2d",
|
|
758
|
+
rendererWorkload: "simple-composition",
|
|
759
|
+
sourceRepresentation: "procedural-data",
|
|
760
|
+
whyNotAlternativeStrategies: ["canvas is simple"],
|
|
761
|
+
},
|
|
762
|
+
rendererWorkload: "simple-composition",
|
|
763
|
+
scenarios: createTextRendererScenarios({ includeStressPreview: false }),
|
|
764
|
+
usesCustomRenderer: true,
|
|
765
|
+
workloadTargets: ["render.density"],
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
769
|
+
expect.arrayContaining([
|
|
770
|
+
"Detail-heavy custom renderers must include a stress preview-render or animation-frame scenario for the largest product canvas and heaviest workload values.",
|
|
771
|
+
]),
|
|
772
|
+
);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it("requires high-count Canvas 2D semantic or dense layers to carry measurable stress evidence", () => {
|
|
776
|
+
const config = defineToolcraftPerformance({
|
|
777
|
+
rendererStrategy: "canvas-2d",
|
|
778
|
+
rendererTechnique: {
|
|
779
|
+
exportRenderer: "canvas-2d",
|
|
780
|
+
fidelityRisks: ["glyphs must stay legible"],
|
|
781
|
+
layers: [
|
|
782
|
+
{
|
|
783
|
+
content: ["text", "dense-pattern"],
|
|
784
|
+
exportMode: "included",
|
|
785
|
+
id: "dense-text-field",
|
|
786
|
+
intentionalRasterizationReason:
|
|
787
|
+
"The product exports raster video and PNG, so text is drawn into one canvas frame.",
|
|
788
|
+
kind: "product-foreground",
|
|
789
|
+
primitiveCount: "high",
|
|
790
|
+
renderer: "canvas-2d",
|
|
791
|
+
uiSelector: '[data-toolcraft-renderer-layer="dense-text-field"]',
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
performanceRisks: ["maximum density may redraw many text runs per frame"],
|
|
795
|
+
previewRenderer: "canvas-2d",
|
|
796
|
+
productRepresentation: "text",
|
|
797
|
+
rendererStrategy: "canvas-2d",
|
|
798
|
+
rendererWorkload: "text-output",
|
|
799
|
+
sourceRepresentation: "dom-text",
|
|
800
|
+
whyNotAlternativeStrategies: ["webgl texture output would rasterize text too early"],
|
|
801
|
+
},
|
|
802
|
+
rendererWorkload: "text-output",
|
|
803
|
+
scenarios: createTextRendererScenarios({ includeStressPreview: false }),
|
|
804
|
+
usesCustomRenderer: true,
|
|
805
|
+
workloadTargets: ["render.density"],
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
809
|
+
expect.arrayContaining([
|
|
810
|
+
"High-count Canvas 2D renderer layers must include stress preview-render or animation-frame evidence before delivery. If that stress evidence fails, revise renderer strategy instead of only lowering product workload.",
|
|
811
|
+
]),
|
|
812
|
+
);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it("accepts animated custom renderers with stress coverage while dragging the viewport", () => {
|
|
816
|
+
const config = createAnimatedVectorRendererConfig({ includeViewportDrag: true });
|
|
817
|
+
|
|
818
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it("requires custom renderers to declare typed renderer technique metadata", () => {
|
|
822
|
+
const config = defineToolcraftPerformance({
|
|
823
|
+
rendererStrategy: "canvas-2d",
|
|
824
|
+
rendererWorkload: "text-output",
|
|
825
|
+
scenarios: createTextRendererScenarios(),
|
|
826
|
+
usesCustomRenderer: true,
|
|
827
|
+
workloadTargets: ["render.density"],
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
831
|
+
expect.arrayContaining([
|
|
832
|
+
"Custom renderers must declare rendererTechnique so renderer choice is machine-checkable.",
|
|
833
|
+
]),
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it("rejects renderer technique mismatches and missing explanations", () => {
|
|
838
|
+
const config = defineToolcraftPerformance({
|
|
839
|
+
rendererStrategy: "canvas-2d",
|
|
840
|
+
rendererTechnique: {
|
|
841
|
+
exportRenderer: "webgl",
|
|
842
|
+
fidelityRisks: [],
|
|
843
|
+
performanceRisks: [],
|
|
844
|
+
previewRenderer: "canvas-2d",
|
|
845
|
+
productRepresentation: "text",
|
|
846
|
+
rendererStrategy: "webgl",
|
|
847
|
+
rendererWorkload: "pixel-output",
|
|
848
|
+
sourceRepresentation: "reference-runtime",
|
|
849
|
+
whyNotAlternativeStrategies: [],
|
|
850
|
+
},
|
|
851
|
+
rendererWorkload: "text-output",
|
|
852
|
+
scenarios: createTextRendererScenarios(),
|
|
853
|
+
usesCustomRenderer: true,
|
|
854
|
+
workloadTargets: ["render.density"],
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
858
|
+
expect.arrayContaining([
|
|
859
|
+
'rendererTechnique.rendererWorkload "pixel-output" must match rendererWorkload "text-output".',
|
|
860
|
+
'rendererTechnique.rendererStrategy "webgl" must match rendererStrategy "canvas-2d".',
|
|
861
|
+
"Custom renderer technique must explain why alternative renderer strategies were rejected.",
|
|
862
|
+
"Custom renderer technique must list fidelity risks.",
|
|
863
|
+
"Custom renderer technique must list performance risks.",
|
|
864
|
+
'productRepresentation "text" requires rendererWorkload "text-output" unless intentionalRasterizationReason is provided.',
|
|
865
|
+
"Different preview/export renderers require previewExportDifferenceReason.",
|
|
866
|
+
"Reference runtime renderer changes require referenceRendererChangeReason.",
|
|
867
|
+
]),
|
|
868
|
+
);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it("allows dense raster backgrounds with semantic svg foreground layers", () => {
|
|
872
|
+
const config = defineToolcraftPerformance({
|
|
873
|
+
rendererStrategy: "canvas-2d",
|
|
874
|
+
rendererTechnique: {
|
|
875
|
+
exportRenderer: "canvas-2d",
|
|
876
|
+
fidelityRisks: ["export composite must preserve foreground line and text edges"],
|
|
877
|
+
layers: [
|
|
878
|
+
{
|
|
879
|
+
content: ["dense-pattern"],
|
|
880
|
+
exportMode: "included",
|
|
881
|
+
id: "dot-grid-background",
|
|
882
|
+
kind: "background",
|
|
883
|
+
primitiveCount: "high",
|
|
884
|
+
renderer: "canvas-2d",
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
content: ["geometry", "text"],
|
|
888
|
+
exportMode: "included",
|
|
889
|
+
id: "semantic-foreground",
|
|
890
|
+
kind: "product-foreground",
|
|
891
|
+
primitiveCount: "low",
|
|
892
|
+
renderer: "svg",
|
|
893
|
+
uiSelector: '[data-toolcraft-renderer-layer="semantic-foreground"]',
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
content: ["composite"],
|
|
897
|
+
exportMode: "composited",
|
|
898
|
+
id: "png-export",
|
|
899
|
+
kind: "export-composite",
|
|
900
|
+
primitiveCount: "low",
|
|
901
|
+
renderer: "canvas-2d",
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
performanceRisks: ["dense background redraw can be expensive at large output size"],
|
|
905
|
+
previewRenderer: "canvas-2d",
|
|
906
|
+
productRepresentation: "mixed",
|
|
907
|
+
rendererStrategy: "canvas-2d",
|
|
908
|
+
rendererWorkload: "simple-composition",
|
|
909
|
+
sourceRepresentation: "procedural-data",
|
|
910
|
+
whyNotAlternativeStrategies: [
|
|
911
|
+
"rendering thousands of background dots as DOM nodes would be expensive",
|
|
912
|
+
],
|
|
913
|
+
},
|
|
914
|
+
rendererWorkload: "simple-composition",
|
|
915
|
+
scenarios: createTextRendererScenarios(),
|
|
916
|
+
usesCustomRenderer: true,
|
|
917
|
+
workloadTargets: ["render.density"],
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual([]);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it("rejects canvas rasterization of low-count semantic foreground without a reason", () => {
|
|
924
|
+
const config = defineToolcraftPerformance({
|
|
925
|
+
rendererStrategy: "canvas-2d",
|
|
926
|
+
rendererTechnique: {
|
|
927
|
+
exportRenderer: "canvas-2d",
|
|
928
|
+
fidelityRisks: ["foreground line edges can become soft"],
|
|
929
|
+
layers: [
|
|
930
|
+
{
|
|
931
|
+
content: ["dense-pattern"],
|
|
932
|
+
exportMode: "included",
|
|
933
|
+
id: "dot-grid-background",
|
|
934
|
+
kind: "background",
|
|
935
|
+
primitiveCount: "high",
|
|
936
|
+
renderer: "canvas-2d",
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
content: ["geometry", "text"],
|
|
940
|
+
exportMode: "included",
|
|
941
|
+
id: "semantic-foreground",
|
|
942
|
+
kind: "product-foreground",
|
|
943
|
+
primitiveCount: "low",
|
|
944
|
+
renderer: "canvas-2d",
|
|
945
|
+
},
|
|
946
|
+
],
|
|
947
|
+
performanceRisks: ["background redraw can be expensive"],
|
|
948
|
+
previewRenderer: "canvas-2d",
|
|
949
|
+
productRepresentation: "mixed",
|
|
950
|
+
rendererStrategy: "canvas-2d",
|
|
951
|
+
rendererWorkload: "simple-composition",
|
|
952
|
+
sourceRepresentation: "procedural-data",
|
|
953
|
+
whyNotAlternativeStrategies: ["canvas is convenient"],
|
|
954
|
+
},
|
|
955
|
+
rendererWorkload: "simple-composition",
|
|
956
|
+
scenarios: createTextRendererScenarios(),
|
|
957
|
+
usesCustomRenderer: true,
|
|
958
|
+
workloadTargets: ["render.density"],
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
962
|
+
expect.arrayContaining([
|
|
963
|
+
'rendererTechnique layer "semantic-foreground" uses canvas-2d for low-count semantic geometry/text. Use dom/svg for semantic foreground or provide intentionalRasterizationReason.',
|
|
964
|
+
]),
|
|
965
|
+
);
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
it("requires visible foreground and handle layers to declare browser selectors", () => {
|
|
969
|
+
const config = defineToolcraftPerformance({
|
|
970
|
+
rendererStrategy: "canvas-2d",
|
|
971
|
+
rendererTechnique: {
|
|
972
|
+
exportRenderer: "canvas-2d",
|
|
973
|
+
fidelityRisks: ["foreground and handles must be verified in browser tests"],
|
|
974
|
+
layers: [
|
|
975
|
+
{
|
|
976
|
+
content: ["dense-pattern"],
|
|
977
|
+
exportMode: "included",
|
|
978
|
+
id: "dot-grid-background",
|
|
979
|
+
kind: "background",
|
|
980
|
+
primitiveCount: "high",
|
|
981
|
+
renderer: "canvas-2d",
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
content: ["geometry", "text"],
|
|
985
|
+
exportMode: "included",
|
|
986
|
+
id: "semantic-foreground",
|
|
987
|
+
kind: "product-foreground",
|
|
988
|
+
primitiveCount: "low",
|
|
989
|
+
renderer: "svg",
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
content: ["handles"],
|
|
993
|
+
exportMode: "excluded",
|
|
994
|
+
id: "focus-handles",
|
|
995
|
+
kind: "editing-handles",
|
|
996
|
+
primitiveCount: "low",
|
|
997
|
+
renderer: "svg",
|
|
998
|
+
},
|
|
999
|
+
],
|
|
1000
|
+
performanceRisks: ["background redraw can be expensive"],
|
|
1001
|
+
previewRenderer: "canvas-2d",
|
|
1002
|
+
productRepresentation: "mixed",
|
|
1003
|
+
rendererStrategy: "canvas-2d",
|
|
1004
|
+
rendererWorkload: "simple-composition",
|
|
1005
|
+
sourceRepresentation: "procedural-data",
|
|
1006
|
+
whyNotAlternativeStrategies: ["background dots are too dense for DOM"],
|
|
1007
|
+
},
|
|
1008
|
+
rendererWorkload: "simple-composition",
|
|
1009
|
+
scenarios: createTextRendererScenarios(),
|
|
1010
|
+
usesCustomRenderer: true,
|
|
1011
|
+
workloadTargets: ["render.density"],
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
1015
|
+
expect.arrayContaining([
|
|
1016
|
+
'rendererTechnique layer "semantic-foreground" is product-foreground and must declare uiSelector so browser tests can verify the visible renderer layer.',
|
|
1017
|
+
'rendererTechnique layer "focus-handles" is editing-handles and must declare uiSelector so browser tests can verify the visible renderer layer.',
|
|
1018
|
+
]),
|
|
1019
|
+
);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
it("rejects mixed product representation without a real layer inventory", () => {
|
|
1023
|
+
const config = defineToolcraftPerformance({
|
|
1024
|
+
rendererStrategy: "canvas-2d",
|
|
1025
|
+
rendererTechnique: {
|
|
1026
|
+
exportRenderer: "canvas-2d",
|
|
1027
|
+
fidelityRisks: ["mixed representation must be proven by layers"],
|
|
1028
|
+
layers: [
|
|
1029
|
+
{
|
|
1030
|
+
content: ["dense-pattern"],
|
|
1031
|
+
exportMode: "included",
|
|
1032
|
+
id: "background",
|
|
1033
|
+
kind: "background",
|
|
1034
|
+
primitiveCount: "high",
|
|
1035
|
+
renderer: "canvas-2d",
|
|
1036
|
+
},
|
|
1037
|
+
],
|
|
1038
|
+
performanceRisks: ["dense redraw can be expensive"],
|
|
1039
|
+
previewRenderer: "canvas-2d",
|
|
1040
|
+
productRepresentation: "mixed",
|
|
1041
|
+
rendererStrategy: "canvas-2d",
|
|
1042
|
+
rendererWorkload: "simple-composition",
|
|
1043
|
+
sourceRepresentation: "procedural-data",
|
|
1044
|
+
whyNotAlternativeStrategies: ["canvas is faster than DOM for many dots"],
|
|
1045
|
+
},
|
|
1046
|
+
rendererWorkload: "simple-composition",
|
|
1047
|
+
scenarios: createTextRendererScenarios(),
|
|
1048
|
+
usesCustomRenderer: true,
|
|
1049
|
+
workloadTargets: ["render.density"],
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
expect(validateToolcraftPerformanceCoverage(testSchema, config)).toEqual(
|
|
1053
|
+
expect.arrayContaining([
|
|
1054
|
+
'productRepresentation "mixed" requires rendererTechnique.layers with at least two different content families.',
|
|
1055
|
+
]),
|
|
1056
|
+
);
|
|
1057
|
+
});
|
|
1058
|
+
});
|