@pixel-point/toolcraft 0.0.4 → 0.0.7
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/scripts/prepare-pack.mjs +5 -0
- package/src/generate.mjs +13 -0
- package/src/generate.test.mjs +7 -1
- package/templates/runtime/contracts/component-contracts.test.ts +96 -3
- package/templates/runtime/contracts/component-contracts.ts +75 -3
- package/templates/runtime/contracts/decision-contracts.test.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.ts +1 -1
- package/templates/runtime/react/canvas-shell.test.tsx +7 -7
- package/templates/runtime/react/controls-panel.test.tsx +455 -1
- package/templates/runtime/react/controls-panel.tsx +515 -30
- package/templates/runtime/react/settings-transfer.test.ts +3 -3
- package/templates/runtime/react/timeline-panel.test.tsx +69 -0
- package/templates/runtime/react/timeline-panel.tsx +98 -10
- package/templates/runtime/react/toolbar-panel.test.tsx +6 -6
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +2 -2
- package/templates/runtime/schema/define-toolcraft.test.ts +78 -1
- package/templates/runtime/schema/define-toolcraft.ts +145 -6
- package/templates/runtime/schema/runtime-targets.ts +1 -0
- package/templates/runtime/schema/types.ts +46 -1
- package/templates/runtime/state/canvas-zoom.ts +1 -1
- package/templates/runtime/state/create-template-state.test.ts +6 -6
- package/templates/runtime/state/reducer.test.ts +139 -8
- package/templates/runtime/state/reducer.ts +88 -22
- package/templates/runtime/state/types.ts +3 -0
- package/templates/starter/AGENTS.md +8 -8
- package/templates/starter/docs/toolcraft/README.md +4 -3
- package/templates/starter/docs/toolcraft/acceptance-testing.md +6 -2
- package/templates/starter/docs/toolcraft/assembly-workflow.md +8 -6
- package/templates/starter/docs/toolcraft/component-rules.md +17 -1
- package/templates/starter/docs/toolcraft/custom-controls.md +2 -2
- package/templates/starter/docs/toolcraft/performance.md +8 -7
- package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +15 -4
- package/templates/starter/docs/toolcraft/workflow.md +5 -8
- package/templates/starter/gitignore +36 -0
- package/templates/starter/package.json +1 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +55 -0
- package/templates/starter/src/app/starter-acceptance.ts +67 -1
- package/templates/starter/src/app/starter-performance.test.ts +1 -1
- 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 +101 -18
- package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1 -6
- package/templates/ui/components/controls/index.ts +8 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +4 -1
- package/templates/ui/components/controls/slider/slider-value.ts +48 -5
- package/templates/ui/components/primitives/editable-slider-value-label.tsx +6 -1
- package/templates/ui/index.ts +1 -0
|
@@ -436,6 +436,101 @@ describe("ControlsPanel", () => {
|
|
|
436
436
|
);
|
|
437
437
|
});
|
|
438
438
|
|
|
439
|
+
it("suppresses obvious help icons in sequential color sections", () => {
|
|
440
|
+
const schema = defineToolcraft({
|
|
441
|
+
canvas: { enabled: false },
|
|
442
|
+
panels: {
|
|
443
|
+
controls: {
|
|
444
|
+
sections: [
|
|
445
|
+
{
|
|
446
|
+
controls: {
|
|
447
|
+
color1: {
|
|
448
|
+
defaultValue: { hex: "#DFFF1A" },
|
|
449
|
+
description: "Sets the first bead color.",
|
|
450
|
+
label: "Color 1",
|
|
451
|
+
target: "beads.color1",
|
|
452
|
+
type: "color",
|
|
453
|
+
},
|
|
454
|
+
color2: {
|
|
455
|
+
defaultValue: { hex: "#8CFF3A" },
|
|
456
|
+
description: "Sets the second bead color.",
|
|
457
|
+
label: "Color 2",
|
|
458
|
+
target: "beads.color2",
|
|
459
|
+
type: "color",
|
|
460
|
+
},
|
|
461
|
+
color3: {
|
|
462
|
+
defaultValue: { hex: "#F4FF5A" },
|
|
463
|
+
description: "Sets the third bead color.",
|
|
464
|
+
label: "Color 3",
|
|
465
|
+
target: "beads.color3",
|
|
466
|
+
type: "color",
|
|
467
|
+
},
|
|
468
|
+
color4: {
|
|
469
|
+
defaultValue: { hex: "#B8FF2E" },
|
|
470
|
+
description: "Sets the fourth bead color.",
|
|
471
|
+
label: "Color 4",
|
|
472
|
+
target: "beads.color4",
|
|
473
|
+
type: "color",
|
|
474
|
+
},
|
|
475
|
+
color5: {
|
|
476
|
+
defaultValue: { hex: "#ECFF68" },
|
|
477
|
+
description: "Sets the fifth bead color.",
|
|
478
|
+
label: "Color 5",
|
|
479
|
+
target: "beads.color5",
|
|
480
|
+
type: "color",
|
|
481
|
+
},
|
|
482
|
+
colorSpread: {
|
|
483
|
+
defaultValue: 34,
|
|
484
|
+
description:
|
|
485
|
+
"Controls how often beads use colors 2-5 instead of Color 1.",
|
|
486
|
+
label: "Spread",
|
|
487
|
+
max: 100,
|
|
488
|
+
min: 0,
|
|
489
|
+
target: "beads.colorSpread",
|
|
490
|
+
type: "slider",
|
|
491
|
+
unit: "%",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
layoutGroups: [
|
|
495
|
+
{
|
|
496
|
+
columns: 2,
|
|
497
|
+
controls: ["color1", "color2"],
|
|
498
|
+
layout: "inline",
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
columns: 2,
|
|
502
|
+
controls: ["color3", "color4"],
|
|
503
|
+
layout: "inline",
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
title: "Bead Colors",
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
controls: {
|
|
510
|
+
includeBackground: {
|
|
511
|
+
defaultValue: true,
|
|
512
|
+
description:
|
|
513
|
+
"Controls PNG background transparency while preview and video keep the background.",
|
|
514
|
+
label: "Include",
|
|
515
|
+
target: "export.includeBackground",
|
|
516
|
+
type: "switch",
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
title: "Background",
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
title: "Controls",
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
renderControlsPanelWithSchema(schema);
|
|
528
|
+
|
|
529
|
+
expect(screen.queryByRole("button", { name: "Color 5 help" })).toBeNull();
|
|
530
|
+
expect(screen.queryByRole("button", { name: "Spread help" })).toBeNull();
|
|
531
|
+
expect(screen.getByRole("button", { name: "Include help" })).toBeTruthy();
|
|
532
|
+
});
|
|
533
|
+
|
|
439
534
|
it("renders short visible toggle plus parameter rows as equal-width toggle-parameter rows", () => {
|
|
440
535
|
const schema = defineToolcraft({
|
|
441
536
|
canvas: { enabled: false },
|
|
@@ -1176,6 +1271,24 @@ describe("ControlsPanel", () => {
|
|
|
1176
1271
|
});
|
|
1177
1272
|
});
|
|
1178
1273
|
|
|
1274
|
+
fireEvent.change(fontSizeInput, {
|
|
1275
|
+
target: { value: "768" },
|
|
1276
|
+
});
|
|
1277
|
+
fireEvent.blur(fontSizeInput);
|
|
1278
|
+
|
|
1279
|
+
await waitFor(() => {
|
|
1280
|
+
expect(JSON.parse(screen.getByTestId("font-value").textContent ?? "{}")).toEqual({
|
|
1281
|
+
color: "#FFFFFF",
|
|
1282
|
+
fontId: "roboto",
|
|
1283
|
+
fontSize: 768,
|
|
1284
|
+
fontWeight: "400",
|
|
1285
|
+
letterSpacing: "wider",
|
|
1286
|
+
lineHeight: "relaxed",
|
|
1287
|
+
opacity: 100,
|
|
1288
|
+
textCase: "uppercase",
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1179
1292
|
const colorHexInput = screen.getByLabelText("Color hex") as HTMLInputElement;
|
|
1180
1293
|
fireEvent.change(colorHexInput, {
|
|
1181
1294
|
target: { value: "#C1FF00" },
|
|
@@ -1191,7 +1304,7 @@ describe("ControlsPanel", () => {
|
|
|
1191
1304
|
expect(JSON.parse(screen.getByTestId("font-value").textContent ?? "{}")).toEqual({
|
|
1192
1305
|
color: "#C1FF00",
|
|
1193
1306
|
fontId: "roboto",
|
|
1194
|
-
fontSize:
|
|
1307
|
+
fontSize: 768,
|
|
1195
1308
|
fontWeight: "400",
|
|
1196
1309
|
letterSpacing: "wider",
|
|
1197
1310
|
lineHeight: "relaxed",
|
|
@@ -1534,6 +1647,46 @@ describe("ControlsPanel", () => {
|
|
|
1534
1647
|
container.querySelector('[data-slot="slider"][data-variant="discrete"]'),
|
|
1535
1648
|
).toBeNull();
|
|
1536
1649
|
expect(container.querySelectorAll('[data-slot="slider-marker"]')).toHaveLength(0);
|
|
1650
|
+
expect(container.textContent).toContain("118 cols/s");
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
it("keeps textual slider value labels non-editable", () => {
|
|
1654
|
+
const schema = defineToolcraft({
|
|
1655
|
+
canvas: { enabled: true },
|
|
1656
|
+
panels: {
|
|
1657
|
+
controls: {
|
|
1658
|
+
sections: [
|
|
1659
|
+
{
|
|
1660
|
+
controls: {
|
|
1661
|
+
letterSpacing: {
|
|
1662
|
+
defaultValue: 2,
|
|
1663
|
+
label: "Letter spacing",
|
|
1664
|
+
max: 4,
|
|
1665
|
+
min: 0,
|
|
1666
|
+
step: 1,
|
|
1667
|
+
target: "text.letterSpacing",
|
|
1668
|
+
type: "slider",
|
|
1669
|
+
valueLabel: "Normal",
|
|
1670
|
+
},
|
|
1671
|
+
},
|
|
1672
|
+
title: "Text",
|
|
1673
|
+
},
|
|
1674
|
+
],
|
|
1675
|
+
title: "Generation Controls",
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
renderControlsPanelWithSchema(schema);
|
|
1681
|
+
|
|
1682
|
+
const valueLabel = screen
|
|
1683
|
+
.getAllByText("Normal")
|
|
1684
|
+
.find((node) => node.getAttribute("aria-hidden") !== "true");
|
|
1685
|
+
|
|
1686
|
+
expect(screen.queryByRole("button", { name: "Edit Letter spacing value" })).toBeNull();
|
|
1687
|
+
expect(valueLabel).toBeTruthy();
|
|
1688
|
+
expect(valueLabel?.className).toContain("cursor-default");
|
|
1689
|
+
expect(valueLabel?.className).not.toContain("cursor-text");
|
|
1537
1690
|
});
|
|
1538
1691
|
|
|
1539
1692
|
it("keeps schema sliders stacked even when a layout group asks for an inline row", () => {
|
|
@@ -1589,6 +1742,8 @@ describe("ControlsPanel", () => {
|
|
|
1589
1742
|
expect(container.querySelector('[data-control-layout="inline"]')).toBeNull();
|
|
1590
1743
|
expect(fpsSlider).toBeTruthy();
|
|
1591
1744
|
expect(container.querySelectorAll('[data-slot="slider-marker"]').length).toBeGreaterThan(0);
|
|
1745
|
+
expect(container.textContent).toContain("17 fps");
|
|
1746
|
+
expect(container.textContent).toContain("3.7x");
|
|
1592
1747
|
});
|
|
1593
1748
|
|
|
1594
1749
|
it("passes schema disabled state into slider controls", () => {
|
|
@@ -2283,6 +2438,76 @@ describe("ControlsPanel", () => {
|
|
|
2283
2438
|
expect(screen.getByTestId("media-count").textContent).toBe("0");
|
|
2284
2439
|
});
|
|
2285
2440
|
|
|
2441
|
+
it("renders multi-image file uploads as an add-last thumbnail grid", () => {
|
|
2442
|
+
const schema = defineToolcraft({
|
|
2443
|
+
canvas: { enabled: true, upload: true },
|
|
2444
|
+
panels: {
|
|
2445
|
+
controls: {
|
|
2446
|
+
sections: [
|
|
2447
|
+
{
|
|
2448
|
+
controls: {
|
|
2449
|
+
source: {
|
|
2450
|
+
accept: "PNG, SVG",
|
|
2451
|
+
label: "Images",
|
|
2452
|
+
multiple: true,
|
|
2453
|
+
target: "input.sources",
|
|
2454
|
+
type: "fileDrop",
|
|
2455
|
+
},
|
|
2456
|
+
},
|
|
2457
|
+
layout: "standalone",
|
|
2458
|
+
title: "Input",
|
|
2459
|
+
},
|
|
2460
|
+
],
|
|
2461
|
+
title: "Generation Controls",
|
|
2462
|
+
},
|
|
2463
|
+
},
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
const { container } = renderControlsPanelWithSchema(schema, undefined, {
|
|
2467
|
+
layers: [
|
|
2468
|
+
{ id: "layer-1", kind: "layer", name: "one", visible: true },
|
|
2469
|
+
{ id: "layer-2", kind: "layer", name: "two", visible: true },
|
|
2470
|
+
],
|
|
2471
|
+
mediaAssets: [
|
|
2472
|
+
{
|
|
2473
|
+
dataUrl:
|
|
2474
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 96 96'%3E%3Crect width='96' height='96' fill='%23777'/%3E%3C/svg%3E",
|
|
2475
|
+
fileName: "one.svg",
|
|
2476
|
+
id: "media-1",
|
|
2477
|
+
layerId: "layer-1",
|
|
2478
|
+
mimeType: "image/svg+xml",
|
|
2479
|
+
position: { x: 0, y: 0 },
|
|
2480
|
+
size: { height: 96, unit: "px", width: 96 },
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
dataUrl:
|
|
2484
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 96 96'%3E%3Crect width='96' height='96' fill='%23999'/%3E%3C/svg%3E",
|
|
2485
|
+
fileName: "two.svg",
|
|
2486
|
+
id: "media-2",
|
|
2487
|
+
layerId: "layer-2",
|
|
2488
|
+
mimeType: "image/svg+xml",
|
|
2489
|
+
position: { x: 0, y: 0 },
|
|
2490
|
+
size: { height: 96, unit: "px", width: 96 },
|
|
2491
|
+
},
|
|
2492
|
+
],
|
|
2493
|
+
selectedLayerId: "layer-2",
|
|
2494
|
+
});
|
|
2495
|
+
|
|
2496
|
+
expect(screen.getByRole("button", { name: "Add image files" })).toBeTruthy();
|
|
2497
|
+
const previewGrid = container.querySelector('[data-slot="file-upload-preview-grid"]');
|
|
2498
|
+
expect(previewGrid?.className).toContain("grid-cols-4");
|
|
2499
|
+
expect(previewGrid?.lastElementChild?.getAttribute("data-slot")).toBe(
|
|
2500
|
+
"file-upload-add-preview",
|
|
2501
|
+
);
|
|
2502
|
+
expect(screen.getByRole("img", { name: "one.svg" })).toBeTruthy();
|
|
2503
|
+
expect(screen.getByRole("img", { name: "two.svg" })).toBeTruthy();
|
|
2504
|
+
|
|
2505
|
+
fireEvent.click(screen.getByRole("button", { name: "Remove two.svg" }));
|
|
2506
|
+
|
|
2507
|
+
expect(screen.getByTestId("media-count").textContent).toBe("1");
|
|
2508
|
+
expect(screen.queryByRole("img", { name: "two.svg" })).toBeNull();
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2286
2511
|
it("keeps file upload deletion in the layers panel for multi-layer apps", () => {
|
|
2287
2512
|
const schema = defineToolcraft({
|
|
2288
2513
|
canvas: { enabled: true, upload: true },
|
|
@@ -3057,6 +3282,173 @@ describe("ControlsPanel", () => {
|
|
|
3057
3282
|
expect(screen.getByTestId("enabled-value").textContent).toBe("true");
|
|
3058
3283
|
});
|
|
3059
3284
|
|
|
3285
|
+
it("resets only controls from the selected section header action", () => {
|
|
3286
|
+
const schema = defineToolcraft({
|
|
3287
|
+
canvas: { enabled: false },
|
|
3288
|
+
panels: {
|
|
3289
|
+
controls: {
|
|
3290
|
+
sections: [
|
|
3291
|
+
{
|
|
3292
|
+
controls: {
|
|
3293
|
+
toneName: {
|
|
3294
|
+
defaultValue: "Soft",
|
|
3295
|
+
label: "Tone name",
|
|
3296
|
+
target: "tone.name",
|
|
3297
|
+
type: "text",
|
|
3298
|
+
},
|
|
3299
|
+
},
|
|
3300
|
+
title: "Tone",
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
controls: {
|
|
3304
|
+
prompt: {
|
|
3305
|
+
defaultValue: "Poster",
|
|
3306
|
+
label: "Prompt",
|
|
3307
|
+
target: "generation.prompt",
|
|
3308
|
+
type: "text",
|
|
3309
|
+
},
|
|
3310
|
+
},
|
|
3311
|
+
title: "Output",
|
|
3312
|
+
},
|
|
3313
|
+
],
|
|
3314
|
+
title: "Controls",
|
|
3315
|
+
},
|
|
3316
|
+
},
|
|
3317
|
+
});
|
|
3318
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
3319
|
+
|
|
3320
|
+
fireEvent.change(screen.getByDisplayValue("Soft"), {
|
|
3321
|
+
target: { value: "Sharp" },
|
|
3322
|
+
});
|
|
3323
|
+
fireEvent.change(screen.getByDisplayValue("Poster"), {
|
|
3324
|
+
target: { value: "Banner" },
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
const toneHeader = screen
|
|
3328
|
+
.getByText("Tone")
|
|
3329
|
+
.closest<HTMLElement>('[data-slot="control-section-header"]');
|
|
3330
|
+
const headerButtons = toneHeader?.querySelectorAll("button");
|
|
3331
|
+
|
|
3332
|
+
expect(headerButtons?.[0]?.getAttribute("data-control-section-reset-button")).toBe("");
|
|
3333
|
+
expect(headerButtons?.[1]?.getAttribute("data-control-section-collapse-button")).toBe("");
|
|
3334
|
+
|
|
3335
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset Tone section" }));
|
|
3336
|
+
|
|
3337
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3338
|
+
|
|
3339
|
+
expect(values["tone.name"]).toBe("Soft");
|
|
3340
|
+
expect(values["generation.prompt"]).toBe("Banner");
|
|
3341
|
+
expect(container.querySelector('[data-control-section-reset-button]')).toBeTruthy();
|
|
3342
|
+
});
|
|
3343
|
+
|
|
3344
|
+
it("renders collection actions as add/remove controls for runtime item arrays", () => {
|
|
3345
|
+
const schema = defineToolcraft({
|
|
3346
|
+
canvas: { enabled: false },
|
|
3347
|
+
panels: {
|
|
3348
|
+
controls: {
|
|
3349
|
+
sections: [
|
|
3350
|
+
{
|
|
3351
|
+
controls: {
|
|
3352
|
+
colors: {
|
|
3353
|
+
defaultValue: [{ hex: "#DFFF1A" }, { hex: "#8CFF3A" }],
|
|
3354
|
+
itemControl: {
|
|
3355
|
+
defaultValue: { hex: "#C1FF00" },
|
|
3356
|
+
label: "Color",
|
|
3357
|
+
type: "color",
|
|
3358
|
+
},
|
|
3359
|
+
label: "Colors",
|
|
3360
|
+
minItems: 1,
|
|
3361
|
+
recommendedMaxItems: 2,
|
|
3362
|
+
target: "bead.colors",
|
|
3363
|
+
type: "collectionActions",
|
|
3364
|
+
},
|
|
3365
|
+
},
|
|
3366
|
+
title: "Bead Colors",
|
|
3367
|
+
},
|
|
3368
|
+
],
|
|
3369
|
+
title: "Controls",
|
|
3370
|
+
},
|
|
3371
|
+
},
|
|
3372
|
+
});
|
|
3373
|
+
|
|
3374
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
3375
|
+
|
|
3376
|
+
const addButton = screen.getByRole("button", { name: "Add Color" });
|
|
3377
|
+
const removeButton = screen.getByRole("button", { name: "Remove Color" });
|
|
3378
|
+
const collectionHeader = container.querySelector(
|
|
3379
|
+
'[data-slot="collection-actions-control-header"]',
|
|
3380
|
+
);
|
|
3381
|
+
const colorGrid = container.querySelector(
|
|
3382
|
+
'[data-slot="collection-actions-items-grid"]',
|
|
3383
|
+
);
|
|
3384
|
+
|
|
3385
|
+
expect(collectionHeader?.textContent).toContain("Colors");
|
|
3386
|
+
expect(collectionHeader?.lastElementChild?.contains(removeButton)).toBe(true);
|
|
3387
|
+
expect(collectionHeader?.lastElementChild?.contains(addButton)).toBe(true);
|
|
3388
|
+
expect(colorGrid).toBeTruthy();
|
|
3389
|
+
expect(colorGrid?.children).toHaveLength(2);
|
|
3390
|
+
expect(screen.queryByText("Color 1")).toBeNull();
|
|
3391
|
+
expect(screen.queryByText("Color 2")).toBeNull();
|
|
3392
|
+
|
|
3393
|
+
fireEvent.click(addButton);
|
|
3394
|
+
fireEvent.click(addButton);
|
|
3395
|
+
|
|
3396
|
+
let values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3397
|
+
|
|
3398
|
+
expect(values["bead.colors"]).toHaveLength(4);
|
|
3399
|
+
expect(screen.queryByText("Color 4")).toBeNull();
|
|
3400
|
+
expect(colorGrid?.children).toHaveLength(4);
|
|
3401
|
+
expect((addButton as HTMLButtonElement).disabled).toBe(false);
|
|
3402
|
+
|
|
3403
|
+
fireEvent.click(removeButton);
|
|
3404
|
+
fireEvent.click(removeButton);
|
|
3405
|
+
fireEvent.click(removeButton);
|
|
3406
|
+
|
|
3407
|
+
values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3408
|
+
|
|
3409
|
+
expect(values["bead.colors"]).toHaveLength(1);
|
|
3410
|
+
expect((removeButton as HTMLButtonElement).disabled).toBe(true);
|
|
3411
|
+
});
|
|
3412
|
+
|
|
3413
|
+
it("renders collection item controls beyond color lists", () => {
|
|
3414
|
+
const schema = defineToolcraft({
|
|
3415
|
+
canvas: { enabled: false },
|
|
3416
|
+
panels: {
|
|
3417
|
+
controls: {
|
|
3418
|
+
sections: [
|
|
3419
|
+
{
|
|
3420
|
+
controls: {
|
|
3421
|
+
labels: {
|
|
3422
|
+
defaultValue: ["Alpha"],
|
|
3423
|
+
itemControl: {
|
|
3424
|
+
defaultValue: "New label",
|
|
3425
|
+
label: "Label",
|
|
3426
|
+
type: "text",
|
|
3427
|
+
},
|
|
3428
|
+
minItems: 1,
|
|
3429
|
+
target: "glyph.labels",
|
|
3430
|
+
type: "collectionActions",
|
|
3431
|
+
},
|
|
3432
|
+
},
|
|
3433
|
+
title: "Glyph Labels",
|
|
3434
|
+
},
|
|
3435
|
+
],
|
|
3436
|
+
title: "Controls",
|
|
3437
|
+
},
|
|
3438
|
+
},
|
|
3439
|
+
});
|
|
3440
|
+
|
|
3441
|
+
renderControlsPanelWithSchema(schema);
|
|
3442
|
+
|
|
3443
|
+
fireEvent.click(screen.getByRole("button", { name: "Add Label" }));
|
|
3444
|
+
|
|
3445
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3446
|
+
|
|
3447
|
+
expect(values["glyph.labels"]).toEqual(["Alpha", "New label"]);
|
|
3448
|
+
expect(screen.getByDisplayValue("Alpha")).toBeTruthy();
|
|
3449
|
+
expect(screen.getByDisplayValue("New label")).toBeTruthy();
|
|
3450
|
+
});
|
|
3451
|
+
|
|
3060
3452
|
it("commits canvas size controls to runtime state on blur or Enter", () => {
|
|
3061
3453
|
const { container } = renderControlsPanel();
|
|
3062
3454
|
|
|
@@ -3126,6 +3518,68 @@ describe("ControlsPanel", () => {
|
|
|
3126
3518
|
expect(values["canvas.size.height"]).toBe(1080);
|
|
3127
3519
|
});
|
|
3128
3520
|
|
|
3521
|
+
it("switches editable-output aspect ratio to custom after manual size edits", () => {
|
|
3522
|
+
const schema = defineToolcraft({
|
|
3523
|
+
canvas: {
|
|
3524
|
+
enabled: true,
|
|
3525
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
3526
|
+
sizing: { mode: "editable-output" },
|
|
3527
|
+
},
|
|
3528
|
+
panels: {
|
|
3529
|
+
controls: {
|
|
3530
|
+
sections: [],
|
|
3531
|
+
title: "Controls",
|
|
3532
|
+
},
|
|
3533
|
+
},
|
|
3534
|
+
});
|
|
3535
|
+
|
|
3536
|
+
renderControlsPanelWithSchema(schema);
|
|
3537
|
+
|
|
3538
|
+
const widthInput = screen.getByDisplayValue("1920") as HTMLInputElement;
|
|
3539
|
+
|
|
3540
|
+
fireEvent.change(widthInput, { target: { value: "1600" } });
|
|
3541
|
+
fireEvent.blur(widthInput);
|
|
3542
|
+
|
|
3543
|
+
expect(screen.getByTestId("canvas-size").textContent).toBe("1600,1080");
|
|
3544
|
+
expect(screen.getAllByText("Custom...").length).toBeGreaterThan(0);
|
|
3545
|
+
expect(screen.getByDisplayValue("40")).toBeTruthy();
|
|
3546
|
+
expect(screen.getByDisplayValue("27")).toBeTruthy();
|
|
3547
|
+
expect(JSON.parse(screen.getByTestId("values-json").textContent ?? "{}")).toMatchObject({
|
|
3548
|
+
"canvas.aspectRatio": {
|
|
3549
|
+
height: 27,
|
|
3550
|
+
mode: "custom",
|
|
3551
|
+
value: "40:27",
|
|
3552
|
+
width: 40,
|
|
3553
|
+
},
|
|
3554
|
+
"canvas.size.height": 1080,
|
|
3555
|
+
"canvas.size.width": 1600,
|
|
3556
|
+
});
|
|
3557
|
+
});
|
|
3558
|
+
|
|
3559
|
+
it("renders raster render scale as the last technical setup control", () => {
|
|
3560
|
+
const schema = defineToolcraft({
|
|
3561
|
+
canvas: {
|
|
3562
|
+
enabled: true,
|
|
3563
|
+
renderScale: true,
|
|
3564
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
3565
|
+
sizing: { mode: "editable-output" },
|
|
3566
|
+
},
|
|
3567
|
+
panels: {
|
|
3568
|
+
controls: {
|
|
3569
|
+
sections: [],
|
|
3570
|
+
title: "Controls",
|
|
3571
|
+
},
|
|
3572
|
+
},
|
|
3573
|
+
});
|
|
3574
|
+
renderControlsPanelWithSchema(schema);
|
|
3575
|
+
|
|
3576
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3577
|
+
|
|
3578
|
+
expect(screen.getByText("Resolution scale")).toBeTruthy();
|
|
3579
|
+
expect(screen.getByText("2x")).toBeTruthy();
|
|
3580
|
+
expect(values["canvas.renderScale"]).toBe(2);
|
|
3581
|
+
});
|
|
3582
|
+
|
|
3129
3583
|
it("applies empty text input values while typing and reset restores defaults", () => {
|
|
3130
3584
|
renderControlsPanel();
|
|
3131
3585
|
|