@pixel-point/toolcraft 0.0.9 → 0.0.11
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/README.md +42 -9
- package/package.json +1 -1
- package/src/generate.mjs +14 -6
- package/src/generate.test.mjs +28 -0
- package/templates/runtime/contracts/component-contracts.test.ts +148 -34
- package/templates/runtime/contracts/component-contracts.ts +79 -38
- package/templates/runtime/contracts/decision-contracts.test.ts +33 -1
- package/templates/runtime/contracts/decision-contracts.ts +19 -6
- package/templates/runtime/export/export.test.ts +63 -0
- package/templates/runtime/export/export.ts +55 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/canvas-shell.test.tsx +58 -4
- package/templates/runtime/react/canvas-shell.tsx +31 -9
- package/templates/runtime/react/controls-panel.test.tsx +474 -27
- package/templates/runtime/react/controls-panel.tsx +71 -26
- package/templates/runtime/react/runtime-public-api.test.tsx +1 -1
- package/templates/runtime/react/settings-transfer.test.ts +4 -0
- package/templates/runtime/react/settings-transfer.ts +6 -1
- package/templates/runtime/react/timeline-panel.test.tsx +14 -0
- package/templates/runtime/react/timeline-panel.tsx +44 -7
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +9 -1
- package/templates/runtime/react/toolcraft-app.test.tsx +112 -3
- package/templates/runtime/react/toolcraft-app.tsx +56 -37
- package/templates/runtime/schema/define-toolcraft.test.ts +225 -173
- package/templates/runtime/schema/define-toolcraft.ts +117 -247
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +41 -0
- package/templates/runtime/state/create-template-state.test.ts +156 -0
- package/templates/runtime/state/create-template-state.ts +38 -8
- package/templates/runtime/state/media-defaults.ts +105 -0
- package/templates/runtime/state/persistence.test.ts +58 -0
- package/templates/runtime/state/persistence.ts +105 -1
- package/templates/runtime/state/reducer.test.ts +280 -4
- package/templates/runtime/state/reducer.ts +195 -9
- package/templates/runtime/state/timeline-loop.test.ts +71 -0
- package/templates/runtime/state/timeline-loop.ts +35 -0
- package/templates/runtime/state/types.ts +27 -0
- package/templates/runtime/testing/performance.test.ts +657 -2
- package/templates/runtime/testing/performance.ts +789 -49
- package/templates/starter/AGENTS.md +22 -16
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +38 -7
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +51 -20
- package/templates/starter/docs/toolcraft/component-rules.md +49 -37
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +30 -10
- package/templates/starter/docs/toolcraft/schema-reference.md +134 -33
- package/templates/starter/docs/toolcraft/workflow.md +5 -2
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +3 -3
- package/templates/starter/e2e/app-performance.spec.ts +55 -2
- package/templates/starter/e2e/performance-helpers.ts +45 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/playwright.config.ts +1 -1
- package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +82 -16
- package/templates/starter/scripts/toolcraft-port.mjs +178 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +147 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +2573 -313
- package/templates/starter/src/app/starter-acceptance.ts +978 -81
- package/templates/starter/src/app/starter-performance.test.ts +111 -7
- package/templates/starter/src/app/starter-performance.ts +5 -0
- package/templates/starter/src/app/starter-schema.test.ts +32 -7
- package/templates/starter/src/app/starter-schema.ts +6 -2
- package/templates/starter/vite.config.ts +58 -2
- package/templates/ui/components/controls/actions/actions-control.tsx +46 -3
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +2 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +4 -26
- package/templates/ui/components/controls/vector/vector-control.tsx +25 -4
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { toolcraftRuntimeCommandTypes } from "../state/types";
|
|
4
|
-
import {
|
|
5
|
-
defineToolcraft,
|
|
6
|
-
getToolcraftSettingsTransferEligibility,
|
|
7
|
-
} from "./define-toolcraft";
|
|
4
|
+
import { defineToolcraft } from "./define-toolcraft";
|
|
8
5
|
import {
|
|
9
6
|
toolcraftReservedTargets,
|
|
10
7
|
toolcraftRuntimeOwnedTargets,
|
|
@@ -27,17 +24,8 @@ describe("defineToolcraft", () => {
|
|
|
27
24
|
);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
function
|
|
31
|
-
return
|
|
32
|
-
controls: {
|
|
33
|
-
opacity: {
|
|
34
|
-
defaultValue: index,
|
|
35
|
-
target: `section${index}.opacity`,
|
|
36
|
-
type: "slider",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
title: `Section ${index + 1}`,
|
|
40
|
-
}));
|
|
27
|
+
function getProductSections(app: ReturnType<typeof defineToolcraft>) {
|
|
28
|
+
return app.panels.controls?.sections.filter((section) => section.title !== "Setup") ?? [];
|
|
41
29
|
}
|
|
42
30
|
|
|
43
31
|
it("defaults enabled canvas to draggable with canvas toolbar features", () => {
|
|
@@ -53,6 +41,30 @@ describe("defineToolcraft", () => {
|
|
|
53
41
|
expect(app.toolbar).toEqual({ history: true, radar: true, theme: true, zoom: true });
|
|
54
42
|
});
|
|
55
43
|
|
|
44
|
+
it("defaults upload canvases to editable output unless intrinsic media is explicit", () => {
|
|
45
|
+
const app = defineToolcraft({
|
|
46
|
+
canvas: { enabled: true, upload: true },
|
|
47
|
+
panels: {},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(app.canvas.size).toEqual({ height: 1080, unit: "px", width: 1920 });
|
|
51
|
+
expect(app.canvas.sizeSource).toBe("runtime-default");
|
|
52
|
+
expect(app.canvas.sizing).toEqual({ mode: "editable-output" });
|
|
53
|
+
expect(app.assembly.capabilities).toContain("canvas.editableSize");
|
|
54
|
+
expect(app.assembly.commands).toContain("canvas.setSize");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("preserves explicit intrinsic media sizing for source-native upload apps", () => {
|
|
58
|
+
const app = defineToolcraft({
|
|
59
|
+
canvas: { enabled: true, sizing: { mode: "intrinsic-media" }, upload: true },
|
|
60
|
+
panels: {},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(app.canvas.sizing).toEqual({ mode: "intrinsic-media" });
|
|
64
|
+
expect(app.assembly.capabilities).not.toContain("canvas.editableSize");
|
|
65
|
+
expect(app.assembly.commands).not.toContain("canvas.setSize");
|
|
66
|
+
});
|
|
67
|
+
|
|
56
68
|
it("disables app state persistence by default", () => {
|
|
57
69
|
const app = defineToolcraft({
|
|
58
70
|
canvas: { enabled: true },
|
|
@@ -82,7 +94,7 @@ describe("defineToolcraft", () => {
|
|
|
82
94
|
});
|
|
83
95
|
});
|
|
84
96
|
|
|
85
|
-
it("keeps settings transfer
|
|
97
|
+
it("keeps settings transfer visible for small auto schemas", () => {
|
|
86
98
|
const app = defineToolcraft({
|
|
87
99
|
canvas: { enabled: true, size: { height: 320, unit: "px", width: 320 } },
|
|
88
100
|
panels: {
|
|
@@ -107,11 +119,15 @@ describe("defineToolcraft", () => {
|
|
|
107
119
|
|
|
108
120
|
expect(app.settingsTransfer).toMatchObject({
|
|
109
121
|
appId: "mini-app",
|
|
110
|
-
enabled:
|
|
122
|
+
enabled: true,
|
|
111
123
|
fileName: "mini-app-settings.json",
|
|
112
124
|
mode: "auto",
|
|
113
125
|
});
|
|
114
126
|
expect(app.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
127
|
+
expect(app.panels.controls?.sections[0]?.controls.settingsTransfer).toMatchObject({
|
|
128
|
+
target: "runtime.settingsTransfer",
|
|
129
|
+
type: "settingsTransfer",
|
|
130
|
+
});
|
|
115
131
|
expect(app.panels.controls?.sections[0]?.controls.canvasAspectRatio).toMatchObject({
|
|
116
132
|
target: "canvas.aspectRatio",
|
|
117
133
|
type: "aspectRatio",
|
|
@@ -119,124 +135,7 @@ describe("defineToolcraft", () => {
|
|
|
119
135
|
expect(app.panels.controls?.sections[0]?.controls.canvasWidth).toBeTruthy();
|
|
120
136
|
});
|
|
121
137
|
|
|
122
|
-
it("
|
|
123
|
-
const small = getToolcraftSettingsTransferEligibility({
|
|
124
|
-
panels: {
|
|
125
|
-
controls: {
|
|
126
|
-
sections: [
|
|
127
|
-
{
|
|
128
|
-
controls: createSliderControls(1),
|
|
129
|
-
title: "Small",
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
title: "Small App",
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
const manyControls = getToolcraftSettingsTransferEligibility({
|
|
137
|
-
panels: {
|
|
138
|
-
controls: {
|
|
139
|
-
sections: [
|
|
140
|
-
{
|
|
141
|
-
controls: createSliderControls(12),
|
|
142
|
-
title: "Many Controls",
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
title: "Many Controls App",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
const manySections = getToolcraftSettingsTransferEligibility({
|
|
150
|
-
panels: {
|
|
151
|
-
controls: {
|
|
152
|
-
sections: createSections(5),
|
|
153
|
-
title: "Many Sections App",
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
const heavyControls = getToolcraftSettingsTransferEligibility({
|
|
158
|
-
panels: {
|
|
159
|
-
controls: {
|
|
160
|
-
sections: [
|
|
161
|
-
{
|
|
162
|
-
controls: Object.fromEntries(
|
|
163
|
-
Array.from({ length: 6 }, (_, index) => [
|
|
164
|
-
`gradient${index}`,
|
|
165
|
-
{
|
|
166
|
-
label: `Gradient ${index + 1}`,
|
|
167
|
-
target: `gradient${index}`,
|
|
168
|
-
type: "gradient",
|
|
169
|
-
},
|
|
170
|
-
]),
|
|
171
|
-
),
|
|
172
|
-
title: "Gradients",
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
title: "Heavy Controls App",
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
const timelineLayersScore = getToolcraftSettingsTransferEligibility({
|
|
180
|
-
panels: {
|
|
181
|
-
controls: {
|
|
182
|
-
sections: [
|
|
183
|
-
{
|
|
184
|
-
controls: Object.fromEntries(
|
|
185
|
-
Array.from({ length: 5 }, (_, index) => [
|
|
186
|
-
`gradient${index}`,
|
|
187
|
-
{
|
|
188
|
-
label: `Gradient ${index + 1}`,
|
|
189
|
-
target: `gradient${index}`,
|
|
190
|
-
type: "gradient",
|
|
191
|
-
},
|
|
192
|
-
]),
|
|
193
|
-
),
|
|
194
|
-
title: "Gradients",
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
title: "Timeline Layers App",
|
|
198
|
-
},
|
|
199
|
-
layers: true,
|
|
200
|
-
timeline: { mode: "playback" },
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
expect(small).toEqual({
|
|
205
|
-
controlCount: 1,
|
|
206
|
-
eligible: false,
|
|
207
|
-
reasons: [],
|
|
208
|
-
score: 1,
|
|
209
|
-
sectionCount: 1,
|
|
210
|
-
});
|
|
211
|
-
expect(manyControls).toMatchObject({
|
|
212
|
-
controlCount: 12,
|
|
213
|
-
eligible: true,
|
|
214
|
-
reasons: ["control-count"],
|
|
215
|
-
sectionCount: 1,
|
|
216
|
-
});
|
|
217
|
-
expect(manySections).toMatchObject({
|
|
218
|
-
controlCount: 5,
|
|
219
|
-
eligible: true,
|
|
220
|
-
reasons: ["section-count"],
|
|
221
|
-
sectionCount: 5,
|
|
222
|
-
});
|
|
223
|
-
expect(heavyControls).toMatchObject({
|
|
224
|
-
controlCount: 6,
|
|
225
|
-
eligible: true,
|
|
226
|
-
reasons: ["score"],
|
|
227
|
-
score: 18,
|
|
228
|
-
sectionCount: 1,
|
|
229
|
-
});
|
|
230
|
-
expect(timelineLayersScore).toMatchObject({
|
|
231
|
-
controlCount: 5,
|
|
232
|
-
eligible: true,
|
|
233
|
-
reasons: ["score"],
|
|
234
|
-
score: 19,
|
|
235
|
-
sectionCount: 1,
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("ignores runtime canvas setup controls in settings transfer eligibility", () => {
|
|
138
|
+
it("keeps runtime setup controls outside product sections", () => {
|
|
240
139
|
const app = defineToolcraft({
|
|
241
140
|
canvas: { enabled: true, size: { height: 720, unit: "px", width: 1280 } },
|
|
242
141
|
panels: {
|
|
@@ -253,16 +152,16 @@ describe("defineToolcraft", () => {
|
|
|
253
152
|
settingsTransfer: false,
|
|
254
153
|
});
|
|
255
154
|
|
|
155
|
+
expect(app.settingsTransfer.enabled).toBe(true);
|
|
156
|
+
expect(app.panels.controls?.sections[0]?.controls.settingsTransfer).toMatchObject({
|
|
157
|
+
target: "runtime.settingsTransfer",
|
|
158
|
+
type: "settingsTransfer",
|
|
159
|
+
});
|
|
256
160
|
expect(app.panels.controls?.sections[0]?.controls.canvasAspectRatio).toBeTruthy();
|
|
257
161
|
expect(app.panels.controls?.sections[0]?.controls.canvasWidth).toBeTruthy();
|
|
258
162
|
expect(app.panels.controls?.sections[0]?.controls.canvasHeight).toBeTruthy();
|
|
259
|
-
expect(
|
|
260
|
-
|
|
261
|
-
eligible: false,
|
|
262
|
-
reasons: [],
|
|
263
|
-
score: 10,
|
|
264
|
-
sectionCount: 1,
|
|
265
|
-
});
|
|
163
|
+
expect(getProductSections(app)).toHaveLength(1);
|
|
164
|
+
expect(getProductSections(app)[0]?.title).toBe("Product Controls");
|
|
266
165
|
});
|
|
267
166
|
|
|
268
167
|
it("injects settings transfer as the first section for complex auto schemas", () => {
|
|
@@ -335,6 +234,7 @@ describe("defineToolcraft", () => {
|
|
|
335
234
|
"canvasAspectRatio",
|
|
336
235
|
"canvasWidth",
|
|
337
236
|
"canvasHeight",
|
|
237
|
+
"timelineExtended",
|
|
338
238
|
]);
|
|
339
239
|
expect(runtimeSettingsSection?.controls.canvasAspectRatio).toMatchObject({
|
|
340
240
|
defaultValue: {
|
|
@@ -362,7 +262,7 @@ describe("defineToolcraft", () => {
|
|
|
362
262
|
expect(firstProductSection?.title).toBe("Text");
|
|
363
263
|
});
|
|
364
264
|
|
|
365
|
-
it("
|
|
265
|
+
it("uses explicit settings transfer naming while keeping the setup controls mandatory", () => {
|
|
366
266
|
const forced = defineToolcraft({
|
|
367
267
|
canvas: { enabled: false },
|
|
368
268
|
panels: {
|
|
@@ -410,10 +310,13 @@ describe("defineToolcraft", () => {
|
|
|
410
310
|
expect(forced.panels.controls?.sections[0]?.controls.settingsTransfer?.type).toBe(
|
|
411
311
|
"settingsTransfer",
|
|
412
312
|
);
|
|
413
|
-
expect(disabled.settingsTransfer
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
)
|
|
313
|
+
expect(disabled.settingsTransfer).toMatchObject({
|
|
314
|
+
enabled: true,
|
|
315
|
+
mode: false,
|
|
316
|
+
});
|
|
317
|
+
expect(disabled.panels.controls?.sections[0]?.controls.settingsTransfer?.type).toBe(
|
|
318
|
+
"settingsTransfer",
|
|
319
|
+
);
|
|
417
320
|
});
|
|
418
321
|
|
|
419
322
|
it("preserves conditional visibility on sections and controls", () => {
|
|
@@ -445,7 +348,7 @@ describe("defineToolcraft", () => {
|
|
|
445
348
|
},
|
|
446
349
|
});
|
|
447
350
|
|
|
448
|
-
const [section] = app
|
|
351
|
+
const [section] = getProductSections(app);
|
|
449
352
|
|
|
450
353
|
expect(section?.visibleWhen).toEqual({
|
|
451
354
|
equals: "co-brand",
|
|
@@ -540,6 +443,7 @@ describe("defineToolcraft", () => {
|
|
|
540
443
|
"media.delete",
|
|
541
444
|
"media.import",
|
|
542
445
|
"media.reorder",
|
|
446
|
+
"media.transform",
|
|
543
447
|
"layers.reorder",
|
|
544
448
|
"timeline.setCurrentTime",
|
|
545
449
|
"canvas.center",
|
|
@@ -548,7 +452,11 @@ describe("defineToolcraft", () => {
|
|
|
548
452
|
);
|
|
549
453
|
expect(app.assembly.surfaces.panels.layers?.defaultPlacement).toBe("left");
|
|
550
454
|
expect(app.assembly.surfaces.panels.timeline?.snapEdges).toEqual(["top", "bottom"]);
|
|
551
|
-
expect(app.panels.timeline).toEqual({
|
|
455
|
+
expect(app.panels.timeline).toEqual({
|
|
456
|
+
defaultDurationSeconds: 8,
|
|
457
|
+
enabled: true,
|
|
458
|
+
mode: "keyframes",
|
|
459
|
+
});
|
|
552
460
|
expect(app.assembly.surfaces.panels.toolbar.enabled).toBe(true);
|
|
553
461
|
});
|
|
554
462
|
|
|
@@ -560,7 +468,11 @@ describe("defineToolcraft", () => {
|
|
|
560
468
|
},
|
|
561
469
|
});
|
|
562
470
|
|
|
563
|
-
expect(app.panels.timeline).toEqual({
|
|
471
|
+
expect(app.panels.timeline).toEqual({
|
|
472
|
+
defaultDurationSeconds: 8,
|
|
473
|
+
enabled: true,
|
|
474
|
+
mode: "playback",
|
|
475
|
+
});
|
|
564
476
|
expect(app.assembly.components).toContain("timelinePanel");
|
|
565
477
|
expect(app.assembly.capabilities).toEqual(
|
|
566
478
|
expect.arrayContaining([
|
|
@@ -584,6 +496,30 @@ describe("defineToolcraft", () => {
|
|
|
584
496
|
expect(app.assembly.commands).not.toContain("timeline.moveKeyframe");
|
|
585
497
|
});
|
|
586
498
|
|
|
499
|
+
it("uses schema timeline default duration as the animation loop duration", () => {
|
|
500
|
+
const app = defineToolcraft({
|
|
501
|
+
canvas: { enabled: true },
|
|
502
|
+
panels: {
|
|
503
|
+
timeline: {
|
|
504
|
+
defaultDurationSeconds: 12,
|
|
505
|
+
mode: "playback",
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
const clamped = defineToolcraft({
|
|
510
|
+
canvas: { enabled: true },
|
|
511
|
+
panels: {
|
|
512
|
+
timeline: {
|
|
513
|
+
defaultDurationSeconds: 120,
|
|
514
|
+
mode: "playback",
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
expect(app.panels.timeline?.defaultDurationSeconds).toBe(12);
|
|
520
|
+
expect(clamped.panels.timeline?.defaultDurationSeconds).toBe(60);
|
|
521
|
+
});
|
|
522
|
+
|
|
587
523
|
it("only publishes commands supported by the runtime command bus", () => {
|
|
588
524
|
const app = defineToolcraft({
|
|
589
525
|
canvas: { enabled: true, upload: true },
|
|
@@ -617,12 +553,18 @@ describe("defineToolcraft", () => {
|
|
|
617
553
|
"canvas.renderScale",
|
|
618
554
|
"canvas.size.width",
|
|
619
555
|
"canvas.size.height",
|
|
556
|
+
"runtime.settingsTransfer",
|
|
557
|
+
"panels.timeline.extended",
|
|
558
|
+
"panels.timeline.visible",
|
|
620
559
|
]);
|
|
621
560
|
expect(toolcraftReservedTargets).toEqual([
|
|
622
561
|
"canvas.aspectRatio",
|
|
623
562
|
"canvas.renderScale",
|
|
624
563
|
"canvas.size.width",
|
|
625
564
|
"canvas.size.height",
|
|
565
|
+
"runtime.settingsTransfer",
|
|
566
|
+
"panels.timeline.extended",
|
|
567
|
+
"panels.timeline.visible",
|
|
626
568
|
"selectedLayer.opacity",
|
|
627
569
|
"selectedLayer.visible",
|
|
628
570
|
]);
|
|
@@ -630,6 +572,9 @@ describe("defineToolcraft", () => {
|
|
|
630
572
|
expect(getToolcraftCanvasSizeTargetDimension("canvas.size.height")).toBe("height");
|
|
631
573
|
expect(getToolcraftCanvasSizeTargetDimension("generation.prompt")).toBeNull();
|
|
632
574
|
expect(isToolcraftRuntimeOwnedTarget("canvas.aspectRatio")).toBe(true);
|
|
575
|
+
expect(isToolcraftRuntimeOwnedTarget("runtime.settingsTransfer")).toBe(true);
|
|
576
|
+
expect(isToolcraftRuntimeOwnedTarget("panels.timeline.extended")).toBe(true);
|
|
577
|
+
expect(isToolcraftRuntimeOwnedTarget("panels.timeline.visible")).toBe(true);
|
|
633
578
|
expect(isToolcraftReservedTarget("selectedLayer.opacity")).toBe(true);
|
|
634
579
|
expect(isToolcraftReservedTarget("generation.prompt")).toBe(false);
|
|
635
580
|
});
|
|
@@ -823,6 +768,7 @@ describe("defineToolcraft", () => {
|
|
|
823
768
|
});
|
|
824
769
|
expect(app.assembly.capabilities).toContain("canvas.renderScale");
|
|
825
770
|
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
771
|
+
"settingsTransfer",
|
|
826
772
|
"canvasAspectRatio",
|
|
827
773
|
"canvasWidth",
|
|
828
774
|
"canvasHeight",
|
|
@@ -841,6 +787,103 @@ describe("defineToolcraft", () => {
|
|
|
841
787
|
});
|
|
842
788
|
});
|
|
843
789
|
|
|
790
|
+
it("appends timeline visibility to the bottom of setup controls when timeline is enabled", () => {
|
|
791
|
+
const app = defineToolcraft({
|
|
792
|
+
canvas: {
|
|
793
|
+
enabled: true,
|
|
794
|
+
renderScale: true,
|
|
795
|
+
size: { height: 900, unit: "px", width: 1440 },
|
|
796
|
+
sizing: { mode: "editable-output" },
|
|
797
|
+
},
|
|
798
|
+
panels: {
|
|
799
|
+
controls: {
|
|
800
|
+
sections: [],
|
|
801
|
+
title: "Controls",
|
|
802
|
+
},
|
|
803
|
+
timeline: { mode: "playback" },
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const setupSection = app.panels.controls?.sections[0];
|
|
808
|
+
|
|
809
|
+
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
810
|
+
"settingsTransfer",
|
|
811
|
+
"canvasAspectRatio",
|
|
812
|
+
"canvasWidth",
|
|
813
|
+
"canvasHeight",
|
|
814
|
+
"canvasRenderScale",
|
|
815
|
+
"timelineExtended",
|
|
816
|
+
]);
|
|
817
|
+
expect(setupSection?.controls.timelineExtended).toMatchObject({
|
|
818
|
+
defaultValue: false,
|
|
819
|
+
description:
|
|
820
|
+
"Shows the extended runtime timeline with scrubber, duration, loop, and keyframe controls; compact mode keeps only Play visible.",
|
|
821
|
+
label: "Timeline",
|
|
822
|
+
target: "panels.timeline.extended",
|
|
823
|
+
type: "switch",
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("does not let app-authored runtime targets suppress mandatory setup controls", () => {
|
|
828
|
+
const app = defineToolcraft({
|
|
829
|
+
canvas: {
|
|
830
|
+
enabled: true,
|
|
831
|
+
renderScale: true,
|
|
832
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
833
|
+
sizing: { mode: "editable-output" },
|
|
834
|
+
},
|
|
835
|
+
panels: {
|
|
836
|
+
controls: {
|
|
837
|
+
sections: [
|
|
838
|
+
{
|
|
839
|
+
controls: {
|
|
840
|
+
manualWidth: {
|
|
841
|
+
defaultValue: 1280,
|
|
842
|
+
label: "Width",
|
|
843
|
+
target: "canvas.size.width",
|
|
844
|
+
type: "text",
|
|
845
|
+
},
|
|
846
|
+
manualRenderScale: {
|
|
847
|
+
defaultValue: 1,
|
|
848
|
+
label: "Scale",
|
|
849
|
+
max: 2,
|
|
850
|
+
min: 1,
|
|
851
|
+
target: "canvas.renderScale",
|
|
852
|
+
type: "slider",
|
|
853
|
+
},
|
|
854
|
+
manualTimeline: {
|
|
855
|
+
defaultValue: true,
|
|
856
|
+
label: "Timeline",
|
|
857
|
+
target: "panels.timeline.extended",
|
|
858
|
+
type: "switch",
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
title: "Manual Runtime Duplicates",
|
|
862
|
+
},
|
|
863
|
+
],
|
|
864
|
+
title: "Controls",
|
|
865
|
+
},
|
|
866
|
+
timeline: { mode: "playback" },
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const [setupSection, duplicateSection] = app.panels.controls?.sections ?? [];
|
|
871
|
+
|
|
872
|
+
expect(Object.keys(setupSection?.controls ?? {})).toEqual([
|
|
873
|
+
"settingsTransfer",
|
|
874
|
+
"canvasAspectRatio",
|
|
875
|
+
"canvasWidth",
|
|
876
|
+
"canvasHeight",
|
|
877
|
+
"canvasRenderScale",
|
|
878
|
+
"timelineExtended",
|
|
879
|
+
]);
|
|
880
|
+
expect(duplicateSection?.controls.manualWidth?.target).toBe("canvas.size.width");
|
|
881
|
+
expect(duplicateSection?.controls.manualRenderScale?.target).toBe("canvas.renderScale");
|
|
882
|
+
expect(duplicateSection?.controls.manualTimeline?.target).toBe(
|
|
883
|
+
"panels.timeline.extended",
|
|
884
|
+
);
|
|
885
|
+
});
|
|
886
|
+
|
|
844
887
|
it("can prepend render scale without editable output sizing", () => {
|
|
845
888
|
const app = defineToolcraft({
|
|
846
889
|
canvas: {
|
|
@@ -873,6 +916,7 @@ describe("defineToolcraft", () => {
|
|
|
873
916
|
const intrinsicApp = defineToolcraft({
|
|
874
917
|
canvas: {
|
|
875
918
|
enabled: true,
|
|
919
|
+
sizing: { mode: "intrinsic-media" },
|
|
876
920
|
upload: true,
|
|
877
921
|
},
|
|
878
922
|
panels: {
|
|
@@ -903,10 +947,14 @@ describe("defineToolcraft", () => {
|
|
|
903
947
|
|
|
904
948
|
expect(intrinsicApp.canvas.sizing).toEqual({ mode: "intrinsic-media" });
|
|
905
949
|
expect(fixedApp.canvas.sizing).toEqual({ mode: "fixed-output" });
|
|
906
|
-
expect(intrinsicApp.panels.controls?.sections[0]?.title).toBe("
|
|
907
|
-
expect(fixedApp.panels.controls?.sections[0]?.title).toBe("
|
|
950
|
+
expect(intrinsicApp.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
951
|
+
expect(fixedApp.panels.controls?.sections[0]?.title).toBe("Setup");
|
|
952
|
+
expect(intrinsicApp.panels.controls?.sections[1]?.title).toBe("Generation");
|
|
953
|
+
expect(fixedApp.panels.controls?.sections.at(-1)?.title).toBe("Generation");
|
|
908
954
|
expect(intrinsicApp.panels.controls?.sections[0]?.controls.canvasWidth).toBeUndefined();
|
|
909
955
|
expect(fixedApp.panels.controls?.sections[0]?.controls.canvasWidth).toBeUndefined();
|
|
956
|
+
expect(intrinsicApp.panels.controls?.sections[0]?.controls.settingsTransfer).toBeTruthy();
|
|
957
|
+
expect(fixedApp.panels.controls?.sections[0]?.controls.settingsTransfer).toBeTruthy();
|
|
910
958
|
});
|
|
911
959
|
|
|
912
960
|
it("adds runtime layout groups only for compact numeric text pairs", () => {
|
|
@@ -970,7 +1018,7 @@ describe("defineToolcraft", () => {
|
|
|
970
1018
|
},
|
|
971
1019
|
});
|
|
972
1020
|
|
|
973
|
-
const outputSection = app
|
|
1021
|
+
const [outputSection] = getProductSections(app);
|
|
974
1022
|
|
|
975
1023
|
expect(outputSection?.layoutGroups).toEqual([
|
|
976
1024
|
{
|
|
@@ -1010,7 +1058,7 @@ describe("defineToolcraft", () => {
|
|
|
1010
1058
|
},
|
|
1011
1059
|
});
|
|
1012
1060
|
|
|
1013
|
-
const maskSection = app
|
|
1061
|
+
const [maskSection] = getProductSections(app);
|
|
1014
1062
|
|
|
1015
1063
|
expect(maskSection?.layoutGroups).toEqual([
|
|
1016
1064
|
{
|
|
@@ -1121,7 +1169,7 @@ describe("defineToolcraft", () => {
|
|
|
1121
1169
|
},
|
|
1122
1170
|
});
|
|
1123
1171
|
|
|
1124
|
-
const [formatPairSection, outputSection] = app
|
|
1172
|
+
const [formatPairSection, outputSection] = getProductSections(app);
|
|
1125
1173
|
|
|
1126
1174
|
expect(formatPairSection?.layoutGroups).toEqual([
|
|
1127
1175
|
{
|
|
@@ -1168,7 +1216,7 @@ describe("defineToolcraft", () => {
|
|
|
1168
1216
|
},
|
|
1169
1217
|
});
|
|
1170
1218
|
|
|
1171
|
-
const [basicSection, anchorSection] = app
|
|
1219
|
+
const [basicSection, anchorSection] = getProductSections(app);
|
|
1172
1220
|
|
|
1173
1221
|
expect(Object.keys(basicSection?.controls ?? {})).toEqual(["opacity", "threshold"]);
|
|
1174
1222
|
expect(basicSection?.title).toBe("Basic");
|
|
@@ -1211,8 +1259,8 @@ describe("defineToolcraft", () => {
|
|
|
1211
1259
|
},
|
|
1212
1260
|
});
|
|
1213
1261
|
|
|
1214
|
-
expect(app
|
|
1215
|
-
expect(app
|
|
1262
|
+
expect(getProductSections(app)[0]?.layout).toBe("standalone");
|
|
1263
|
+
expect(getProductSections(app)[0]?.title).toBe("Palette");
|
|
1216
1264
|
});
|
|
1217
1265
|
|
|
1218
1266
|
it("adds semantic implicit titles to standalone color sections", () => {
|
|
@@ -1244,8 +1292,8 @@ describe("defineToolcraft", () => {
|
|
|
1244
1292
|
},
|
|
1245
1293
|
});
|
|
1246
1294
|
|
|
1247
|
-
expect(app
|
|
1248
|
-
expect(app
|
|
1295
|
+
expect(getProductSections(app)[0]?.layout).toBe("standalone");
|
|
1296
|
+
expect(getProductSections(app)[0]?.title).toBe("Fill & Stroke");
|
|
1249
1297
|
|
|
1250
1298
|
const inferredLayoutApp = defineToolcraft({
|
|
1251
1299
|
canvas: { enabled: true },
|
|
@@ -1268,7 +1316,7 @@ describe("defineToolcraft", () => {
|
|
|
1268
1316
|
},
|
|
1269
1317
|
});
|
|
1270
1318
|
|
|
1271
|
-
expect(inferredLayoutApp
|
|
1319
|
+
expect(getProductSections(inferredLayoutApp)[0]?.title).toBe("Fill");
|
|
1272
1320
|
});
|
|
1273
1321
|
|
|
1274
1322
|
it("replaces generic color section titles with a semantic fallback", () => {
|
|
@@ -1344,9 +1392,9 @@ describe("defineToolcraft", () => {
|
|
|
1344
1392
|
},
|
|
1345
1393
|
});
|
|
1346
1394
|
|
|
1347
|
-
expect(genericColorApp
|
|
1348
|
-
expect(genericColorsApp
|
|
1349
|
-
expect(explicitGenericTitleApp
|
|
1395
|
+
expect(getProductSections(genericColorApp)[0]?.title).toBe("Appearance");
|
|
1396
|
+
expect(getProductSections(genericColorsApp)[0]?.title).toBe("Appearance");
|
|
1397
|
+
expect(getProductSections(explicitGenericTitleApp)[0]?.title).toBe("Appearance");
|
|
1350
1398
|
});
|
|
1351
1399
|
|
|
1352
1400
|
it("keeps color controls inside semantic mixed sections", () => {
|
|
@@ -1387,9 +1435,11 @@ describe("defineToolcraft", () => {
|
|
|
1387
1435
|
},
|
|
1388
1436
|
});
|
|
1389
1437
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
expect(
|
|
1438
|
+
const productSections = getProductSections(app);
|
|
1439
|
+
|
|
1440
|
+
expect(productSections).toHaveLength(1);
|
|
1441
|
+
expect(productSections[0]?.title).toBe("Square 1 (Right)");
|
|
1442
|
+
expect(Object.keys(productSections[0]?.controls ?? {})).toEqual([
|
|
1393
1443
|
"connections",
|
|
1394
1444
|
"hoverRadius",
|
|
1395
1445
|
"color",
|
|
@@ -1432,10 +1482,12 @@ describe("defineToolcraft", () => {
|
|
|
1432
1482
|
},
|
|
1433
1483
|
});
|
|
1434
1484
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
expect(
|
|
1438
|
-
expect(
|
|
1485
|
+
const productSections = getProductSections(app);
|
|
1486
|
+
|
|
1487
|
+
expect(productSections).toHaveLength(1);
|
|
1488
|
+
expect(productSections[0]?.title).toBe("Source");
|
|
1489
|
+
expect(productSections[0]?.layout).toBeUndefined();
|
|
1490
|
+
expect(Object.keys(productSections[0]?.controls ?? {})).toEqual([
|
|
1439
1491
|
"sourceMode",
|
|
1440
1492
|
"sourceUpload",
|
|
1441
1493
|
]);
|
|
@@ -1493,7 +1545,7 @@ describe("defineToolcraft", () => {
|
|
|
1493
1545
|
},
|
|
1494
1546
|
});
|
|
1495
1547
|
|
|
1496
|
-
const [slidersSection, actionsSection] = app
|
|
1548
|
+
const [slidersSection, actionsSection] = getProductSections(app);
|
|
1497
1549
|
|
|
1498
1550
|
expect(slidersSection?.controls.opacity?.markerCount).toBe(101);
|
|
1499
1551
|
expect(slidersSection?.controls.opacity?.variant).toBe("discrete");
|
|
@@ -1563,7 +1615,7 @@ describe("defineToolcraft", () => {
|
|
|
1563
1615
|
},
|
|
1564
1616
|
});
|
|
1565
1617
|
|
|
1566
|
-
const controls = app
|
|
1618
|
+
const controls = getProductSections(app)[0]?.controls;
|
|
1567
1619
|
|
|
1568
1620
|
expect(controls?.speed?.variant).toBeUndefined();
|
|
1569
1621
|
expect(controls?.speed?.markerCount).toBe(6);
|