@pixel-point/toolcraft 0.0.8 → 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 +42 -5
- package/src/generate.test.mjs +40 -0
- package/src/package-json.mjs +15 -0
- package/src/package-json.test.mjs +14 -1
- package/templates/runtime/contracts/component-contracts.test.ts +251 -47
- package/templates/runtime/contracts/component-contracts.ts +130 -57
- package/templates/runtime/contracts/decision-contracts.test.ts +38 -1
- package/templates/runtime/contracts/decision-contracts.ts +22 -9
- 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 +916 -108
- package/templates/runtime/react/controls-panel.tsx +136 -30
- 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 +266 -170
- package/templates/runtime/schema/define-toolcraft.ts +140 -246
- package/templates/runtime/schema/runtime-targets.ts +21 -0
- package/templates/runtime/schema/types.ts +44 -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 +810 -21
- package/templates/runtime/testing/performance.ts +823 -53
- package/templates/starter/AGENTS.md +24 -18
- package/templates/starter/docs/toolcraft/README.md +8 -4
- package/templates/starter/docs/toolcraft/acceptance-testing.md +43 -10
- package/templates/starter/docs/toolcraft/agent-worklog.md +1 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +55 -20
- package/templates/starter/docs/toolcraft/component-rules.md +75 -42
- package/templates/starter/docs/toolcraft/decision-contract.md +2 -0
- package/templates/starter/docs/toolcraft/performance.md +37 -11
- package/templates/starter/docs/toolcraft/schema-reference.md +171 -41
- 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/gitignore +1 -0
- package/templates/starter/index.html +1 -0
- package/templates/starter/package.json +3 -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 +114 -13
- package/templates/starter/scripts/toolcraft-port.mjs +280 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +207 -1
- package/templates/starter/src/app/starter-acceptance.test.ts +3412 -479
- package/templates/starter/src/app/starter-acceptance.ts +1453 -97
- 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/control-layout/index.tsx +8 -3
- package/templates/ui/components/controls/actions/actions-control.tsx +56 -5
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +7 -3
- package/templates/ui/components/controls/color/index.ts +4 -1
- package/templates/ui/components/controls/color/palette-control.tsx +34 -4
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +7 -2
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +2 -2
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +186 -14
- package/templates/ui/components/controls/file-drop/index.ts +6 -1
- package/templates/ui/components/controls/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +12 -4
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -1
- package/templates/ui/components/controls/select/select-control.tsx +8 -25
- package/templates/ui/components/controls/slider/slider-value.ts +0 -1
- package/templates/ui/components/controls/text-input/text-input-control.tsx +4 -1
- package/templates/ui/components/controls/vector/index.ts +1 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +109 -12
- package/templates/ui/components/panel/panel-actions.tsx +1 -1
- package/templates/ui/components/panel/panel-section.tsx +29 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
1
|
+
import { act, cleanup, createEvent, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import { FileDrop, Panel } from "@repo/ui";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_COLOR_FORMAT_MODE,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getColorSurfaceStyle,
|
|
8
8
|
getColorSurfaceThumbPosition,
|
|
9
9
|
getSurfaceHsvColor,
|
|
10
|
+
StyleGuideColorPicker,
|
|
10
11
|
} from "@repo/ui/controls";
|
|
11
12
|
import * as React from "react";
|
|
12
13
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
@@ -53,18 +54,6 @@ function createSchema() {
|
|
|
53
54
|
sections: [
|
|
54
55
|
{
|
|
55
56
|
controls: {
|
|
56
|
-
canvasHeight: {
|
|
57
|
-
defaultValue: 180,
|
|
58
|
-
label: "Canvas height",
|
|
59
|
-
target: "canvas.size.height",
|
|
60
|
-
type: "text",
|
|
61
|
-
},
|
|
62
|
-
canvasWidth: {
|
|
63
|
-
defaultValue: 320,
|
|
64
|
-
label: "Canvas width",
|
|
65
|
-
target: "canvas.size.width",
|
|
66
|
-
type: "text",
|
|
67
|
-
},
|
|
68
57
|
prompt: {
|
|
69
58
|
defaultValue: "Initial prompt",
|
|
70
59
|
description: "Describe the generated result.",
|
|
@@ -108,13 +97,6 @@ function createSchema() {
|
|
|
108
97
|
type: "switch",
|
|
109
98
|
},
|
|
110
99
|
},
|
|
111
|
-
layoutGroups: [
|
|
112
|
-
{
|
|
113
|
-
columns: 2,
|
|
114
|
-
controls: ["canvasWidth", "canvasHeight"],
|
|
115
|
-
layout: "inline",
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
100
|
title: "Basic",
|
|
119
101
|
},
|
|
120
102
|
{
|
|
@@ -251,7 +233,19 @@ function StateProbe() {
|
|
|
251
233
|
<span data-testid="media-ids">
|
|
252
234
|
{state.mediaAssets.map((asset) => asset.id).join(",")}
|
|
253
235
|
</span>
|
|
236
|
+
<span data-testid="media-sizes">
|
|
237
|
+
{state.mediaAssets
|
|
238
|
+
.map((asset) =>
|
|
239
|
+
asset.size ? `${asset.size.width}x${asset.size.height}` : "none",
|
|
240
|
+
)
|
|
241
|
+
.join(",")}
|
|
242
|
+
</span>
|
|
243
|
+
<span data-testid="media-transforms">
|
|
244
|
+
{JSON.stringify(state.mediaAssets.map((asset) => asset.transform ?? null))}
|
|
245
|
+
</span>
|
|
254
246
|
<span data-testid="timeline-expanded">{String(state.timeline.expanded)}</span>
|
|
247
|
+
<span data-testid="timeline-panel-extended">{String(state.panels.timeline.extended)}</span>
|
|
248
|
+
<span data-testid="timeline-panel-hidden">{String(state.panels.timeline.hidden)}</span>
|
|
255
249
|
<span data-testid="timeline-keyframes">
|
|
256
250
|
{JSON.stringify(state.timeline.keyframeGroups)}
|
|
257
251
|
</span>
|
|
@@ -387,7 +381,43 @@ describe("ControlsPanel", () => {
|
|
|
387
381
|
expect(inlineGroup?.textContent).toContain("Snap Y");
|
|
388
382
|
});
|
|
389
383
|
|
|
390
|
-
it("
|
|
384
|
+
it("commits palette family and shade changes to runtime state immediately", () => {
|
|
385
|
+
const schema = defineToolcraft({
|
|
386
|
+
canvas: { enabled: false },
|
|
387
|
+
panels: {
|
|
388
|
+
controls: {
|
|
389
|
+
sections: [
|
|
390
|
+
{
|
|
391
|
+
controls: {
|
|
392
|
+
inkPalette: {
|
|
393
|
+
defaultValue: { family: "Slate", shade: "900" },
|
|
394
|
+
label: "Palette",
|
|
395
|
+
target: "ink.palette",
|
|
396
|
+
type: "palette",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
title: "Ink",
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
title: "Controls",
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
renderControlsPanelWithSchema(schema);
|
|
408
|
+
|
|
409
|
+
fireEvent.click(screen.getByRole("button", { name: "Primary family Red" }));
|
|
410
|
+
let values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
411
|
+
|
|
412
|
+
expect(values["ink.palette"]).toEqual({ family: "Red", shade: "900" });
|
|
413
|
+
|
|
414
|
+
fireEvent.click(screen.getByRole("button", { name: "Primary shade 500" }));
|
|
415
|
+
values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
416
|
+
|
|
417
|
+
expect(values["ink.palette"]).toEqual({ family: "Red", shade: "500" });
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("renders local action buttons as a two-column grid below their label", () => {
|
|
391
421
|
const schema = defineToolcraft({
|
|
392
422
|
canvas: { enabled: false },
|
|
393
423
|
panels: {
|
|
@@ -421,10 +451,53 @@ describe("ControlsPanel", () => {
|
|
|
421
451
|
'[data-slot="actions-control-buttons"]',
|
|
422
452
|
);
|
|
423
453
|
|
|
424
|
-
expect(actionField?.
|
|
425
|
-
expect(actionField?.className).toContain("
|
|
426
|
-
expect(actionButtons?.className).toContain("
|
|
454
|
+
expect(actionField?.getAttribute("data-orientation")).toBe("vertical");
|
|
455
|
+
expect(actionField?.className).not.toContain("justify-between");
|
|
456
|
+
expect(actionButtons?.className).toContain("grid");
|
|
457
|
+
expect(actionButtons?.className).toContain("w-full");
|
|
458
|
+
expect(actionButtons?.className).toContain("grid-cols-2");
|
|
459
|
+
expect(actionButtons?.className).not.toContain("justify-start");
|
|
427
460
|
expect(actionButtons?.className).not.toContain("ml-auto");
|
|
461
|
+
for (const label of ["Rect", "Circle", "Tri", "Delete"]) {
|
|
462
|
+
expect(screen.getByRole("button", { name: label }).className).toContain(
|
|
463
|
+
"w-full",
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("keeps a single local action button at half row width", () => {
|
|
469
|
+
const schema = defineToolcraft({
|
|
470
|
+
canvas: { enabled: false },
|
|
471
|
+
panels: {
|
|
472
|
+
controls: {
|
|
473
|
+
sections: [
|
|
474
|
+
{
|
|
475
|
+
controls: {
|
|
476
|
+
paletteActions: {
|
|
477
|
+
actions: [{ label: "Randomize", value: "palette.randomize" }],
|
|
478
|
+
defaultValue: null,
|
|
479
|
+
label: "Actions",
|
|
480
|
+
target: "palette.actions",
|
|
481
|
+
type: "actions",
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
title: "Palette",
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
title: "Controls",
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
492
|
+
const actionButtons = container.querySelector<HTMLElement>(
|
|
493
|
+
'[data-slot="actions-control-buttons"]',
|
|
494
|
+
);
|
|
495
|
+
const button = screen.getByRole("button", { name: "Randomize" });
|
|
496
|
+
|
|
497
|
+
expect(actionButtons?.getAttribute("data-actions-count")).toBe("1");
|
|
498
|
+
expect(actionButtons?.className).toContain("w-1/2");
|
|
499
|
+
expect(actionButtons?.className).toContain("grid-cols-1");
|
|
500
|
+
expect(button.className).toContain("w-full");
|
|
428
501
|
});
|
|
429
502
|
|
|
430
503
|
it("renders the managed background toggle plus unlabeled color row as equal-width inline columns", () => {
|
|
@@ -474,6 +547,7 @@ describe("ControlsPanel", () => {
|
|
|
474
547
|
expect(screen.getByText("Include")).toBeTruthy();
|
|
475
548
|
expect(screen.getByRole("switch")).toBeTruthy();
|
|
476
549
|
expect(toggleParameterGroup).toBeTruthy();
|
|
550
|
+
expect(toggleParameterGroup?.className).toContain("gap-x-2.5");
|
|
477
551
|
expect(toggleParameterGroup?.getAttribute("style")).toContain(
|
|
478
552
|
"repeat(2, minmax(0, 1fr))",
|
|
479
553
|
);
|
|
@@ -616,13 +690,100 @@ describe("ControlsPanel", () => {
|
|
|
616
690
|
);
|
|
617
691
|
|
|
618
692
|
expect(screen.getByText("Loop")).toBeTruthy();
|
|
619
|
-
expect(screen.
|
|
693
|
+
expect(screen.queryByText("Duration")).toBeNull();
|
|
620
694
|
expect(toggleParameterGroup).toBeTruthy();
|
|
695
|
+
expect(toggleParameterGroup?.className).toContain("gap-x-2.5");
|
|
621
696
|
expect(toggleParameterGroup?.getAttribute("style")).toContain(
|
|
622
697
|
"repeat(2, minmax(0, 1fr))",
|
|
623
698
|
);
|
|
624
699
|
});
|
|
625
700
|
|
|
701
|
+
it("keeps segmented controls full-width even when an inline row is requested", () => {
|
|
702
|
+
const schema = defineToolcraft({
|
|
703
|
+
canvas: { enabled: false },
|
|
704
|
+
panels: {
|
|
705
|
+
controls: {
|
|
706
|
+
sections: [
|
|
707
|
+
{
|
|
708
|
+
controls: {
|
|
709
|
+
includeText: {
|
|
710
|
+
defaultValue: true,
|
|
711
|
+
label: "Include",
|
|
712
|
+
target: "text.enabled",
|
|
713
|
+
type: "switch",
|
|
714
|
+
},
|
|
715
|
+
dragTarget: {
|
|
716
|
+
defaultValue: "glass",
|
|
717
|
+
label: "Drag",
|
|
718
|
+
options: [
|
|
719
|
+
{ label: "Glass", value: "glass" },
|
|
720
|
+
{ label: "Text", value: "text" },
|
|
721
|
+
],
|
|
722
|
+
target: "text.dragTarget",
|
|
723
|
+
type: "segmented",
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
layoutGroups: [
|
|
727
|
+
{
|
|
728
|
+
columns: 2,
|
|
729
|
+
controls: ["includeText", "dragTarget"],
|
|
730
|
+
layout: "inline",
|
|
731
|
+
},
|
|
732
|
+
],
|
|
733
|
+
title: "Glass Text",
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
title: "Controls",
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
742
|
+
|
|
743
|
+
expect(screen.getByText("Include")).toBeTruthy();
|
|
744
|
+
expect(screen.getByText("Drag")).toBeTruthy();
|
|
745
|
+
expect(container.querySelector('[data-control-layout="inline"]')).toBeNull();
|
|
746
|
+
expect(container.querySelector('[data-slot="toggle-group"]')).toBeTruthy();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it("renders standalone select controls stacked full-width instead of compact side-label rows", () => {
|
|
750
|
+
const schema = defineToolcraft({
|
|
751
|
+
canvas: { enabled: false },
|
|
752
|
+
panels: {
|
|
753
|
+
controls: {
|
|
754
|
+
sections: [
|
|
755
|
+
{
|
|
756
|
+
controls: {
|
|
757
|
+
shape: {
|
|
758
|
+
defaultValue: "pill",
|
|
759
|
+
label: "Shape",
|
|
760
|
+
options: [
|
|
761
|
+
{ label: "Pill", value: "pill" },
|
|
762
|
+
{ label: "Circle", value: "circle" },
|
|
763
|
+
],
|
|
764
|
+
target: "glass.shape",
|
|
765
|
+
type: "select",
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
title: "Glass",
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
title: "Controls",
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
renderControlsPanelWithSchema(schema);
|
|
777
|
+
const trigger = screen.getByRole("combobox");
|
|
778
|
+
const field = trigger.closest<HTMLElement>('[data-slot="field"]');
|
|
779
|
+
|
|
780
|
+
expect(screen.getByText("Shape")).toBeTruthy();
|
|
781
|
+
expect(trigger.textContent).toContain("Pill");
|
|
782
|
+
expect(field?.dataset.orientation).toBe("vertical");
|
|
783
|
+
expect(field?.className).toContain("gap-2");
|
|
784
|
+
expect(trigger.closest("div")?.className).toContain("w-full");
|
|
785
|
+
});
|
|
786
|
+
|
|
626
787
|
it("renders compact select pairs inline with stacked full-width fields", () => {
|
|
627
788
|
const schema = defineToolcraft({
|
|
628
789
|
canvas: { enabled: false },
|
|
@@ -677,6 +838,7 @@ describe("ControlsPanel", () => {
|
|
|
677
838
|
);
|
|
678
839
|
|
|
679
840
|
expect(inlineGroup).toBeTruthy();
|
|
841
|
+
expect(inlineGroup?.className).toContain("gap-x-2.5");
|
|
680
842
|
expect(screen.getByText("Format")).toBeTruthy();
|
|
681
843
|
expect(screen.getByText("Resolution")).toBeTruthy();
|
|
682
844
|
expect(triggers).toHaveLength(2);
|
|
@@ -1417,16 +1579,399 @@ describe("ControlsPanel", () => {
|
|
|
1417
1579
|
},
|
|
1418
1580
|
});
|
|
1419
1581
|
|
|
1420
|
-
const { container } = renderControlsPanelWithSchema(schema);
|
|
1582
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
1583
|
+
|
|
1584
|
+
expect(
|
|
1585
|
+
container
|
|
1586
|
+
.querySelector('[aria-label="Position X/Y pad"]')
|
|
1587
|
+
?.getAttribute("data-vector-pad-shape"),
|
|
1588
|
+
).toBe("square");
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
it("keeps compact vector pads when the panel has multiple vector controls", () => {
|
|
1592
|
+
const schema = defineToolcraft({
|
|
1593
|
+
canvas: { enabled: true },
|
|
1594
|
+
panels: {
|
|
1595
|
+
controls: {
|
|
1596
|
+
sections: [
|
|
1597
|
+
{
|
|
1598
|
+
controls: {
|
|
1599
|
+
origin: {
|
|
1600
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1601
|
+
label: "Origin",
|
|
1602
|
+
target: "geometry.origin",
|
|
1603
|
+
type: "vector",
|
|
1604
|
+
},
|
|
1605
|
+
target: {
|
|
1606
|
+
defaultValue: { x: "0.50", y: "-0.50" },
|
|
1607
|
+
label: "Target",
|
|
1608
|
+
target: "geometry.target",
|
|
1609
|
+
type: "vector",
|
|
1610
|
+
},
|
|
1611
|
+
},
|
|
1612
|
+
title: "Geometry",
|
|
1613
|
+
},
|
|
1614
|
+
],
|
|
1615
|
+
title: "Controls",
|
|
1616
|
+
},
|
|
1617
|
+
},
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
1621
|
+
|
|
1622
|
+
expect(
|
|
1623
|
+
[...container.querySelectorAll("[data-vector-pad-shape]")].map((pad) =>
|
|
1624
|
+
pad.getAttribute("data-vector-pad-shape"),
|
|
1625
|
+
),
|
|
1626
|
+
).toEqual(["compact", "compact"]);
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
it("passes supported vector pad variants from schema to the vector pad", () => {
|
|
1630
|
+
const schema = defineToolcraft({
|
|
1631
|
+
canvas: { enabled: true },
|
|
1632
|
+
panels: {
|
|
1633
|
+
controls: {
|
|
1634
|
+
sections: [
|
|
1635
|
+
{
|
|
1636
|
+
controls: {
|
|
1637
|
+
whiteBalance: {
|
|
1638
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1639
|
+
label: "White Balance",
|
|
1640
|
+
target: "color.whiteBalance",
|
|
1641
|
+
type: "vector",
|
|
1642
|
+
variant: "whiteBalance",
|
|
1643
|
+
},
|
|
1644
|
+
colorBalance: {
|
|
1645
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1646
|
+
label: "Color Balance",
|
|
1647
|
+
target: "color.balance",
|
|
1648
|
+
type: "vector",
|
|
1649
|
+
variant: "colorBalance",
|
|
1650
|
+
},
|
|
1651
|
+
chromaOffset: {
|
|
1652
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1653
|
+
label: "Chroma Offset",
|
|
1654
|
+
target: "effect.chromaOffset",
|
|
1655
|
+
type: "vector",
|
|
1656
|
+
variant: "chromaOffset",
|
|
1657
|
+
},
|
|
1658
|
+
toneBias: {
|
|
1659
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1660
|
+
label: "Tone Bias",
|
|
1661
|
+
target: "tone.bias",
|
|
1662
|
+
type: "vector",
|
|
1663
|
+
variant: "toneBias",
|
|
1664
|
+
},
|
|
1665
|
+
unknown: {
|
|
1666
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1667
|
+
label: "Unknown",
|
|
1668
|
+
target: "effect.unknown",
|
|
1669
|
+
type: "vector",
|
|
1670
|
+
variant: "unknown",
|
|
1671
|
+
},
|
|
1672
|
+
},
|
|
1673
|
+
title: "Color",
|
|
1674
|
+
},
|
|
1675
|
+
],
|
|
1676
|
+
title: "Controls",
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
1682
|
+
|
|
1683
|
+
expect(
|
|
1684
|
+
[...container.querySelectorAll("[data-vector-pad-variant]")].map((pad) =>
|
|
1685
|
+
pad.getAttribute("data-vector-pad-variant"),
|
|
1686
|
+
),
|
|
1687
|
+
).toEqual([
|
|
1688
|
+
"whiteBalance",
|
|
1689
|
+
"colorBalance",
|
|
1690
|
+
"chromaOffset",
|
|
1691
|
+
"toneBias",
|
|
1692
|
+
"default",
|
|
1693
|
+
]);
|
|
1694
|
+
expect(
|
|
1695
|
+
[...container.querySelectorAll("[data-vector-pad-coordinate-mode]")].map((pad) =>
|
|
1696
|
+
pad.getAttribute("data-vector-pad-coordinate-mode"),
|
|
1697
|
+
),
|
|
1698
|
+
).toEqual([
|
|
1699
|
+
"cartesian",
|
|
1700
|
+
"cartesian",
|
|
1701
|
+
"cartesian",
|
|
1702
|
+
"cartesian",
|
|
1703
|
+
"screen",
|
|
1704
|
+
]);
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
it("maps default spatial vector pad movement to screen coordinates", () => {
|
|
1708
|
+
const schema = defineToolcraft({
|
|
1709
|
+
canvas: { enabled: true },
|
|
1710
|
+
panels: {
|
|
1711
|
+
controls: {
|
|
1712
|
+
sections: [
|
|
1713
|
+
{
|
|
1714
|
+
controls: {
|
|
1715
|
+
position: {
|
|
1716
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1717
|
+
label: "Position",
|
|
1718
|
+
target: "geometry.position",
|
|
1719
|
+
type: "vector",
|
|
1720
|
+
},
|
|
1721
|
+
},
|
|
1722
|
+
title: "Geometry",
|
|
1723
|
+
},
|
|
1724
|
+
],
|
|
1725
|
+
title: "Controls",
|
|
1726
|
+
},
|
|
1727
|
+
},
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
renderControlsPanelWithSchema(schema);
|
|
1731
|
+
const pad = screen.getByRole("button", { name: "Position X/Y pad" });
|
|
1732
|
+
|
|
1733
|
+
expect(pad.getAttribute("data-vector-pad-coordinate-mode")).toBe("screen");
|
|
1734
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
1735
|
+
bottom: 100,
|
|
1736
|
+
height: 100,
|
|
1737
|
+
left: 0,
|
|
1738
|
+
right: 100,
|
|
1739
|
+
toJSON: () => undefined,
|
|
1740
|
+
top: 0,
|
|
1741
|
+
width: 100,
|
|
1742
|
+
x: 0,
|
|
1743
|
+
y: 0,
|
|
1744
|
+
} as DOMRect);
|
|
1745
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
1746
|
+
configurable: true,
|
|
1747
|
+
value: vi.fn(),
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
fireEvent.pointerDown(pad, {
|
|
1751
|
+
buttons: 1,
|
|
1752
|
+
clientX: 0,
|
|
1753
|
+
clientY: 0,
|
|
1754
|
+
pointerId: 1,
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
1758
|
+
expect(values["geometry.position"]).toEqual({ x: "-1.00", y: "-1.00" });
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
it("resets a vector pad to its default value on double click", () => {
|
|
1762
|
+
const schema = defineToolcraft({
|
|
1763
|
+
canvas: { enabled: true },
|
|
1764
|
+
panels: {
|
|
1765
|
+
controls: {
|
|
1766
|
+
sections: [
|
|
1767
|
+
{
|
|
1768
|
+
controls: {
|
|
1769
|
+
position: {
|
|
1770
|
+
defaultValue: { x: "0.48", y: "-0.34" },
|
|
1771
|
+
label: "Position",
|
|
1772
|
+
target: "geometry.position",
|
|
1773
|
+
type: "vector",
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
title: "Geometry",
|
|
1777
|
+
},
|
|
1778
|
+
],
|
|
1779
|
+
title: "Controls",
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
renderControlsPanelWithSchema(schema);
|
|
1785
|
+
const pad = screen.getByRole("button", { name: "Position X/Y pad" });
|
|
1786
|
+
|
|
1787
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
1788
|
+
bottom: 100,
|
|
1789
|
+
height: 100,
|
|
1790
|
+
left: 0,
|
|
1791
|
+
right: 100,
|
|
1792
|
+
toJSON: () => undefined,
|
|
1793
|
+
top: 0,
|
|
1794
|
+
width: 100,
|
|
1795
|
+
x: 0,
|
|
1796
|
+
y: 0,
|
|
1797
|
+
} as DOMRect);
|
|
1798
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
1799
|
+
configurable: true,
|
|
1800
|
+
value: vi.fn(),
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
fireEvent.pointerDown(pad, {
|
|
1804
|
+
buttons: 1,
|
|
1805
|
+
clientX: 0,
|
|
1806
|
+
clientY: 0,
|
|
1807
|
+
pointerId: 1,
|
|
1808
|
+
});
|
|
1809
|
+
expect(
|
|
1810
|
+
JSON.parse(screen.getByTestId("values-json").textContent ?? "{}")[
|
|
1811
|
+
"geometry.position"
|
|
1812
|
+
],
|
|
1813
|
+
).toEqual({ x: "-1.00", y: "-1.00" });
|
|
1814
|
+
|
|
1815
|
+
fireEvent.doubleClick(pad);
|
|
1816
|
+
|
|
1817
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
1818
|
+
expect(values["geometry.position"]).toEqual({ x: "0.48", y: "-0.34" });
|
|
1819
|
+
expect(screen.getByText("0.48, -0.34")).toBeTruthy();
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
it("locks vector pad movement to the dominant axis while shift is held", () => {
|
|
1823
|
+
const schema = defineToolcraft({
|
|
1824
|
+
canvas: { enabled: true },
|
|
1825
|
+
panels: {
|
|
1826
|
+
controls: {
|
|
1827
|
+
sections: [
|
|
1828
|
+
{
|
|
1829
|
+
controls: {
|
|
1830
|
+
position: {
|
|
1831
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1832
|
+
label: "Position",
|
|
1833
|
+
target: "geometry.position",
|
|
1834
|
+
type: "vector",
|
|
1835
|
+
},
|
|
1836
|
+
},
|
|
1837
|
+
title: "Geometry",
|
|
1838
|
+
},
|
|
1839
|
+
],
|
|
1840
|
+
title: "Controls",
|
|
1841
|
+
},
|
|
1842
|
+
},
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
renderControlsPanelWithSchema(schema);
|
|
1846
|
+
const pad = screen.getByRole("button", { name: "Position X/Y pad" });
|
|
1847
|
+
|
|
1848
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
1849
|
+
bottom: 100,
|
|
1850
|
+
height: 100,
|
|
1851
|
+
left: 0,
|
|
1852
|
+
right: 100,
|
|
1853
|
+
toJSON: () => undefined,
|
|
1854
|
+
top: 0,
|
|
1855
|
+
width: 100,
|
|
1856
|
+
x: 0,
|
|
1857
|
+
y: 0,
|
|
1858
|
+
} as DOMRect);
|
|
1859
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
1860
|
+
configurable: true,
|
|
1861
|
+
value: vi.fn(),
|
|
1862
|
+
});
|
|
1863
|
+
|
|
1864
|
+
fireEvent.pointerDown(pad, {
|
|
1865
|
+
buttons: 1,
|
|
1866
|
+
clientX: 50,
|
|
1867
|
+
clientY: 50,
|
|
1868
|
+
pointerId: 1,
|
|
1869
|
+
});
|
|
1870
|
+
fireEvent.pointerMove(pad, {
|
|
1871
|
+
buttons: 1,
|
|
1872
|
+
clientX: 90,
|
|
1873
|
+
clientY: 70,
|
|
1874
|
+
pointerId: 1,
|
|
1875
|
+
shiftKey: true,
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
1879
|
+
expect(values["geometry.position"]).toEqual({ x: "0.80", y: "0.00" });
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
it("locks vector pad movement vertically when shift drag is y-dominant", () => {
|
|
1883
|
+
const schema = defineToolcraft({
|
|
1884
|
+
canvas: { enabled: true },
|
|
1885
|
+
panels: {
|
|
1886
|
+
controls: {
|
|
1887
|
+
sections: [
|
|
1888
|
+
{
|
|
1889
|
+
controls: {
|
|
1890
|
+
position: {
|
|
1891
|
+
defaultValue: { x: "0.00", y: "0.00" },
|
|
1892
|
+
label: "Position",
|
|
1893
|
+
target: "geometry.position",
|
|
1894
|
+
type: "vector",
|
|
1895
|
+
},
|
|
1896
|
+
},
|
|
1897
|
+
title: "Geometry",
|
|
1898
|
+
},
|
|
1899
|
+
],
|
|
1900
|
+
title: "Controls",
|
|
1901
|
+
},
|
|
1902
|
+
},
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1905
|
+
renderControlsPanelWithSchema(schema);
|
|
1906
|
+
const pad = screen.getByRole("button", { name: "Position X/Y pad" });
|
|
1907
|
+
|
|
1908
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
1909
|
+
bottom: 100,
|
|
1910
|
+
height: 100,
|
|
1911
|
+
left: 0,
|
|
1912
|
+
right: 100,
|
|
1913
|
+
toJSON: () => undefined,
|
|
1914
|
+
top: 0,
|
|
1915
|
+
width: 100,
|
|
1916
|
+
x: 0,
|
|
1917
|
+
y: 0,
|
|
1918
|
+
} as DOMRect);
|
|
1919
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
1920
|
+
configurable: true,
|
|
1921
|
+
value: vi.fn(),
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
fireEvent.pointerDown(pad, {
|
|
1925
|
+
buttons: 1,
|
|
1926
|
+
clientX: 50,
|
|
1927
|
+
clientY: 50,
|
|
1928
|
+
pointerId: 1,
|
|
1929
|
+
});
|
|
1930
|
+
fireEvent.pointerMove(pad, {
|
|
1931
|
+
buttons: 1,
|
|
1932
|
+
clientX: 70,
|
|
1933
|
+
clientY: 90,
|
|
1934
|
+
pointerId: 1,
|
|
1935
|
+
shiftKey: true,
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
1939
|
+
expect(values["geometry.position"]).toEqual({ x: "0.00", y: "0.80" });
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
it("renders vector pad values as compact rounded coordinates", () => {
|
|
1943
|
+
const schema = defineToolcraft({
|
|
1944
|
+
canvas: { enabled: true },
|
|
1945
|
+
panels: {
|
|
1946
|
+
controls: {
|
|
1947
|
+
sections: [
|
|
1948
|
+
{
|
|
1949
|
+
controls: {
|
|
1950
|
+
center: {
|
|
1951
|
+
defaultValue: {
|
|
1952
|
+
x: "-0.07070312499999998",
|
|
1953
|
+
y: "-0.00392795138",
|
|
1954
|
+
},
|
|
1955
|
+
label: "Center",
|
|
1956
|
+
target: "geometry.center",
|
|
1957
|
+
type: "vector",
|
|
1958
|
+
},
|
|
1959
|
+
},
|
|
1960
|
+
title: "Geometry",
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1963
|
+
title: "Controls",
|
|
1964
|
+
},
|
|
1965
|
+
},
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
renderControlsPanelWithSchema(schema);
|
|
1421
1969
|
|
|
1422
|
-
expect(
|
|
1423
|
-
|
|
1424
|
-
.querySelector('[aria-label="Position X/Y pad"]')
|
|
1425
|
-
?.getAttribute("data-vector-pad-shape"),
|
|
1426
|
-
).toBe("square");
|
|
1970
|
+
expect(screen.getByText("-0.07, 0.00")).toBeTruthy();
|
|
1971
|
+
expect(screen.queryByText("-0.07070312499999998, -0.00392795138")).toBeNull();
|
|
1427
1972
|
});
|
|
1428
1973
|
|
|
1429
|
-
it("
|
|
1974
|
+
it("prevents native text selection while dragging a vector pad", () => {
|
|
1430
1975
|
const schema = defineToolcraft({
|
|
1431
1976
|
canvas: { enabled: true },
|
|
1432
1977
|
panels: {
|
|
@@ -1434,16 +1979,10 @@ describe("ControlsPanel", () => {
|
|
|
1434
1979
|
sections: [
|
|
1435
1980
|
{
|
|
1436
1981
|
controls: {
|
|
1437
|
-
|
|
1982
|
+
center: {
|
|
1438
1983
|
defaultValue: { x: "0.00", y: "0.00" },
|
|
1439
|
-
label: "
|
|
1440
|
-
target: "geometry.
|
|
1441
|
-
type: "vector",
|
|
1442
|
-
},
|
|
1443
|
-
target: {
|
|
1444
|
-
defaultValue: { x: "0.50", y: "-0.50" },
|
|
1445
|
-
label: "Target",
|
|
1446
|
-
target: "geometry.target",
|
|
1984
|
+
label: "Center",
|
|
1985
|
+
target: "geometry.center",
|
|
1447
1986
|
type: "vector",
|
|
1448
1987
|
},
|
|
1449
1988
|
},
|
|
@@ -1455,16 +1994,39 @@ describe("ControlsPanel", () => {
|
|
|
1455
1994
|
},
|
|
1456
1995
|
});
|
|
1457
1996
|
|
|
1458
|
-
|
|
1997
|
+
renderControlsPanelWithSchema(schema);
|
|
1998
|
+
const pad = screen.getByRole("button", { name: "Center X/Y pad" });
|
|
1459
1999
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
2000
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
2001
|
+
bottom: 100,
|
|
2002
|
+
height: 100,
|
|
2003
|
+
left: 0,
|
|
2004
|
+
right: 100,
|
|
2005
|
+
toJSON: () => undefined,
|
|
2006
|
+
top: 0,
|
|
2007
|
+
width: 100,
|
|
2008
|
+
x: 0,
|
|
2009
|
+
y: 0,
|
|
2010
|
+
} as DOMRect);
|
|
2011
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
2012
|
+
configurable: true,
|
|
2013
|
+
value: vi.fn(),
|
|
2014
|
+
});
|
|
2015
|
+
|
|
2016
|
+
const pointerDown = createEvent.pointerDown(pad, {
|
|
2017
|
+
buttons: 1,
|
|
2018
|
+
clientX: 50,
|
|
2019
|
+
clientY: 50,
|
|
2020
|
+
pointerId: 1,
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
fireEvent(pad, pointerDown);
|
|
2024
|
+
|
|
2025
|
+
expect(pointerDown.defaultPrevented).toBe(true);
|
|
2026
|
+
expect(pad.className).toContain("select-none");
|
|
1465
2027
|
});
|
|
1466
2028
|
|
|
1467
|
-
it("
|
|
2029
|
+
it("allows cartesian vector pads when a product intentionally uses y-up math", () => {
|
|
1468
2030
|
const schema = defineToolcraft({
|
|
1469
2031
|
canvas: { enabled: true },
|
|
1470
2032
|
panels: {
|
|
@@ -1472,43 +2034,15 @@ describe("ControlsPanel", () => {
|
|
|
1472
2034
|
sections: [
|
|
1473
2035
|
{
|
|
1474
2036
|
controls: {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
label: "White Balance",
|
|
1478
|
-
target: "color.whiteBalance",
|
|
1479
|
-
type: "vector",
|
|
1480
|
-
variant: "whiteBalance",
|
|
1481
|
-
},
|
|
1482
|
-
colorBalance: {
|
|
1483
|
-
defaultValue: { x: "0.00", y: "0.00" },
|
|
1484
|
-
label: "Color Balance",
|
|
1485
|
-
target: "color.balance",
|
|
1486
|
-
type: "vector",
|
|
1487
|
-
variant: "colorBalance",
|
|
1488
|
-
},
|
|
1489
|
-
chromaOffset: {
|
|
1490
|
-
defaultValue: { x: "0.00", y: "0.00" },
|
|
1491
|
-
label: "Chroma Offset",
|
|
1492
|
-
target: "effect.chromaOffset",
|
|
1493
|
-
type: "vector",
|
|
1494
|
-
variant: "chromaOffset",
|
|
1495
|
-
},
|
|
1496
|
-
toneBias: {
|
|
1497
|
-
defaultValue: { x: "0.00", y: "0.00" },
|
|
1498
|
-
label: "Tone Bias",
|
|
1499
|
-
target: "tone.bias",
|
|
1500
|
-
type: "vector",
|
|
1501
|
-
variant: "toneBias",
|
|
1502
|
-
},
|
|
1503
|
-
unknown: {
|
|
2037
|
+
vector: {
|
|
2038
|
+
coordinateMode: "cartesian",
|
|
1504
2039
|
defaultValue: { x: "0.00", y: "0.00" },
|
|
1505
|
-
label: "
|
|
1506
|
-
target: "
|
|
2040
|
+
label: "Vector",
|
|
2041
|
+
target: "math.vector",
|
|
1507
2042
|
type: "vector",
|
|
1508
|
-
variant: "unknown",
|
|
1509
2043
|
},
|
|
1510
2044
|
},
|
|
1511
|
-
title: "
|
|
2045
|
+
title: "Math",
|
|
1512
2046
|
},
|
|
1513
2047
|
],
|
|
1514
2048
|
title: "Controls",
|
|
@@ -1516,19 +2050,35 @@ describe("ControlsPanel", () => {
|
|
|
1516
2050
|
},
|
|
1517
2051
|
});
|
|
1518
2052
|
|
|
1519
|
-
|
|
2053
|
+
renderControlsPanelWithSchema(schema);
|
|
2054
|
+
const pad = screen.getByRole("button", { name: "Vector X/Y pad" });
|
|
1520
2055
|
|
|
1521
|
-
expect(
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
2056
|
+
expect(pad.getAttribute("data-vector-pad-coordinate-mode")).toBe("cartesian");
|
|
2057
|
+
vi.spyOn(pad, "getBoundingClientRect").mockReturnValue({
|
|
2058
|
+
bottom: 100,
|
|
2059
|
+
height: 100,
|
|
2060
|
+
left: 0,
|
|
2061
|
+
right: 100,
|
|
2062
|
+
toJSON: () => undefined,
|
|
2063
|
+
top: 0,
|
|
2064
|
+
width: 100,
|
|
2065
|
+
x: 0,
|
|
2066
|
+
y: 0,
|
|
2067
|
+
} as DOMRect);
|
|
2068
|
+
Object.defineProperty(pad, "setPointerCapture", {
|
|
2069
|
+
configurable: true,
|
|
2070
|
+
value: vi.fn(),
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
fireEvent.pointerDown(pad, {
|
|
2074
|
+
buttons: 1,
|
|
2075
|
+
clientX: 0,
|
|
2076
|
+
clientY: 0,
|
|
2077
|
+
pointerId: 1,
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
2081
|
+
expect(values["math.vector"]).toEqual({ x: "-1.00", y: "1.00" });
|
|
1532
2082
|
});
|
|
1533
2083
|
|
|
1534
2084
|
it("keeps explanatory label text in the native title instead of the visible label", () => {
|
|
@@ -1758,7 +2308,6 @@ describe("ControlsPanel", () => {
|
|
|
1758
2308
|
step: 0.1,
|
|
1759
2309
|
target: "animation.speed",
|
|
1760
2310
|
type: "slider",
|
|
1761
|
-
unit: "x",
|
|
1762
2311
|
},
|
|
1763
2312
|
},
|
|
1764
2313
|
layoutGroups: [
|
|
@@ -1785,7 +2334,7 @@ describe("ControlsPanel", () => {
|
|
|
1785
2334
|
expect(fpsSlider).toBeTruthy();
|
|
1786
2335
|
expect(container.querySelectorAll('[data-slot="slider-marker"]').length).toBeGreaterThan(0);
|
|
1787
2336
|
expect(container.textContent).toContain("17 fps");
|
|
1788
|
-
expect(container.textContent).toContain("3.
|
|
2337
|
+
expect(container.textContent).toContain("3.7");
|
|
1789
2338
|
});
|
|
1790
2339
|
|
|
1791
2340
|
it("passes schema disabled state into slider controls", () => {
|
|
@@ -2233,9 +2782,12 @@ describe("ControlsPanel", () => {
|
|
|
2233
2782
|
|
|
2234
2783
|
for (const [draftValue, expectedValue, expectedLabel] of [
|
|
2235
2784
|
["0/1", '"shape.range":[0,1]', "0% – 1%"],
|
|
2785
|
+
["1-6", '"shape.range":[1,6]', "1% – 6%"],
|
|
2236
2786
|
["2-3", '"shape.range":[2,3]', "2% – 3%"],
|
|
2237
2787
|
["4 - 5", '"shape.range":[4,5]', "4% – 5%"],
|
|
2238
2788
|
["6–7", '"shape.range":[6,7]', "6% – 7%"],
|
|
2789
|
+
["7—8", '"shape.range":[7,8]', "7% – 8%"],
|
|
2790
|
+
["8−9", '"shape.range":[8,9]', "8% – 9%"],
|
|
2239
2791
|
] as const) {
|
|
2240
2792
|
fireEvent.click(screen.getByRole("button", { name: "Edit Range value" }));
|
|
2241
2793
|
const editor = screen.getByRole("textbox", { name: "Range value" });
|
|
@@ -2374,7 +2926,7 @@ describe("ControlsPanel", () => {
|
|
|
2374
2926
|
},
|
|
2375
2927
|
});
|
|
2376
2928
|
|
|
2377
|
-
renderControlsPanelWithSchema(schema, undefined, {
|
|
2929
|
+
const { container } = renderControlsPanelWithSchema(schema, undefined, {
|
|
2378
2930
|
layers: [
|
|
2379
2931
|
{
|
|
2380
2932
|
id: "layer-1",
|
|
@@ -2399,9 +2951,66 @@ describe("ControlsPanel", () => {
|
|
|
2399
2951
|
});
|
|
2400
2952
|
|
|
2401
2953
|
expect(screen.getByRole("img", { name: "material.svg" })).toBeTruthy();
|
|
2954
|
+
const transformActions = container.querySelector(
|
|
2955
|
+
'[data-slot="actions-control"][aria-label="Image transforms"]',
|
|
2956
|
+
);
|
|
2957
|
+
const transformActionButtons = transformActions?.querySelector(
|
|
2958
|
+
'[data-slot="actions-control-buttons"]',
|
|
2959
|
+
);
|
|
2960
|
+
|
|
2961
|
+
expect(transformActions).toBeTruthy();
|
|
2962
|
+
expect(transformActions?.parentElement?.style.gap).toBe("6px");
|
|
2963
|
+
expect(transformActionButtons?.className).toContain("grid-cols-3");
|
|
2964
|
+
expect(transformActionButtons?.getAttribute("data-actions-columns")).toBe("3");
|
|
2965
|
+
expect(screen.queryByRole("button", { name: "90° Left" })).toBeNull();
|
|
2966
|
+
|
|
2967
|
+
for (const [name, visibleLabel] of [
|
|
2968
|
+
["90° Right", "90°"],
|
|
2969
|
+
["Flip horizontal", "Flip H"],
|
|
2970
|
+
["Flip vertical", "Flip V"],
|
|
2971
|
+
] as const) {
|
|
2972
|
+
const button = screen.getByRole("button", { name });
|
|
2973
|
+
|
|
2974
|
+
expect(button).toBeTruthy();
|
|
2975
|
+
expect(button.textContent).toContain(visibleLabel);
|
|
2976
|
+
}
|
|
2402
2977
|
expect(
|
|
2403
2978
|
screen.queryByText("Click to upload an image"),
|
|
2404
2979
|
).toBeNull();
|
|
2980
|
+
const initialPreviewFrameStyle = container
|
|
2981
|
+
.querySelector('[data-slot="file-upload-preview-frame"]')
|
|
2982
|
+
?.getAttribute("style");
|
|
2983
|
+
|
|
2984
|
+
expect(initialPreviewFrameStyle).toBe(
|
|
2985
|
+
"aspect-ratio: 96 / 64; max-height: 196px; width: 100%;",
|
|
2986
|
+
);
|
|
2987
|
+
expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe(
|
|
2988
|
+
"transform: translate(-50%, -50%);",
|
|
2989
|
+
);
|
|
2990
|
+
|
|
2991
|
+
fireEvent.click(screen.getByRole("button", { name: "90° Right" }));
|
|
2992
|
+
expect(screen.getByTestId("media-transforms").textContent).toBe(
|
|
2993
|
+
'[{"rotationDeg":90}]',
|
|
2994
|
+
);
|
|
2995
|
+
expect(
|
|
2996
|
+
container.querySelector('[data-slot="file-upload-preview-frame"]')?.getAttribute("style"),
|
|
2997
|
+
).toBe(initialPreviewFrameStyle);
|
|
2998
|
+
expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe(
|
|
2999
|
+
"transform: translate(-50%, -50%) rotate(90deg); width: 66.6667%;",
|
|
3000
|
+
);
|
|
3001
|
+
|
|
3002
|
+
fireEvent.click(screen.getByRole("button", { name: "Flip horizontal" }));
|
|
3003
|
+
expect(screen.getByTestId("media-transforms").textContent).toBe(
|
|
3004
|
+
'[{"rotationDeg":90,"flipHorizontal":true}]',
|
|
3005
|
+
);
|
|
3006
|
+
expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe(
|
|
3007
|
+
"transform: translate(-50%, -50%) rotate(90deg) scale(-1, 1); width: 66.6667%;",
|
|
3008
|
+
);
|
|
3009
|
+
|
|
3010
|
+
fireEvent.click(screen.getByRole("button", { name: "Flip vertical" }));
|
|
3011
|
+
expect(screen.getByTestId("media-transforms").textContent).toBe(
|
|
3012
|
+
'[{"rotationDeg":90,"flipHorizontal":true,"flipVertical":true}]',
|
|
3013
|
+
);
|
|
2405
3014
|
|
|
2406
3015
|
fireEvent.click(screen.getByRole("button", { name: "Remove image" }));
|
|
2407
3016
|
|
|
@@ -2410,6 +3019,113 @@ describe("ControlsPanel", () => {
|
|
|
2410
3019
|
expect(screen.getByText("Click to upload an image")).toBeTruthy();
|
|
2411
3020
|
});
|
|
2412
3021
|
|
|
3022
|
+
it("renders schema default media as a removable fileDrop attachment", () => {
|
|
3023
|
+
const schema = defineToolcraft({
|
|
3024
|
+
canvas: { enabled: true, upload: true },
|
|
3025
|
+
media: {
|
|
3026
|
+
defaultAssets: [
|
|
3027
|
+
{
|
|
3028
|
+
dataUrl:
|
|
3029
|
+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='64' viewBox='0 0 96 64'%3E%3Crect width='96' height='64' fill='%23777'/%3E%3C/svg%3E",
|
|
3030
|
+
fileName: "default-background.svg",
|
|
3031
|
+
id: "default-background",
|
|
3032
|
+
layerId: "default-background-layer",
|
|
3033
|
+
mimeType: "image/svg+xml",
|
|
3034
|
+
size: { height: 64, unit: "px", width: 96 },
|
|
3035
|
+
sourceTarget: "input.source",
|
|
3036
|
+
},
|
|
3037
|
+
],
|
|
3038
|
+
},
|
|
3039
|
+
panels: {
|
|
3040
|
+
controls: {
|
|
3041
|
+
sections: [
|
|
3042
|
+
{
|
|
3043
|
+
controls: {
|
|
3044
|
+
source: {
|
|
3045
|
+
accept: "PNG, SVG",
|
|
3046
|
+
label: "Image",
|
|
3047
|
+
target: "input.source",
|
|
3048
|
+
type: "fileDrop",
|
|
3049
|
+
},
|
|
3050
|
+
},
|
|
3051
|
+
layout: "standalone",
|
|
3052
|
+
title: "Input",
|
|
3053
|
+
},
|
|
3054
|
+
],
|
|
3055
|
+
title: "Generation Controls",
|
|
3056
|
+
},
|
|
3057
|
+
},
|
|
3058
|
+
});
|
|
3059
|
+
|
|
3060
|
+
renderControlsPanelWithSchema(schema);
|
|
3061
|
+
|
|
3062
|
+
expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy();
|
|
3063
|
+
expect(screen.getByTestId("media-ids").textContent).toBe("default-background");
|
|
3064
|
+
|
|
3065
|
+
fireEvent.click(screen.getByRole("button", { name: "Remove image" }));
|
|
3066
|
+
|
|
3067
|
+
expect(screen.getByTestId("media-count").textContent).toBe("0");
|
|
3068
|
+
expect(screen.queryByRole("img", { name: "default-background.svg" })).toBeNull();
|
|
3069
|
+
expect(screen.getByText("Click to upload an image")).toBeTruthy();
|
|
3070
|
+
|
|
3071
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset Input section" }));
|
|
3072
|
+
|
|
3073
|
+
expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy();
|
|
3074
|
+
expect(screen.getByTestId("media-count").textContent).toBe("1");
|
|
3075
|
+
|
|
3076
|
+
fireEvent.click(screen.getByRole("button", { name: "Remove image" }));
|
|
3077
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset controls" }));
|
|
3078
|
+
|
|
3079
|
+
expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy();
|
|
3080
|
+
expect(screen.getByTestId("media-count").textContent).toBe("1");
|
|
3081
|
+
});
|
|
3082
|
+
|
|
3083
|
+
it("keeps the current editable canvas size when uploading an image through fileDrop", async () => {
|
|
3084
|
+
const schema = defineToolcraft({
|
|
3085
|
+
canvas: {
|
|
3086
|
+
enabled: true,
|
|
3087
|
+
size: { height: 720, unit: "px", width: 1280 },
|
|
3088
|
+
upload: true,
|
|
3089
|
+
},
|
|
3090
|
+
panels: {
|
|
3091
|
+
controls: {
|
|
3092
|
+
sections: [
|
|
3093
|
+
{
|
|
3094
|
+
controls: {
|
|
3095
|
+
source: {
|
|
3096
|
+
label: "Image",
|
|
3097
|
+
target: "input.source",
|
|
3098
|
+
type: "fileDrop",
|
|
3099
|
+
},
|
|
3100
|
+
},
|
|
3101
|
+
layout: "standalone",
|
|
3102
|
+
title: "Input",
|
|
3103
|
+
},
|
|
3104
|
+
],
|
|
3105
|
+
title: "Generation Controls",
|
|
3106
|
+
},
|
|
3107
|
+
},
|
|
3108
|
+
});
|
|
3109
|
+
const { container } = renderControlsPanelWithSchema(schema);
|
|
3110
|
+
const input = container.querySelector<HTMLInputElement>('input[type="file"]');
|
|
3111
|
+
|
|
3112
|
+
expect(input).toBeTruthy();
|
|
3113
|
+
expect(screen.getByTestId("canvas-size").textContent).toBe("1280,720");
|
|
3114
|
+
|
|
3115
|
+
fireEvent.change(input as HTMLInputElement, {
|
|
3116
|
+
target: {
|
|
3117
|
+
files: [new File(["image"], "wide.png", { type: "image/png" })],
|
|
3118
|
+
},
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
await waitFor(() => {
|
|
3122
|
+
expect(screen.getByTestId("media-count").textContent).toBe("1");
|
|
3123
|
+
});
|
|
3124
|
+
|
|
3125
|
+
expect(screen.getByTestId("canvas-size").textContent).toBe("1280,720");
|
|
3126
|
+
expect(screen.getByTestId("media-sizes").textContent).toBe("1280x720");
|
|
3127
|
+
});
|
|
3128
|
+
|
|
2413
3129
|
it("removes single-layer file uploads from the canvas and returns the file control to empty", () => {
|
|
2414
3130
|
const schema = defineToolcraft({
|
|
2415
3131
|
canvas: { enabled: true, upload: true },
|
|
@@ -2544,6 +3260,7 @@ describe("ControlsPanel", () => {
|
|
|
2544
3260
|
expect(screen.getByRole("img", { name: "one.svg" })).toBeTruthy();
|
|
2545
3261
|
expect(screen.getByRole("img", { name: "two.svg" })).toBeTruthy();
|
|
2546
3262
|
expect(screen.getByTestId("media-ids").textContent).toBe("media-1,media-2");
|
|
3263
|
+
expect(screen.queryByRole("button", { name: "90° Left" })).toBeNull();
|
|
2547
3264
|
|
|
2548
3265
|
const firstPreview = screen
|
|
2549
3266
|
.getByRole("img", { name: "one.svg" })
|
|
@@ -2560,6 +3277,15 @@ describe("ControlsPanel", () => {
|
|
|
2560
3277
|
expect(firstPreview.getAttribute("tabindex")).toBe("0");
|
|
2561
3278
|
expect(firstPreview.className).toContain("cursor-grab");
|
|
2562
3279
|
|
|
3280
|
+
fireEvent.click(firstPreview);
|
|
3281
|
+
expect(firstPreview.getAttribute("data-selected")).toBe("true");
|
|
3282
|
+
expect(screen.getByRole("button", { name: "90° Right" })).toBeTruthy();
|
|
3283
|
+
|
|
3284
|
+
fireEvent.click(screen.getByRole("button", { name: "90° Right" }));
|
|
3285
|
+
expect(screen.getByTestId("media-transforms").textContent).toBe(
|
|
3286
|
+
'[{"rotationDeg":90},null]',
|
|
3287
|
+
);
|
|
3288
|
+
|
|
2563
3289
|
fireEvent.click(screen.getByRole("button", { name: "Remove two.svg" }));
|
|
2564
3290
|
|
|
2565
3291
|
expect(screen.getByTestId("media-count").textContent).toBe("1");
|
|
@@ -2944,6 +3670,35 @@ describe("ControlsPanel", () => {
|
|
|
2944
3670
|
});
|
|
2945
3671
|
});
|
|
2946
3672
|
|
|
3673
|
+
it("applies color picker slider changes while dragging", async () => {
|
|
3674
|
+
const onChange = vi.fn();
|
|
3675
|
+
|
|
3676
|
+
render(<StyleGuideColorPicker value="#C1FF00" onChange={onChange} />);
|
|
3677
|
+
|
|
3678
|
+
const hueSlider = screen.getByRole("slider", { name: "Color hue" });
|
|
3679
|
+
Object.defineProperty(hueSlider, "getBoundingClientRect", {
|
|
3680
|
+
configurable: true,
|
|
3681
|
+
value: () => ({
|
|
3682
|
+
bottom: 18,
|
|
3683
|
+
height: 18,
|
|
3684
|
+
left: 0,
|
|
3685
|
+
right: 360,
|
|
3686
|
+
top: 0,
|
|
3687
|
+
width: 360,
|
|
3688
|
+
x: 0,
|
|
3689
|
+
y: 0,
|
|
3690
|
+
toJSON: () => ({}),
|
|
3691
|
+
}),
|
|
3692
|
+
});
|
|
3693
|
+
|
|
3694
|
+
fireEvent.pointerDown(hueSlider, { clientX: 0 });
|
|
3695
|
+
fireEvent.pointerMove(window, { clientX: 180 });
|
|
3696
|
+
|
|
3697
|
+
await waitFor(() => {
|
|
3698
|
+
expect(onChange).toHaveBeenLastCalledWith("#00ffff");
|
|
3699
|
+
});
|
|
3700
|
+
});
|
|
3701
|
+
|
|
2947
3702
|
it("keeps two color opacity controls stacked even when an inline row is requested", () => {
|
|
2948
3703
|
const schema = defineToolcraft({
|
|
2949
3704
|
canvas: { enabled: true },
|
|
@@ -3416,19 +4171,17 @@ describe("ControlsPanel", () => {
|
|
|
3416
4171
|
expect(gradientControlItem?.className).toContain(
|
|
3417
4172
|
"has-data-[control-section-divider=compound]:after:inset-x-3",
|
|
3418
4173
|
);
|
|
3419
|
-
expect(fontControlItem?.dataset.controlItemCompoundDividerPlacement).toBe(
|
|
3420
|
-
"both",
|
|
3421
|
-
);
|
|
4174
|
+
expect(fontControlItem?.dataset.controlItemCompoundDividerPlacement).toBe("top");
|
|
3422
4175
|
expect(fontControlItem?.className).toContain(
|
|
3423
4176
|
"has-data-[control-section-divider=compound]:pt-[18px]",
|
|
3424
4177
|
);
|
|
3425
|
-
expect(fontControlItem?.className).toContain(
|
|
4178
|
+
expect(fontControlItem?.className).not.toContain(
|
|
3426
4179
|
"has-data-[control-section-divider=compound]:pb-[18px]",
|
|
3427
4180
|
);
|
|
3428
4181
|
expect(fontControlItem?.className).toContain(
|
|
3429
4182
|
"has-data-[control-section-divider=compound]:before:inset-x-3",
|
|
3430
4183
|
);
|
|
3431
|
-
expect(fontControlItem?.className).toContain(
|
|
4184
|
+
expect(fontControlItem?.className).not.toContain(
|
|
3432
4185
|
"has-data-[control-section-divider=compound]:after:inset-x-3",
|
|
3433
4186
|
);
|
|
3434
4187
|
});
|
|
@@ -3502,7 +4255,10 @@ describe("ControlsPanel", () => {
|
|
|
3502
4255
|
fireEvent.change(promptInput, { target: { value: "Updated prompt" } });
|
|
3503
4256
|
expect(screen.getByTestId("prompt-value").textContent).toBe("Updated prompt");
|
|
3504
4257
|
|
|
3505
|
-
|
|
4258
|
+
const enabledField = screen.getByText("Enabled").closest('[role="group"]');
|
|
4259
|
+
const enabledSwitch = enabledField?.querySelector<HTMLElement>('[role="switch"]');
|
|
4260
|
+
|
|
4261
|
+
fireEvent.click(enabledSwitch as HTMLElement);
|
|
3506
4262
|
expect(screen.getByTestId("enabled-value").textContent).toBe("false");
|
|
3507
4263
|
|
|
3508
4264
|
fireEvent.click(screen.getByRole("button", { name: "Reset controls" }));
|
|
@@ -3948,10 +4704,58 @@ describe("ControlsPanel", () => {
|
|
|
3948
4704
|
const values = JSON.parse(screen.getByTestId("values-json").textContent ?? "{}");
|
|
3949
4705
|
|
|
3950
4706
|
expect(screen.getByText("Resolution scale")).toBeTruthy();
|
|
3951
|
-
expect(screen.getByText("
|
|
4707
|
+
expect(screen.getByText("2")).toBeTruthy();
|
|
3952
4708
|
expect(values["canvas.renderScale"]).toBe(2);
|
|
3953
4709
|
});
|
|
3954
4710
|
|
|
4711
|
+
it("renders timeline extended mode as runtime panel state instead of product values", () => {
|
|
4712
|
+
const schema = defineToolcraft({
|
|
4713
|
+
canvas: {
|
|
4714
|
+
enabled: true,
|
|
4715
|
+
renderScale: true,
|
|
4716
|
+
size: { height: 1080, unit: "px", width: 1920 },
|
|
4717
|
+
sizing: { mode: "editable-output" },
|
|
4718
|
+
},
|
|
4719
|
+
panels: {
|
|
4720
|
+
controls: {
|
|
4721
|
+
sections: [],
|
|
4722
|
+
title: "Controls",
|
|
4723
|
+
},
|
|
4724
|
+
timeline: { mode: "playback" },
|
|
4725
|
+
},
|
|
4726
|
+
});
|
|
4727
|
+
|
|
4728
|
+
renderControlsPanelWithSchema(schema, undefined, {
|
|
4729
|
+
timeline: { currentTimeSeconds: 2, expanded: true, isPlaying: true },
|
|
4730
|
+
});
|
|
4731
|
+
|
|
4732
|
+
const setupText = screen.getByText("Timeline").closest("section")?.textContent ?? "";
|
|
4733
|
+
|
|
4734
|
+
expect(setupText.indexOf("Resolution scale")).toBeLessThan(setupText.indexOf("Timeline"));
|
|
4735
|
+
const timelineField = screen.getByText("Timeline").closest('[role="group"]');
|
|
4736
|
+
const timelineSwitch = timelineField?.querySelector<HTMLElement>('[role="switch"]');
|
|
4737
|
+
|
|
4738
|
+
expect(timelineSwitch?.getAttribute("aria-checked")).toBe("false");
|
|
4739
|
+
expect(screen.getByTestId("timeline-panel-extended").textContent).toBe("undefined");
|
|
4740
|
+
expect(screen.getByTestId("timeline-panel-hidden").textContent).toBe("undefined");
|
|
4741
|
+
expect(screen.getByTestId("timeline-expanded").textContent).toBe("true");
|
|
4742
|
+
|
|
4743
|
+
fireEvent.click(timelineSwitch as HTMLElement);
|
|
4744
|
+
|
|
4745
|
+
expect(timelineSwitch?.getAttribute("aria-checked")).toBe("true");
|
|
4746
|
+
expect(screen.getByTestId("timeline-panel-extended").textContent).toBe("true");
|
|
4747
|
+
expect(screen.getByTestId("timeline-panel-hidden").textContent).toBe("undefined");
|
|
4748
|
+
expect(screen.getByTestId("timeline-expanded").textContent).toBe("true");
|
|
4749
|
+
expect(JSON.parse(screen.getByTestId("values-json").textContent ?? "{}")).not.toHaveProperty(
|
|
4750
|
+
"panels.timeline.extended",
|
|
4751
|
+
);
|
|
4752
|
+
|
|
4753
|
+
fireEvent.click(screen.getByRole("button", { name: "Reset controls" }));
|
|
4754
|
+
|
|
4755
|
+
expect(timelineSwitch?.getAttribute("aria-checked")).toBe("true");
|
|
4756
|
+
expect(screen.getByTestId("timeline-panel-extended").textContent).toBe("true");
|
|
4757
|
+
});
|
|
4758
|
+
|
|
3955
4759
|
it("applies empty text input values while typing and reset restores defaults", () => {
|
|
3956
4760
|
renderControlsPanel();
|
|
3957
4761
|
|
|
@@ -4188,7 +4992,7 @@ describe("ControlsPanel", () => {
|
|
|
4188
4992
|
expect(handledActions).toEqual(["download:Generation Controls"]);
|
|
4189
4993
|
});
|
|
4190
4994
|
|
|
4191
|
-
it("renders runtime settings transfer as a body section instead of sticky footer actions", () => {
|
|
4995
|
+
it("renders runtime settings transfer as a headerless body section instead of sticky footer actions", () => {
|
|
4192
4996
|
const schema = defineToolcraft({
|
|
4193
4997
|
canvas: { enabled: true, size: { height: 720, unit: "px", width: 1280 } },
|
|
4194
4998
|
panels: {
|
|
@@ -4252,10 +5056,12 @@ describe("ControlsPanel", () => {
|
|
|
4252
5056
|
).not.toContain("Setup");
|
|
4253
5057
|
expect(firstSection?.querySelector('[data-slot="panel-title"]')).toBeNull();
|
|
4254
5058
|
expect(firstSection?.querySelector("[data-control-section-collapse-button]")).toBeNull();
|
|
5059
|
+
expect(firstSection?.querySelector('[data-slot="control-section-header"]')).toBeNull();
|
|
5060
|
+
expect(firstSection?.querySelector('[data-slot="panel-section-collapsible-body"]')).toBeNull();
|
|
5061
|
+
expect(firstSection?.getAttribute("data-collapsed")).toBeNull();
|
|
4255
5062
|
expect(firstSection?.className).toContain("py-0");
|
|
4256
|
-
expect(firstSection?.querySelector("[data-control-list]")?.className).toContain("
|
|
4257
|
-
expect(firstSection?.querySelector("[data-control-list]")?.className).
|
|
4258
|
-
expect(firstSection?.querySelector("[data-control-list]")?.className).not.toContain("pb-5");
|
|
5063
|
+
expect(firstSection?.querySelector("[data-control-list]")?.className).toContain("pt-2");
|
|
5064
|
+
expect(firstSection?.querySelector("[data-control-list]")?.className).toContain("pb-6");
|
|
4259
5065
|
expect(firstInlineGroup?.textContent).toContain("Canvas width");
|
|
4260
5066
|
expect(firstInlineGroup?.textContent).toContain("Canvas height");
|
|
4261
5067
|
expect(firstSection?.textContent).toContain("Aspect ratio");
|
|
@@ -4497,10 +5303,12 @@ describe("ControlsPanel", () => {
|
|
|
4497
5303
|
const stickyFooter = container.querySelector(
|
|
4498
5304
|
'[data-slot="toolcraft-panel-sticky-actions"]',
|
|
4499
5305
|
);
|
|
5306
|
+
const exportPngButton = screen.getByRole("button", { name: "Export PNG" });
|
|
4500
5307
|
|
|
4501
5308
|
expect(stickyFooter?.getAttribute("data-sticky-footer-active")).toBeNull();
|
|
5309
|
+
expect(exportPngButton.querySelector('[data-icon-name="upload-simple"]')).toBeTruthy();
|
|
4502
5310
|
|
|
4503
|
-
fireEvent.click(
|
|
5311
|
+
fireEvent.click(exportPngButton);
|
|
4504
5312
|
|
|
4505
5313
|
expect(onPanelAction).toHaveBeenCalledTimes(1);
|
|
4506
5314
|
await waitFor(() => {
|