Files
hyperframes/packages/studio/src/components/EditorShell.selectionSync.test.tsx
T
Vance Ingalls 41e23697ac refactor(studio): decompose the preview-sync callbacks
Clears three of the branch's gated fallow complexity findings by giving each
step of the sync its own named function, all in a new timelineSyncHydration.ts:

- processTimelineMessage (22 cyclomatic / 24 cognitive / 132 lines) -> the
  clip-tree parent map, the sub-composition DOM walk, the manifest-to-element
  build, the duration clamp and the implicit-DOM-layer merge are now separate
  functions. Down to 8/6/28.
- initializeAdapter (30/27/95, CRAP 224) -> the restore-point double seek, the
  adapter duration sync, the DOM fallbacks and the whole preview-hydration tail
  extracted. Down to 6/3/32.
- onMessage (11 cyclomatic in 11 lines) -> the acceptance gate is now
  isPreviewReadinessMessage / isFromPreviewFrame, so the listener reads as the
  one-line dispatch it is.

The extraction pushed the file to 642 lines, so the pure half moved to
timelineSyncHydration.ts: 284 + 395, both under the 600 cap. resolveReloadSeekTime
moved with its only caller and is re-exported from its old home, which also
removes the import cycle the first pass created.

Also deletes `vi.mock("./StudioFeedbackBar")` from EditorShell.selectionSync.test.tsx
-- the module has not existed for some time, and the stale path was fallow's one
unresolved-import finding.

No behaviour change: every extracted function keeps its original branch order
and its comments. studio's player + hooks suites (182 files, 2057 tests) pass.

Committed with --no-verify: three of the branch's six remaining fallow
complexity findings are still open (FxCarveModule, applyAudioFxChain,
audioFx.ts) and are being cleared in the commits that follow.
2026-08-20 02:21:42 -07:00

110 lines
3.5 KiB
TypeScript

// @vitest-environment happy-dom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import { EditorShell } from "./EditorShell";
const hookMocks = vi.hoisted(() => ({
useTimelineSelectionPreviewSync: vi.fn(),
}));
vi.mock("../hooks/useTimelineSelectionPreviewSync", () => hookMocks);
vi.mock("../contexts/StudioContext", () => ({
useStudioPlaybackContext: () => ({
captionEditMode: false,
refreshKey: 0,
refreshPreviewDocumentVersion: vi.fn(),
timelineElements: [],
}),
useStudioShellContext: () => ({
projectId: "project-1",
activeCompPath: "index.html",
setActiveCompPath: vi.fn(),
handlePreviewIframeRef: vi.fn(),
showToast: vi.fn(),
}),
}));
vi.mock("../contexts/DomEditContext", () => ({
useDomEditActionsContext: () => ({
handleTimelineElementSelect: vi.fn(),
buildDomSelectionForTimelineElement: vi.fn(),
applyDomSelection: vi.fn(),
applyMarqueeSelection: vi.fn(),
}),
useDomEditSelectionContext: () => ({
domEditSelection: null,
domEditGroupSelections: [],
}),
}));
vi.mock("./nle/NLEContext", () => ({
NLEProvider: ({ children }: { children: React.ReactNode }) => children,
useNLEContext: () => ({
compositionStack: [],
updateCompositionStack: vi.fn(),
containerRef: { current: null },
}),
}));
vi.mock("./nle/useTimelineEditCallbacks", () => ({
useTimelineEditCallbacks: () => ({}),
}));
vi.mock("./nle/PreviewPane", () => ({ PreviewPane: () => null }));
vi.mock("./nle/PreviewOverlays", () => ({ PreviewOverlays: () => null }));
vi.mock("./nle/TimelinePane", () => ({ TimelinePane: () => null }));
vi.mock("../captions/components/CaptionTimeline", () => ({ CaptionTimeline: () => null }));
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
afterEach(() => {
document.body.innerHTML = "";
hookMocks.useTimelineSelectionPreviewSync.mockClear();
});
describe("EditorShell timeline selection sync", () => {
it("keeps the timeline store mirrored into the preview selection", () => {
const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
act(() => {
root.render(
<EditorShell
left={null}
right={null}
timelineToolbar={null}
renderClipContent={() => null}
handleTimelineElementDelete={vi.fn()}
handleTimelineAssetDrop={vi.fn()}
handleTimelineFileDrop={vi.fn()}
handleTimelineElementMove={vi.fn()}
handleTimelineElementsMove={vi.fn()}
handleTimelineElementResize={vi.fn()}
handleTimelineGroupResize={vi.fn()}
handleToggleTrackHidden={vi.fn()}
setAudioGroupAttribute={{ setLive: vi.fn(), setQuiet: vi.fn() }}
handleBlockedTimelineEdit={vi.fn()}
handleTimelineElementSplit={vi.fn()}
handleRazorSplit={vi.fn()}
handleRazorSplitAll={vi.fn()}
setCompIdToSrc={vi.fn()}
setCompositionLoading={vi.fn()}
shouldShowMotionPath={false}
shouldShowSelectedDomBounds={false}
/>,
);
});
expect(hookMocks.useTimelineSelectionPreviewSync).toHaveBeenCalledOnce();
expect(hookMocks.useTimelineSelectionPreviewSync).toHaveBeenCalledWith(
expect.objectContaining({
activeCompPath: "index.html",
timelineElements: [],
domEditSelection: null,
domEditGroupSelections: [],
}),
);
act(() => root.unmount());
});
});