@hyperframes/studio 0.7.58 → 0.7.60
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/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-3XTTaVNf.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-D6etaey-.js} +1 -1
- package/dist/assets/index-DXbu6IPT.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-Dh_WhagG.js} +1 -1
- package/dist/assets/index-cH6NfVV_.js +426 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13755 -8627
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +4 -4
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +42 -53
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +107 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/contexts/PanelLayoutContext.tsx +3 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineEditingHelpers.ts +2 -2
- package/src/hooks/timelineTrackVisibility.test.ts +52 -0
- package/src/hooks/timelineTrackVisibility.ts +27 -10
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.test.tsx +41 -1
- package/src/hooks/useDomEditCommits.ts +31 -177
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useEditorSave.ts +1 -1
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +109 -0
- package/src/hooks/useFileManager.ts +151 -65
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePanelLayout.test.ts +118 -0
- package/src/hooks/usePanelLayout.ts +24 -1
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/usePreviewPersistence.ts +4 -1
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useRazorSplit.history.test.tsx +12 -4
- package/src/hooks/useRazorSplit.test.tsx +88 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +3 -2
- package/src/hooks/useRazorSplit.ts +50 -16
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +4 -1
- package/src/hooks/useTimelineEditing.ts +18 -10
- package/src/hooks/useTimelineEditingTypes.ts +5 -1
- package/src/hooks/useTimelineGroupEditing.ts +17 -3
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/domEditSaveQueue.test.ts +19 -0
- package/src/utils/domEditSaveQueue.ts +2 -1
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +511 -62
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +293 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +34 -29
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioFileVersion.test.ts +36 -0
- package/src/utils/studioFileVersion.ts +23 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.test.ts +18 -0
- package/src/utils/studioSaveDiagnostics.ts +38 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import {
|
|
7
|
+
FlatColorGradingAccessory,
|
|
8
|
+
FlatColorGradingSection,
|
|
9
|
+
} from "./propertyPanelFlatColorGradingSection";
|
|
10
|
+
import { normalizeHfColorGrading } from "@hyperframes/core/color-grading";
|
|
11
|
+
|
|
12
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
document.body.innerHTML = "";
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function renderInto(node: React.ReactElement) {
|
|
19
|
+
const host = document.createElement("div");
|
|
20
|
+
document.body.append(host);
|
|
21
|
+
const root = createRoot(host);
|
|
22
|
+
act(() => {
|
|
23
|
+
root.render(node);
|
|
24
|
+
});
|
|
25
|
+
return { host, root };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function neutralGrading() {
|
|
29
|
+
const grading = normalizeHfColorGrading("neutral");
|
|
30
|
+
if (!grading) throw new Error("expected a neutral grading");
|
|
31
|
+
return grading;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function findRowByText(
|
|
35
|
+
host: HTMLElement,
|
|
36
|
+
selector: string,
|
|
37
|
+
text: string,
|
|
38
|
+
match: "includes" | "startsWith" = "includes",
|
|
39
|
+
) {
|
|
40
|
+
const row = Array.from(host.querySelectorAll(selector)).find((el) =>
|
|
41
|
+
el.textContent?.[match](text),
|
|
42
|
+
);
|
|
43
|
+
if (!row) throw new Error(`expected a ${text} row`);
|
|
44
|
+
return row;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function dragSliderTrack(row: Element, clientX: number, trackWidth: number) {
|
|
48
|
+
const track = row.querySelector<HTMLElement>('[data-flat-slider-track="true"]');
|
|
49
|
+
if (!track) throw new Error("expected a slider track");
|
|
50
|
+
Object.defineProperty(track, "getBoundingClientRect", {
|
|
51
|
+
value: () => ({ left: 0, width: trackWidth, top: 0, height: 2, right: trackWidth, bottom: 2 }),
|
|
52
|
+
});
|
|
53
|
+
act(() => {
|
|
54
|
+
track.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX }));
|
|
55
|
+
track.dispatchEvent(new MouseEvent("pointerup", { bubbles: true, clientX }));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function clickSliderReset(row: Element) {
|
|
60
|
+
const resetButton = row.querySelector<HTMLButtonElement>('[data-flat-slider-reset="true"]');
|
|
61
|
+
expect(resetButton).not.toBeNull();
|
|
62
|
+
act(() => resetButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("FlatColorGradingAccessory", () => {
|
|
66
|
+
it("shows a 5px status dot colored by runtime status, with the message as its title", () => {
|
|
67
|
+
const { host, root } = renderInto(
|
|
68
|
+
<FlatColorGradingAccessory
|
|
69
|
+
state={{
|
|
70
|
+
grading: neutralGrading(),
|
|
71
|
+
compareEnabled: false,
|
|
72
|
+
runtimeStatus: { state: "active", message: "Shader active" },
|
|
73
|
+
commitCompare: vi.fn(),
|
|
74
|
+
resetGrading: vi.fn(),
|
|
75
|
+
}}
|
|
76
|
+
/>,
|
|
77
|
+
);
|
|
78
|
+
const dot = host.querySelector('[data-flat-grade-status-dot="true"]');
|
|
79
|
+
expect(dot).not.toBeNull();
|
|
80
|
+
expect(dot?.getAttribute("title")).toBe("Shader active");
|
|
81
|
+
expect(dot?.className).toContain("bg-emerald-400");
|
|
82
|
+
act(() => root.unmount());
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("disables the compare hold button when grading is inactive, and fires resetGrading on click", () => {
|
|
86
|
+
const resetGrading = vi.fn();
|
|
87
|
+
const { host, root } = renderInto(
|
|
88
|
+
<FlatColorGradingAccessory
|
|
89
|
+
state={{
|
|
90
|
+
grading: neutralGrading(),
|
|
91
|
+
compareEnabled: false,
|
|
92
|
+
runtimeStatus: { state: "inactive", message: "No grading applied" },
|
|
93
|
+
commitCompare: vi.fn(),
|
|
94
|
+
resetGrading,
|
|
95
|
+
}}
|
|
96
|
+
/>,
|
|
97
|
+
);
|
|
98
|
+
const compareButton = host.querySelector<HTMLButtonElement>(
|
|
99
|
+
'[aria-label="Hold to show original"]',
|
|
100
|
+
);
|
|
101
|
+
expect(compareButton?.disabled).toBe(true);
|
|
102
|
+
const resetButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-reset="true"]');
|
|
103
|
+
act(() => resetButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
104
|
+
expect(resetGrading).toHaveBeenCalledTimes(1);
|
|
105
|
+
act(() => root.unmount());
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("shows the runtime status message as visible text next to the dot, not only as a title", () => {
|
|
109
|
+
const { host, root } = renderInto(
|
|
110
|
+
<FlatColorGradingAccessory
|
|
111
|
+
state={{
|
|
112
|
+
grading: neutralGrading(),
|
|
113
|
+
compareEnabled: false,
|
|
114
|
+
runtimeStatus: { state: "pending", message: "Waiting for shader" },
|
|
115
|
+
commitCompare: vi.fn(),
|
|
116
|
+
resetGrading: vi.fn(),
|
|
117
|
+
}}
|
|
118
|
+
/>,
|
|
119
|
+
);
|
|
120
|
+
const messageEl = host.querySelector('[data-flat-grade-status-message="true"]');
|
|
121
|
+
expect(messageEl).not.toBeNull();
|
|
122
|
+
expect(messageEl?.textContent).toBe("Waiting for shader");
|
|
123
|
+
expect(host.textContent).toContain("Waiting for shader");
|
|
124
|
+
act(() => root.unmount());
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function activeGrading() {
|
|
128
|
+
const grading = neutralGrading();
|
|
129
|
+
return { ...grading, adjust: { ...grading.adjust, contrast: 0.2 } };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
it("activates hold-to-compare on pointerdown and releases on window pointerup", () => {
|
|
133
|
+
const commitCompare = vi.fn();
|
|
134
|
+
const { host, root } = renderInto(
|
|
135
|
+
<FlatColorGradingAccessory
|
|
136
|
+
state={{
|
|
137
|
+
grading: activeGrading(),
|
|
138
|
+
compareEnabled: false,
|
|
139
|
+
runtimeStatus: { state: "active", message: "Shader active" },
|
|
140
|
+
commitCompare,
|
|
141
|
+
resetGrading: vi.fn(),
|
|
142
|
+
}}
|
|
143
|
+
/>,
|
|
144
|
+
);
|
|
145
|
+
const compareButton = host.querySelector<HTMLButtonElement>(
|
|
146
|
+
'[aria-label="Hold to show original"]',
|
|
147
|
+
);
|
|
148
|
+
if (!compareButton) throw new Error("expected a compare button");
|
|
149
|
+
act(() => compareButton.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true })));
|
|
150
|
+
expect(commitCompare).toHaveBeenNthCalledWith(1, true);
|
|
151
|
+
act(() => window.dispatchEvent(new MouseEvent("pointerup", { bubbles: true })));
|
|
152
|
+
expect(commitCompare).toHaveBeenNthCalledWith(2, false);
|
|
153
|
+
act(() => root.unmount());
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("activates hold-to-compare via keyboard Space and releases on keyup", () => {
|
|
157
|
+
const commitCompare = vi.fn();
|
|
158
|
+
const { host, root } = renderInto(
|
|
159
|
+
<FlatColorGradingAccessory
|
|
160
|
+
state={{
|
|
161
|
+
grading: activeGrading(),
|
|
162
|
+
compareEnabled: false,
|
|
163
|
+
runtimeStatus: { state: "active", message: "Shader active" },
|
|
164
|
+
commitCompare,
|
|
165
|
+
resetGrading: vi.fn(),
|
|
166
|
+
}}
|
|
167
|
+
/>,
|
|
168
|
+
);
|
|
169
|
+
const compareButton = host.querySelector<HTMLButtonElement>(
|
|
170
|
+
'[aria-label="Hold to show original"]',
|
|
171
|
+
);
|
|
172
|
+
if (!compareButton) throw new Error("expected a compare button");
|
|
173
|
+
act(() =>
|
|
174
|
+
compareButton.dispatchEvent(
|
|
175
|
+
new KeyboardEvent("keydown", { key: " ", bubbles: true, cancelable: true }),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
expect(commitCompare).toHaveBeenNthCalledWith(1, true);
|
|
179
|
+
act(() =>
|
|
180
|
+
compareButton.dispatchEvent(
|
|
181
|
+
new KeyboardEvent("keyup", { key: " ", bubbles: true, cancelable: true }),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
expect(commitCompare).toHaveBeenNthCalledWith(2, false);
|
|
185
|
+
act(() => root.unmount());
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("releases an active hold when the window loses focus mid-hold", () => {
|
|
189
|
+
const commitCompare = vi.fn();
|
|
190
|
+
const { host, root } = renderInto(
|
|
191
|
+
<FlatColorGradingAccessory
|
|
192
|
+
state={{
|
|
193
|
+
grading: activeGrading(),
|
|
194
|
+
compareEnabled: false,
|
|
195
|
+
runtimeStatus: { state: "active", message: "Shader active" },
|
|
196
|
+
commitCompare,
|
|
197
|
+
resetGrading: vi.fn(),
|
|
198
|
+
}}
|
|
199
|
+
/>,
|
|
200
|
+
);
|
|
201
|
+
const compareButton = host.querySelector<HTMLButtonElement>(
|
|
202
|
+
'[aria-label="Hold to show original"]',
|
|
203
|
+
);
|
|
204
|
+
if (!compareButton) throw new Error("expected a compare button");
|
|
205
|
+
act(() => compareButton.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true })));
|
|
206
|
+
expect(commitCompare).toHaveBeenNthCalledWith(1, true);
|
|
207
|
+
act(() => window.dispatchEvent(new Event("blur")));
|
|
208
|
+
expect(commitCompare).toHaveBeenNthCalledWith(2, false);
|
|
209
|
+
act(() => root.unmount());
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
function neutralPropsBase() {
|
|
214
|
+
return {
|
|
215
|
+
grading: neutralGrading(),
|
|
216
|
+
assets: [] as string[],
|
|
217
|
+
onCommitColorGrading: vi.fn(),
|
|
218
|
+
applyScope: "source-file" as const,
|
|
219
|
+
applyBusy: false,
|
|
220
|
+
onSetApplyScope: vi.fn(),
|
|
221
|
+
onApplyToScope: vi.fn(),
|
|
222
|
+
onApplyScopeAvailable: true,
|
|
223
|
+
mediaMetadata: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
describe("FlatColorGradingSection — Preset + LUT", () => {
|
|
228
|
+
it("renders the Preset dropdown with id/label pairs and fires onCommitColorGrading on change", () => {
|
|
229
|
+
const onCommitColorGrading = vi.fn();
|
|
230
|
+
const { host, root } = renderInto(
|
|
231
|
+
<FlatColorGradingSection
|
|
232
|
+
{...neutralPropsBase()}
|
|
233
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
234
|
+
/>,
|
|
235
|
+
);
|
|
236
|
+
const presetSelect = host.querySelector<HTMLSelectElement>(
|
|
237
|
+
'[data-flat-grade-preset="true"] select',
|
|
238
|
+
);
|
|
239
|
+
if (!presetSelect) throw new Error("expected a preset select");
|
|
240
|
+
expect(presetSelect.value).toBe("neutral");
|
|
241
|
+
// The visible "Preset" label is a sibling span outside FlatSelectRow
|
|
242
|
+
// (label="" there, to avoid rendering it twice) — the select still
|
|
243
|
+
// needs its own accessible name via the dedicated ariaLabel prop.
|
|
244
|
+
expect(presetSelect.getAttribute("aria-label")).toBe("Preset");
|
|
245
|
+
act(() => {
|
|
246
|
+
presetSelect.value = "fresh-pop";
|
|
247
|
+
presetSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
|
248
|
+
});
|
|
249
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
250
|
+
expect(onCommitColorGrading.mock.calls[0][0].preset).toBe("fresh-pop");
|
|
251
|
+
act(() => root.unmount());
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("shows the Custom LUT row collapsed by default, expanding to reveal the strength slider when a LUT is set", () => {
|
|
255
|
+
const grading = { ...neutralGrading(), lut: { src: "assets/luts/warm.cube", intensity: 0.8 } };
|
|
256
|
+
const { host, root } = renderInto(
|
|
257
|
+
<FlatColorGradingSection {...neutralPropsBase()} grading={grading} />,
|
|
258
|
+
);
|
|
259
|
+
const lutToggle = host.querySelector<HTMLButtonElement>('[data-flat-grade-lut-toggle="true"]');
|
|
260
|
+
expect(lutToggle).not.toBeNull();
|
|
261
|
+
act(() => lutToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
262
|
+
expect(host.textContent).toContain("warm.cube");
|
|
263
|
+
act(() => root.unmount());
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("commits the selected catalog LUT via the select control, resetting intensity to 1 when switching LUTs", () => {
|
|
267
|
+
const onCommitColorGrading = vi.fn();
|
|
268
|
+
const grading = { ...neutralGrading(), lut: { src: "assets/luts/warm.cube", intensity: 0.5 } };
|
|
269
|
+
const { host, root } = renderInto(
|
|
270
|
+
<FlatColorGradingSection
|
|
271
|
+
{...neutralPropsBase()}
|
|
272
|
+
assets={["assets/luts/warm.cube", "assets/luts/cool.cube"]}
|
|
273
|
+
grading={grading}
|
|
274
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
275
|
+
/>,
|
|
276
|
+
);
|
|
277
|
+
const lutToggle = host.querySelector<HTMLButtonElement>('[data-flat-grade-lut-toggle="true"]');
|
|
278
|
+
act(() => lutToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
279
|
+
const lutSelect = host.querySelector<HTMLSelectElement>('[data-flat-grade-lut-select="true"]');
|
|
280
|
+
if (!lutSelect) throw new Error("expected a LUT catalog select");
|
|
281
|
+
expect(lutSelect.getAttribute("aria-label")).toBe("Custom LUT");
|
|
282
|
+
act(() => {
|
|
283
|
+
lutSelect.value = "assets/luts/cool.cube";
|
|
284
|
+
lutSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
|
285
|
+
});
|
|
286
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
287
|
+
expect(onCommitColorGrading.mock.calls[0][0].lut).toEqual({
|
|
288
|
+
src: "assets/luts/cool.cube",
|
|
289
|
+
intensity: 1,
|
|
290
|
+
});
|
|
291
|
+
act(() => root.unmount());
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("imports a LUT via the hidden file input and commits the resolved asset", async () => {
|
|
295
|
+
const onCommitColorGrading = vi.fn();
|
|
296
|
+
const onImportAssets = vi.fn().mockResolvedValue(["assets/luts/x.cube"]);
|
|
297
|
+
const { host, root } = renderInto(
|
|
298
|
+
<FlatColorGradingSection
|
|
299
|
+
{...neutralPropsBase()}
|
|
300
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
301
|
+
onImportAssets={onImportAssets}
|
|
302
|
+
/>,
|
|
303
|
+
);
|
|
304
|
+
const lutToggle = host.querySelector<HTMLButtonElement>('[data-flat-grade-lut-toggle="true"]');
|
|
305
|
+
act(() => lutToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
306
|
+
const fileInput = host.querySelector<HTMLInputElement>('input[type="file"]');
|
|
307
|
+
if (!fileInput) throw new Error("expected a hidden file input");
|
|
308
|
+
const file = new File(["cube data"], "x.cube");
|
|
309
|
+
Object.defineProperty(fileInput, "files", { value: [file], configurable: true });
|
|
310
|
+
await act(async () => {
|
|
311
|
+
fileInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
312
|
+
await Promise.resolve();
|
|
313
|
+
await Promise.resolve();
|
|
314
|
+
});
|
|
315
|
+
expect(onImportAssets).toHaveBeenCalledTimes(1);
|
|
316
|
+
expect(onImportAssets.mock.calls[0][0]).toEqual([file]);
|
|
317
|
+
expect(onImportAssets.mock.calls[0][1]).toBe("assets/luts");
|
|
318
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
319
|
+
expect(onCommitColorGrading.mock.calls[0][0].lut).toEqual({
|
|
320
|
+
src: "assets/luts/x.cube",
|
|
321
|
+
intensity: 1,
|
|
322
|
+
});
|
|
323
|
+
act(() => root.unmount());
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("FlatColorGradingSection — Adjust sliders", () => {
|
|
328
|
+
it("renders all 10 adjust rows with a center tick, formatting exposure distinctly from percentage sliders", () => {
|
|
329
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
330
|
+
const adjustRows = host.querySelectorAll('[data-flat-grade-adjust="true"]');
|
|
331
|
+
expect(adjustRows).toHaveLength(10);
|
|
332
|
+
for (const row of Array.from(adjustRows)) {
|
|
333
|
+
expect(row.querySelector('[data-flat-slider-center-tick="true"]')).not.toBeNull();
|
|
334
|
+
}
|
|
335
|
+
expect(host.textContent).toContain("+0.00");
|
|
336
|
+
act(() => root.unmount());
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("commits an adjust change scaled correctly and shows a reset when non-neutral", () => {
|
|
340
|
+
const onCommitColorGrading = vi.fn();
|
|
341
|
+
const grading = { ...neutralGrading(), adjust: { ...neutralGrading().adjust, contrast: 0.12 } };
|
|
342
|
+
const { host, root } = renderInto(
|
|
343
|
+
<FlatColorGradingSection
|
|
344
|
+
{...neutralPropsBase()}
|
|
345
|
+
grading={grading}
|
|
346
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
347
|
+
/>,
|
|
348
|
+
);
|
|
349
|
+
const contrastRow = findRowByText(host, '[data-flat-grade-adjust="true"]', "Contrast");
|
|
350
|
+
clickSliderReset(contrastRow);
|
|
351
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
352
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).toBe(0);
|
|
353
|
+
act(() => root.unmount());
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("commits a dragged contrast value on slider track pointerdown, scaled from percent back to the internal -1..1 range", () => {
|
|
357
|
+
const onCommitColorGrading = vi.fn();
|
|
358
|
+
const { host, root } = renderInto(
|
|
359
|
+
<FlatColorGradingSection
|
|
360
|
+
{...neutralPropsBase()}
|
|
361
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
362
|
+
/>,
|
|
363
|
+
);
|
|
364
|
+
const contrastRow = findRowByText(host, '[data-flat-grade-adjust="true"]', "Contrast");
|
|
365
|
+
// min=-100, max=100, step=1, ratio=0.75 -> raw=50 -> commit(50) -> adjust.contrast = 50/100 = 0.5
|
|
366
|
+
dragSliderTrack(contrastRow, 75, 100);
|
|
367
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
368
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).toBe(0.5);
|
|
369
|
+
act(() => root.unmount());
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("commits a dragged exposure value scaled into stops, keeping other adjust keys untouched", () => {
|
|
373
|
+
const onCommitColorGrading = vi.fn();
|
|
374
|
+
const grading = {
|
|
375
|
+
...neutralGrading(),
|
|
376
|
+
adjust: { ...neutralGrading().adjust, saturation: 0.2 },
|
|
377
|
+
};
|
|
378
|
+
const { host, root } = renderInto(
|
|
379
|
+
<FlatColorGradingSection
|
|
380
|
+
{...neutralPropsBase()}
|
|
381
|
+
grading={grading}
|
|
382
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
383
|
+
/>,
|
|
384
|
+
);
|
|
385
|
+
const exposureRow = findRowByText(host, '[data-flat-grade-adjust="true"]', "Exposure");
|
|
386
|
+
// min=-200, max=200, step=5, ratio=1.0 -> raw=200 -> commit(200) -> adjust.exposure = 200/100 = 2
|
|
387
|
+
dragSliderTrack(exposureRow, 200, 200);
|
|
388
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
389
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.exposure).toBe(2);
|
|
390
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.saturation).toBe(0.2);
|
|
391
|
+
act(() => root.unmount());
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("revives a grade parked at 0% strength back to 100% when an Adjust slider is committed", () => {
|
|
395
|
+
const onCommitColorGrading = vi.fn();
|
|
396
|
+
const grading = { ...neutralGrading(), intensity: 0 };
|
|
397
|
+
const { host, root } = renderInto(
|
|
398
|
+
<FlatColorGradingSection
|
|
399
|
+
{...neutralPropsBase()}
|
|
400
|
+
grading={grading}
|
|
401
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
402
|
+
/>,
|
|
403
|
+
);
|
|
404
|
+
const contrastRow = findRowByText(host, '[data-flat-grade-adjust="true"]', "Contrast");
|
|
405
|
+
// min=-100, max=100, step=1, ratio=0.75 -> raw=50 -> commit(50) -> adjust.contrast = 0.5
|
|
406
|
+
dragSliderTrack(contrastRow, 75, 100);
|
|
407
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
408
|
+
expect(onCommitColorGrading.mock.calls[0][0].intensity).toBe(1);
|
|
409
|
+
expect(onCommitColorGrading.mock.calls[0][0].adjust.contrast).toBe(0.5);
|
|
410
|
+
act(() => root.unmount());
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("does NOT force intensity to revive when the Strength slider itself is dragged — it writes the value directly", () => {
|
|
414
|
+
const onCommitColorGrading = vi.fn();
|
|
415
|
+
const grading = { ...neutralGrading(), intensity: 0 };
|
|
416
|
+
const { host, root } = renderInto(
|
|
417
|
+
<FlatColorGradingSection
|
|
418
|
+
{...neutralPropsBase()}
|
|
419
|
+
grading={grading}
|
|
420
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
421
|
+
/>,
|
|
422
|
+
);
|
|
423
|
+
const strengthRow = findRowByText(host, "div", "Strength", "startsWith");
|
|
424
|
+
// min=0, max=100, step=1, ratio=0.4 -> raw=40 -> commit(40) -> intensity = 40/100 = 0.4
|
|
425
|
+
dragSliderTrack(strengthRow, 40, 100);
|
|
426
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
427
|
+
expect(onCommitColorGrading.mock.calls[0][0].intensity).toBe(0.4);
|
|
428
|
+
act(() => root.unmount());
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("FlatColorGradingSection — Vignette and Grain", () => {
|
|
433
|
+
it("renders Vignette and Grain amount rows with a settings gear, expanding tuned sliders on click", () => {
|
|
434
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
435
|
+
const vignetteGear = host.querySelector<HTMLButtonElement>(
|
|
436
|
+
'[data-flat-grade-settings="vignette"]',
|
|
437
|
+
);
|
|
438
|
+
expect(vignetteGear).not.toBeNull();
|
|
439
|
+
act(() => vignetteGear?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
440
|
+
expect(host.textContent).toContain("Midpoint");
|
|
441
|
+
expect(host.textContent).toContain("Feather");
|
|
442
|
+
act(() => root.unmount());
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("shows tuned Midpoint at its 50% default with no reset until moved from default", () => {
|
|
446
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
447
|
+
const gear = host.querySelector<HTMLButtonElement>('[data-flat-grade-settings="vignette"]');
|
|
448
|
+
act(() => gear?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
449
|
+
const midpointRow = findRowByText(host, "div", "Midpoint", "startsWith");
|
|
450
|
+
expect(midpointRow.querySelector('[data-flat-slider-reset="true"]')).toBeNull();
|
|
451
|
+
act(() => root.unmount());
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("commits a dragged Roundness value on slider track pointerdown, scaled from percent back into the -1..1 detail range", () => {
|
|
455
|
+
const onCommitColorGrading = vi.fn();
|
|
456
|
+
const { host, root } = renderInto(
|
|
457
|
+
<FlatColorGradingSection
|
|
458
|
+
{...neutralPropsBase()}
|
|
459
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
460
|
+
/>,
|
|
461
|
+
);
|
|
462
|
+
const gear = host.querySelector<HTMLButtonElement>('[data-flat-grade-settings="vignette"]');
|
|
463
|
+
act(() => gear?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
464
|
+
const roundnessRow = findRowByText(host, "div", "Roundness", "startsWith");
|
|
465
|
+
// min=-100, max=100, step=1, ratio=0.75 -> raw=50 -> commit(50) -> details.vignetteRoundness = 50/100 = 0.5
|
|
466
|
+
dragSliderTrack(roundnessRow, 75, 100);
|
|
467
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
468
|
+
expect(onCommitColorGrading.mock.calls[0][0].details.vignetteRoundness).toBe(0.5);
|
|
469
|
+
act(() => root.unmount());
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("resets a non-default Roundness back to its 0 default via the tuned slider's reset button", () => {
|
|
473
|
+
const onCommitColorGrading = vi.fn();
|
|
474
|
+
const grading = {
|
|
475
|
+
...neutralGrading(),
|
|
476
|
+
details: { ...neutralGrading().details, vignetteRoundness: 0.4 },
|
|
477
|
+
};
|
|
478
|
+
const { host, root } = renderInto(
|
|
479
|
+
<FlatColorGradingSection
|
|
480
|
+
{...neutralPropsBase()}
|
|
481
|
+
grading={grading}
|
|
482
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
483
|
+
/>,
|
|
484
|
+
);
|
|
485
|
+
const gear = host.querySelector<HTMLButtonElement>('[data-flat-grade-settings="vignette"]');
|
|
486
|
+
act(() => gear?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
487
|
+
const roundnessRow = findRowByText(host, "div", "Roundness", "startsWith");
|
|
488
|
+
clickSliderReset(roundnessRow);
|
|
489
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
490
|
+
expect(onCommitColorGrading.mock.calls[0][0].details.vignetteRoundness).toBe(0);
|
|
491
|
+
act(() => root.unmount());
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
describe("FlatColorGradingSection — Effects", () => {
|
|
496
|
+
it("renders Blur and Pixelate sliders under an Effects micro-label", () => {
|
|
497
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
498
|
+
expect(host.textContent).toContain("Effects");
|
|
499
|
+
const rows = host.querySelectorAll('[data-flat-grade-effect="true"]');
|
|
500
|
+
expect(rows).toHaveLength(2);
|
|
501
|
+
act(() => root.unmount());
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("commits a dragged Pixelate value on slider track pointerdown, scaled from percent to the 0..1 effect range", () => {
|
|
505
|
+
const onCommitColorGrading = vi.fn();
|
|
506
|
+
const { host, root } = renderInto(
|
|
507
|
+
<FlatColorGradingSection
|
|
508
|
+
{...neutralPropsBase()}
|
|
509
|
+
onCommitColorGrading={onCommitColorGrading}
|
|
510
|
+
/>,
|
|
511
|
+
);
|
|
512
|
+
const pixelateRow = findRowByText(host, '[data-flat-grade-effect="true"]', "Pixelate");
|
|
513
|
+
// min=0, max=100, step=1, ratio=0.75 -> raw=75 -> commit(75) -> effects.pixelate = 75/100 = 0.75
|
|
514
|
+
dragSliderTrack(pixelateRow, 75, 100);
|
|
515
|
+
expect(onCommitColorGrading).toHaveBeenCalledTimes(1);
|
|
516
|
+
expect(onCommitColorGrading.mock.calls[0][0].effects.pixelate).toBe(0.75);
|
|
517
|
+
act(() => root.unmount());
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe("FlatColorGradingSection — HDR banner and Apply scope", () => {
|
|
522
|
+
it("shows the HDR banner only when mediaMetadata reports an HDR source", () => {
|
|
523
|
+
const { host, root } = renderInto(
|
|
524
|
+
<FlatColorGradingSection
|
|
525
|
+
{...neutralPropsBase()}
|
|
526
|
+
mediaMetadata={{
|
|
527
|
+
kind: "video",
|
|
528
|
+
color: { dynamicRange: "hdr", hdrTransfer: "pq", label: "HDR10", isHdr: true },
|
|
529
|
+
}}
|
|
530
|
+
/>,
|
|
531
|
+
);
|
|
532
|
+
expect(host.textContent).toContain("SDR preview");
|
|
533
|
+
act(() => root.unmount());
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("shows a codec/profile/pixel-format/color detail line in the HDR banner when metadata provides it", () => {
|
|
537
|
+
const { host, root } = renderInto(
|
|
538
|
+
<FlatColorGradingSection
|
|
539
|
+
{...neutralPropsBase()}
|
|
540
|
+
mediaMetadata={{
|
|
541
|
+
kind: "video",
|
|
542
|
+
color: {
|
|
543
|
+
dynamicRange: "hdr",
|
|
544
|
+
hdrTransfer: "pq",
|
|
545
|
+
label: "HDR10",
|
|
546
|
+
isHdr: true,
|
|
547
|
+
codecName: "hevc",
|
|
548
|
+
profile: "Main10",
|
|
549
|
+
pixelFormat: "yuv420p10le",
|
|
550
|
+
colorPrimaries: "bt2020",
|
|
551
|
+
colorTransfer: "smpte2084",
|
|
552
|
+
},
|
|
553
|
+
}}
|
|
554
|
+
/>,
|
|
555
|
+
);
|
|
556
|
+
const detail = host.querySelector('[data-flat-grade-hdr-detail="true"]');
|
|
557
|
+
expect(detail).not.toBeNull();
|
|
558
|
+
expect(detail?.textContent).toBe("hevc · Main10 · yuv420p10le · bt2020 · smpte2084");
|
|
559
|
+
act(() => root.unmount());
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("omits the HDR detail line entirely when no detail fields are populated", () => {
|
|
563
|
+
const { host, root } = renderInto(
|
|
564
|
+
<FlatColorGradingSection
|
|
565
|
+
{...neutralPropsBase()}
|
|
566
|
+
mediaMetadata={{
|
|
567
|
+
kind: "video",
|
|
568
|
+
color: { dynamicRange: "hdr", hdrTransfer: "pq", label: "HDR10", isHdr: true },
|
|
569
|
+
}}
|
|
570
|
+
/>,
|
|
571
|
+
);
|
|
572
|
+
expect(host.querySelector('[data-flat-grade-hdr-detail="true"]')).toBeNull();
|
|
573
|
+
act(() => root.unmount());
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("omits the HDR banner for SDR media", () => {
|
|
577
|
+
const { host, root } = renderInto(
|
|
578
|
+
<FlatColorGradingSection
|
|
579
|
+
{...neutralPropsBase()}
|
|
580
|
+
mediaMetadata={{
|
|
581
|
+
kind: "video",
|
|
582
|
+
color: { dynamicRange: "sdr", hdrTransfer: null, label: "SDR", isHdr: false },
|
|
583
|
+
}}
|
|
584
|
+
/>,
|
|
585
|
+
);
|
|
586
|
+
expect(host.textContent).not.toContain("SDR preview");
|
|
587
|
+
act(() => root.unmount());
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it("fires onApplyToScope from the Apply button, respecting applyBusy", () => {
|
|
591
|
+
const onApplyToScope = vi.fn();
|
|
592
|
+
const { host, root } = renderInto(
|
|
593
|
+
<FlatColorGradingSection {...neutralPropsBase()} onApplyToScope={onApplyToScope} applyBusy />,
|
|
594
|
+
);
|
|
595
|
+
const applyButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-apply="true"]');
|
|
596
|
+
expect(applyButton?.disabled).toBe(true);
|
|
597
|
+
act(() => root.unmount());
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("fires onApplyToScope exactly once when the Apply button is clicked while not busy", () => {
|
|
601
|
+
const onApplyToScope = vi.fn();
|
|
602
|
+
const { host, root } = renderInto(
|
|
603
|
+
<FlatColorGradingSection
|
|
604
|
+
{...neutralPropsBase()}
|
|
605
|
+
onApplyToScope={onApplyToScope}
|
|
606
|
+
applyBusy={false}
|
|
607
|
+
/>,
|
|
608
|
+
);
|
|
609
|
+
const applyButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-apply="true"]');
|
|
610
|
+
expect(applyButton?.disabled).toBe(false);
|
|
611
|
+
act(() => applyButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
612
|
+
expect(onApplyToScope).toHaveBeenCalledTimes(1);
|
|
613
|
+
act(() => root.unmount());
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it("gives the Copy-grade-to scope select an accessible name — the visible text sits in a sibling span, not a <label>", () => {
|
|
617
|
+
const { host, root } = renderInto(<FlatColorGradingSection {...neutralPropsBase()} />);
|
|
618
|
+
const scopeSelect = host.querySelector<HTMLSelectElement>('[aria-label="Copy grade to"]');
|
|
619
|
+
expect(scopeSelect).not.toBeNull();
|
|
620
|
+
act(() => root.unmount());
|
|
621
|
+
});
|
|
622
|
+
});
|