Files
jhonny-editor/code/tests/uxp/advanced-workflows.test.ts
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

1911 lines
111 KiB
TypeScript
Executable File

import { createRequire } from "node:module";
import { describe, expect, it, vi } from "vitest";
const require = createRequire(import.meta.url);
const Commands = require("../../uxp-plugin/commands.cjs");
const Events = require("../../uxp-plugin/events.cjs");
const Protocol = require("../../uxp-plugin/protocol.cjs");
type MutableItem = {
id: string;
name: string;
isFolder?: boolean;
isClip?: boolean;
type?: number;
parent?: MutableItem | null;
children?: MutableItem[];
color?: number;
getId: ReturnType<typeof vi.fn>;
getItems?: ReturnType<typeof vi.fn>;
getParentBin: ReturnType<typeof vi.fn>;
getColorLabelIndex: ReturnType<typeof vi.fn>;
isSequence?: ReturnType<typeof vi.fn>;
isMergedClip?: ReturnType<typeof vi.fn>;
isMulticamClip?: ReturnType<typeof vi.fn>;
isOffline?: ReturnType<typeof vi.fn>;
getContentType?: ReturnType<typeof vi.fn>;
getSequence?: ReturnType<typeof vi.fn>;
createSetNameAction: ReturnType<typeof vi.fn>;
createSetColorLabelAction: ReturnType<typeof vi.fn>;
createBinAction?: ReturnType<typeof vi.fn>;
createSmartBinAction?: ReturnType<typeof vi.fn>;
createMoveItemAction?: ReturnType<typeof vi.fn>;
createRemoveItemAction?: ReturnType<typeof vi.fn>;
createSubClipAction?: ReturnType<typeof vi.fn>;
};
function advancedHost() {
let nextItem = 1;
const makeItem = (id: string, name: string, options: Partial<MutableItem> = {}): MutableItem => {
const item = {
id, name, color: 0, type: options.isFolder ? 2 : 1, parent: null,
children: options.isFolder ? [] : undefined,
...options,
} as MutableItem;
item.getId = vi.fn(async () => item.id);
item.getItems = options.isFolder ? vi.fn(async () => item.children || []) : undefined;
item.getParentBin = vi.fn(async () => item.parent);
item.getColorLabelIndex = vi.fn(async () => item.color);
item.createSetNameAction = vi.fn((name: string) => ({ apply: () => { item.name = name; } }));
item.createSetColorLabelAction = vi.fn((color: number) => ({ apply: () => { item.color = color; } }));
if (options.isFolder) {
item.createBinAction = vi.fn((name: string) => ({ apply: () => {
const created = makeItem(`created-bin-${nextItem++}`, name, { isFolder: true, parent: item });
item.children?.push(created);
} }));
item.createSmartBinAction = vi.fn((name: string) => ({ apply: () => {
const created = makeItem(`smart-bin-${nextItem++}`, name, { isFolder: true, parent: item });
item.children?.push(created);
} }));
item.createMoveItemAction = vi.fn((child: MutableItem, destination: MutableItem) => ({ apply: () => {
if (child.parent?.children) child.parent.children = child.parent.children.filter((value) => value !== child);
destination.children?.push(child);
child.parent = destination;
} }));
item.createRemoveItemAction = vi.fn((child: MutableItem) => ({ apply: () => {
item.children = item.children?.filter((value) => value !== child);
} }));
}
if (options.isClip) {
item.createSubClipAction = vi.fn((name: string) => ({ apply: () => {
const created = makeItem(`subclip-${nextItem++}`, name, { isClip: true, parent: item.parent });
item.parent?.children?.push(created);
} }));
}
return item;
};
const root = makeItem("root", "Root", { isFolder: true });
const bin = makeItem("bin-1", "Rushes", { isFolder: true, parent: root });
const clip = makeItem("clip-1", "Interview.mov", { isClip: true, parent: bin });
clip.isSequence = vi.fn(async () => false);
clip.isMergedClip = vi.fn(async () => false);
clip.isMulticamClip = vi.fn(async () => false);
clip.isOffline = vi.fn(async () => false);
clip.getContentType = vi.fn(async () => 2);
root.children?.push(bin);
bin.children?.push(clip);
let markerCounter = 2;
const makeMarker = (guid: string, name: string, startSeconds = 1) => {
const state = { name, type: "Comment", comments: "", color: 0, start: startSeconds, duration: 0 };
return {
guid,
getName: vi.fn(async () => state.name),
getType: vi.fn(async () => state.type),
getComments: vi.fn(async () => state.comments),
getColorIndex: vi.fn(async () => state.color),
getStart: vi.fn(async () => ({ seconds: state.start })),
getDuration: vi.fn(async () => ({ seconds: state.duration })),
createSetNameAction: vi.fn((value: string) => ({ apply: () => { state.name = value; } })),
createSetTypeAction: vi.fn((value: string) => ({ apply: () => { state.type = value; } })),
createSetCommentsAction: vi.fn((value: string) => ({ apply: () => { state.comments = value; } })),
createSetDurationAction: vi.fn((value: { seconds: number }) => ({ apply: () => { state.duration = value.seconds; } })),
createSetColorByIndexAction: vi.fn((value: number) => ({ apply: () => { state.color = value; } })),
state,
};
};
const markerValues = [makeMarker("marker-1", "Beat")];
const markers = {
getMarkers: vi.fn(() => markerValues),
createAddMarkerAction: vi.fn((name: string, type: string, start: { seconds: number }, duration: { seconds: number }, comments: string) => ({ apply: () => {
const marker = makeMarker(`marker-${markerCounter++}`, name, start.seconds);
marker.state.type = type;
marker.state.duration = duration.seconds;
marker.state.comments = comments;
markerValues.push(marker);
} })),
createMoveMarkerAction: vi.fn((marker: ReturnType<typeof makeMarker>, time: { seconds: number }) => ({ apply: () => { marker.state.start = time.seconds; } })),
createRemoveMarkerAction: vi.fn((marker: ReturnType<typeof makeMarker>) => ({ apply: () => markerValues.splice(markerValues.indexOf(marker), 1) })),
};
const parameterState: { value: unknown; varying: boolean; keyframes: number[] } = { value: 50, varying: false, keyframes: [] };
const parameterKeyframe = (seconds: number) => ({
position: { seconds },
getTemporalInterpolationMode: vi.fn(async () => 1),
});
const parameter = {
displayName: "Opacity",
areKeyframesSupported: vi.fn(async () => true),
isTimeVarying: vi.fn(() => parameterState.varying),
getKeyframeListAsTickTimes: vi.fn(() => parameterState.keyframes.map((seconds) => ({ seconds }))),
getStartValue: vi.fn(async () => ({ value: parameterState.value })),
getValueAtTime: vi.fn(async () => parameterState.value),
getKeyframePtr: vi.fn((time: { seconds: number }) => parameterState.keyframes.includes(time.seconds) ? parameterKeyframe(time.seconds) : null),
findNextKeyframe: vi.fn((time: { seconds: number }) => {
const next = parameterState.keyframes.find((seconds) => seconds > time.seconds);
return next == null ? null : parameterKeyframe(next);
}),
findPreviousKeyframe: vi.fn((time: { seconds: number }) => {
const previous = parameterState.keyframes.filter((seconds) => seconds < time.seconds).at(-1);
return previous == null ? null : parameterKeyframe(previous);
}),
findNearestKeyframe: vi.fn((inTime: { seconds: number }, outTime: { seconds: number }) => {
const result = parameterState.keyframes.find((seconds) => seconds >= inTime.seconds && seconds <= outTime.seconds);
return result == null ? null : parameterKeyframe(result);
}),
createKeyframe: vi.fn((value: unknown) => ({ value, position: null as { seconds: number } | null })),
createSetValueAction: vi.fn((keyframe: { value: unknown }) => ({ apply: () => { parameterState.value = keyframe.value; } })),
createSetTimeVaryingAction: vi.fn((value: boolean) => ({ apply: () => { parameterState.varying = value; } })),
createAddKeyframeAction: vi.fn((keyframe: { position: { seconds: number } }) => ({ apply: () => parameterState.keyframes.push(keyframe.position.seconds) })),
createRemoveKeyframeAction: vi.fn((time: { seconds: number }) => ({ apply: () => { parameterState.keyframes = parameterState.keyframes.filter((value) => value !== time.seconds); } })),
createRemoveKeyframeRangeAction: vi.fn((start: { seconds: number }, end: { seconds: number }) => ({ apply: () => { parameterState.keyframes = parameterState.keyframes.filter((value) => value < start.seconds || value > end.seconds); } })),
createSetInterpolationAtKeyframeAction: vi.fn(() => ({ apply: () => undefined })),
};
const component = {
getMatchName: vi.fn(async () => "ADBE Opacity"),
getDisplayName: vi.fn(async () => "Opacity"),
getParamCount: vi.fn(() => 1),
getParam: vi.fn(() => parameter),
};
const chain = { getComponentCount: vi.fn(() => 1), getComponentAtIndex: vi.fn(() => component) };
const trackState = { name: "Interview V", start: 10, end: 20, inPoint: 0, outPoint: 10, disabled: false };
const trackItem = {
getComponentChain: vi.fn(async () => chain),
getProjectItem: vi.fn(async () => clip),
getName: vi.fn(async () => trackState.name),
getStartTime: vi.fn(async () => ({ seconds: trackState.start })),
getEndTime: vi.fn(async () => ({ seconds: trackState.end })),
getInPoint: vi.fn(async () => ({ seconds: trackState.inPoint })),
getOutPoint: vi.fn(async () => ({ seconds: trackState.outPoint })),
getDuration: vi.fn(async () => ({ seconds: trackState.end - trackState.start })),
getSpeed: vi.fn(async () => 1),
isSpeedReversed: vi.fn(async () => false),
isAdjustmentLayer: vi.fn(async () => false),
isDisabled: vi.fn(async () => trackState.disabled),
createMoveAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.start += time.seconds; trackState.end += time.seconds; } })),
createSetStartAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.start = time.seconds; } })),
createSetEndAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.end = time.seconds; } })),
createSetInPointAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.inPoint = time.seconds; } })),
createSetOutPointAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.outPoint = time.seconds; } })),
createSetDisabledAction: vi.fn((value: boolean) => ({ apply: () => { trackState.disabled = value; } })),
createSetNameAction: vi.fn((value: string) => ({ apply: () => { trackState.name = value; } })),
};
const settingsState = {
maximumBitDepth: false, maxRenderQuality: false, compositeInLinearColor: false,
audioSampleRate: 48000, videoFrameRate: 23.976, videoFieldType: 0,
videoPixelAspectRatio: "square", editingMode: "custom", previewFileFormat: "mpeg",
previewCodec: "i-frame", videoWidth: 1280, videoHeight: 720,
audioDisplayFormat: 1, videoDisplayFormat: 20,
};
const displayFormatCandidate: { audio?: number; video?: number } = {};
const settings = {
getMaximumBitDepth: vi.fn(async () => settingsState.maximumBitDepth),
getMaxRenderQuality: vi.fn(async () => settingsState.maxRenderQuality),
getCompositeInLinearColor: vi.fn(async () => settingsState.compositeInLinearColor),
getAudioChannelCount: vi.fn(async () => 2),
getAudioChannelType: vi.fn(async () => 1),
getAudioDisplayFormat: vi.fn(async () => ({ type: settingsState.audioDisplayFormat })),
getAudioSampleRate: vi.fn(async () => ({ value: settingsState.audioSampleRate })),
getVideoDisplayFormat: vi.fn(async () => ({ type: settingsState.videoDisplayFormat })),
getVideoFrameRate: vi.fn(() => ({ value: settingsState.videoFrameRate })),
getVideoFieldType: vi.fn(async () => settingsState.videoFieldType),
getVideoPixelAspectRatio: vi.fn(async () => settingsState.videoPixelAspectRatio),
getEditingMode: vi.fn(async () => settingsState.editingMode),
getPreviewFileFormat: vi.fn(async () => settingsState.previewFileFormat),
getPreviewCodec: vi.fn(async () => settingsState.previewCodec),
getVideoFrameRect: vi.fn(async () => ({ width: settingsState.videoWidth, height: settingsState.videoHeight })),
getPreviewFrameRect: vi.fn(async () => ({ width: 640, height: 360 })),
setMaximumBitDepth: vi.fn(async (value: boolean) => { settingsState.maximumBitDepth = value; return true; }),
setMaxRenderQuality: vi.fn(async (value: boolean) => { settingsState.maxRenderQuality = value; return true; }),
setCompositeInLinearColor: vi.fn(async (value: boolean) => { settingsState.compositeInLinearColor = value; return true; }),
setAudioDisplayFormat: vi.fn(async (value: { type: number }) => { displayFormatCandidate.audio = value.type; return true; }),
setAudioSampleRate: vi.fn(async (value: { value: number }) => { settingsState.audioSampleRate = value.value; return true; }),
setVideoDisplayFormat: vi.fn(async (value: { type: number }) => { displayFormatCandidate.video = value.type; return true; }),
setVideoFrameRate: vi.fn((value: { value: number }) => { settingsState.videoFrameRate = value.value; return true; }),
setVideoFieldType: vi.fn(async (value: number) => { settingsState.videoFieldType = value; return true; }),
setVideoPixelAspectRatio: vi.fn(async (value: string) => { settingsState.videoPixelAspectRatio = value; return true; }),
setEditingMode: vi.fn(async (value: string) => { settingsState.editingMode = value; return true; }),
setPreviewFileFormat: vi.fn(async (value: string) => { settingsState.previewFileFormat = value; return true; }),
setPreviewCodec: vi.fn(async (value: string) => { settingsState.previewCodec = value; return true; }),
setVideoFrameRect: vi.fn(async (value: { width: number; height: number }) => { settingsState.videoWidth = value.width; settingsState.videoHeight = value.height; return true; }),
};
const selection = { getTrackItems: vi.fn(async () => [trackItem]) };
const sequence = {
guid: "sequence-1", name: "Assembly",
getSelection: vi.fn(async () => selection),
getVideoTrackCount: vi.fn(async () => 1),
getVideoTrack: vi.fn(async () => ({ getTrackItems: vi.fn(async () => [trackItem]) })),
getAudioTrackCount: vi.fn(async () => 1),
getAudioTrack: vi.fn(async () => ({ getTrackItems: vi.fn(async () => [trackItem]) })),
getSettings: vi.fn(async () => settings),
createSetSettingsAction: vi.fn(() => ({ apply: () => {
if (displayFormatCandidate.audio !== undefined) settingsState.audioDisplayFormat = displayFormatCandidate.audio;
if (displayFormatCandidate.video !== undefined) settingsState.videoDisplayFormat = displayFormatCandidate.video;
delete displayFormatCandidate.audio;
delete displayFormatCandidate.video;
} })),
createCloneAction: vi.fn(() => ({ apply: () => sequences.push({ guid: "sequence-2", name: "Assembly Copy" }) })),
createSubsequence: vi.fn(async () => ({ guid: "sequence-3", name: "Assembly Subsequence" })),
};
clip.getSequence = vi.fn(async () => sequence);
const sequences: Array<{ guid: string; name: string }> = [sequence];
const addAction = vi.fn((action: { apply?: () => void }) => { action.apply?.(); return true; });
const project = {
guid: "project-1", name: "Documentary",
getActiveSequence: vi.fn(async () => sequence),
getSequences: vi.fn(async () => sequences),
getRootItem: vi.fn(async () => root),
lockedAccess: vi.fn((callback: () => void) => callback()),
executeTransaction: vi.fn((callback: (compound: { addAction: typeof addAction }) => void) => { callback({ addAction }); return true; }),
importFiles: vi.fn(async (paths: string[], _suppress: boolean, target?: MutableItem) => {
const parent = target || root;
for (const path of paths) parent.children?.push(makeItem(`import-${nextItem++}`, path.split("/").at(-1) || path, { isClip: true, parent }));
return true;
}),
importSequences: vi.fn(async () => true),
importAEComps: vi.fn(async () => true),
importAllAEComps: vi.fn(async () => true),
createSequence: vi.fn(async (name: string) => {
const created = { guid: `sequence-empty-${sequences.length}`, name };
sequences.push(created);
return created;
}),
createSequenceFromMedia: vi.fn(async (name: string) => ({ guid: "sequence-created", name })),
setActiveSequence: vi.fn(async () => true),
openSequence: vi.fn(async () => true),
closeSequence: vi.fn(async () => true),
deleteSequence: vi.fn(async (target: { guid: string }) => { const index = sequences.indexOf(target); if (index >= 0) sequences.splice(index, 1); return true; }),
};
const editor = {
createInsertProjectItemAction: vi.fn(() => ({ apply: () => undefined })),
createOverwriteItemAction: vi.fn(() => ({ apply: () => undefined })),
createCloneTrackItemAction: vi.fn(() => ({ apply: () => undefined })),
createRemoveItemsAction: vi.fn(() => ({ apply: () => undefined })),
insertMogrtFromPath: vi.fn(() => [trackItem]),
insertMogrtFromLibrary: vi.fn(() => [trackItem]),
};
const manager = {
isAMEInstalled: true,
exportSequence: vi.fn(async () => true),
encodeProjectItem: vi.fn(async () => true),
encodeFile: vi.fn(async () => true),
};
const ppro = {
Project: { getActiveProject: vi.fn(async () => project) },
ProjectUtils: {
getSelection: vi.fn(async () => ({ getItems: vi.fn(async () => [clip]) })),
getProjectViewIds: vi.fn(async () => ["view-1"]),
getProjectFromViewId: vi.fn(async () => project),
getSelectionFromViewId: vi.fn(async () => ({ getItems: vi.fn(async () => [clip]) })),
},
Markers: { getMarkers: vi.fn(async () => markers) },
Marker: { MARKER_TYPE_COMMENT: "Comment" },
FolderItem: { cast: vi.fn((item: MutableItem) => { if (!item.isFolder) throw new Error("not folder"); return item; }) },
ClipProjectItem: { cast: vi.fn((item: MutableItem) => { if (!item.isClip) throw new Error("not clip"); return item; }) },
TickTime: { createWithSeconds: vi.fn((seconds: number) => ({ seconds })) },
PointF: class PointF {
constructor(readonly x: number, readonly y: number) {}
distanceTo(point: { x: number; y: number }) { return Math.hypot(this.x - point.x, this.y - point.y); }
},
Color: class Color { constructor(readonly red: number, readonly green: number, readonly blue: number, readonly alpha: number) {} },
FrameRate: { createWithValue: vi.fn((value: number) => ({ value })) },
RectF: class RectF { width = 0; height = 0; },
Guid: { fromString: vi.fn((value: string) => value) },
Constants: {
TrackItemType: { CLIP: 1 }, MediaType: { ANY: 0, VIDEO: 1, AUDIO: 2 }, ContentType: { ANY: 0, SEQUENCE: 1, MEDIA: 2 },
InterpolationMode: { LINEAR: 1, HOLD: 2, BEZIER: 3, TIME: 4 },
ExportType: { QUEUE_TO_AME: "ame", QUEUE_TO_APP: "app", IMMEDIATELY: "now" },
},
SequenceEditor: { getEditor: vi.fn(() => editor) },
EncoderManager: {
getManager: vi.fn(() => manager),
getExportFileExtension: vi.fn(async () => "mp4"),
EXPORT_QUEUE_TO_AME: "ame", EXPORT_QUEUE_TO_APP: "app", EXPORT_IMMEDIATELY: "now",
},
SequenceSettings: {
AUDIO_DISPLAY_FORMAT_SAMPLE_RATE: 1,
AUDIO_DISPLAY_FORMAT_MILISECONDS: 2,
VIDEO_DISPLAY_FORMAT_23976: 20,
VIDEO_DISPLAY_FORMAT_25: 21,
VIDEO_DISPLAY_FORMAT_2997: 22,
VIDEO_DISPLAY_FORMAT_2997_NON_DROP: 23,
VIDEO_DISPLAY_FORMAT_16mm: 24,
VIDEO_DISPLAY_FORMAT_35mm: 25,
VIDEO_DISPLAY_FORMAT_FRAMES: 26,
},
Utils: { isAEInstalled: vi.fn(async () => true) },
};
const workspace = {
status: vi.fn(() => ({ configured: true, accessMode: "request", rootName: "Approved", persistent: true, pathDisclosure: "redacted", canonicalPathValidation: "available" })),
assertPathAllowed: vi.fn((path: string) => path.replace(/\\/g, "/")),
};
const events = Events.createEventJournal({ capacity: 16 });
return {
registry: Commands.createCommandRegistry({ ppro, Protocol, workspace, events }),
project, ppro, workspace, markers, markerValues, root, bin, clip,
sequence, sequences, settings, settingsState, parameter, parameterState, trackState, trackItem, editor, manager, events,
};
}
describe("advanced stable Premiere UXP workflows", () => {
it("advertises all ten groups from runtime probes and labels filesystem boundaries", async () => {
const value = advancedHost();
const capabilities = await value.registry.capabilities();
expect(Object.keys(capabilities.commands)).toEqual(expect.arrayContaining([
"projectSelection.views", "projectSelection.inspect", "projectTree.inspect", "markers.inspect", "markers.add", "markers.addBeatGrid", "markers.removeMany",
"bins.inspect", "bins.create", "sequenceSettings.get", "sequenceSettings.update", "sequence.displayFormat.inspect", "sequence.displayFormat.update",
"project.import", "parameters.inspect", "parameters.point.inspect", "parameters.point.set", "parameters.keyframeAdd", "parameters.keyframe.inspect", "parameters.timeVarying.inspect", "parameters.timeVarying.set", "trackItem.inspect",
"trackItem.update", "timeline.insert", "timeline.mogrtPath", "timeline.structure.inspect", "sequences.inspect",
"sequences.createEmpty",
"trackItem.splitEdit",
"sequences.clone", "encoder.preflight", "encoder.sequence", "encoder.file",
]));
expect(capabilities.commands["projectSelection.inspect"]).toMatchObject({ supported: true, readOnly: true });
expect(capabilities.commands["markers.add"]).toMatchObject({ supported: true, destructive: true, undoable: true });
expect(capabilities.commands["markers.addBeatGrid"]).toMatchObject({ supported: true, destructive: true, undoable: true });
expect(capabilities.commands["markers.removeMany"]).toMatchObject({ supported: true, destructive: true, undoable: true });
expect(capabilities.commands["project.import"]).toMatchObject({ supported: true, workspaceRequired: true, undoable: false });
expect(capabilities.commands["encoder.sequence"]).toMatchObject({ supported: true, workspaceRequired: true, undoable: false });
expect(capabilities.commands["sequence.displayFormat.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false });
expect(capabilities.commands["sequence.displayFormat.update"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true });
expect(capabilities.commands["parameters.keyframe.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false });
expect(capabilities.commands["parameters.point.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false, minHostVersion: "26.3.0" });
expect(capabilities.commands["parameters.point.displacement.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false, minHostVersion: "26.3.0" });
expect(capabilities.commands["parameters.point.set"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true, minHostVersion: "26.3.0" });
expect(capabilities.commands["parameters.timeVarying.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false });
expect(capabilities.commands["parameters.timeVarying.set"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true });
expect(capabilities.commands["sequences.createEmpty"]).toMatchObject({ supported: true, destructive: true, undoable: false, idempotent: true, minHostVersion: "26.3.0" });
});
it("uses Project-view selection and completes marker/bin actions with identity and field readback", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("projectSelection.views", {})).resolves.toMatchObject({
count: 1, views: [{ viewId: "view-1", projectId: "project-1", projectName: "Documentary" }],
});
await expect(value.registry.dispatch("projectSelection.inspect", { viewId: "view-1" })).resolves.toMatchObject({
count: 1, items: [{ id: "clip-1", name: "Interview.mov" }], resolver: "project_view_selection",
});
await expect(value.registry.dispatch("markers.add", {
name: "Turn", startSeconds: 3, comments: "Cut here", operationId: "marker-add",
})).resolves.toMatchObject({ added: true, outcome: "verified", marker: { name: "Turn", startSeconds: 3 } });
await expect(value.registry.dispatch("markers.addBeatGrid", {
beatTimesSeconds: [1, 2, 3], offsetSeconds: 0.5, namePrefix: "Beat",
comments: "Detected grid", operationId: "beat-grid",
})).resolves.toMatchObject({
added: 3, outcome: "verified", verificationBoundary: "beat_marker_guid_and_time_readback",
markers: [
{ name: "Beat 1", startSeconds: 1.5 },
{ name: "Beat 2", startSeconds: 2.5 },
{ name: "Beat 3", startSeconds: 3.5 },
],
});
expect(value.project.executeTransaction).toHaveBeenCalledTimes(2);
await expect(value.registry.dispatch("markers.update", {
markerGuid: "marker-1", expectedName: "Beat", name: "Beat updated",
startSeconds: 2, colorIndex: 4, operationId: "marker-update",
})).resolves.toMatchObject({
updated: true, outcome: "verified",
marker: { guid: "marker-1", name: "Beat updated", startSeconds: 2, colorIndex: 4 },
});
await expect(value.registry.dispatch("bins.create", {
parentBinId: "bin-1", name: "Selects", operationId: "bin-create",
})).resolves.toMatchObject({ created: true, outcome: "verified", item: { name: "Selects" } });
});
it("walks the native Project root in deterministic bounded order without reading beyond the requested tree", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("projectTree.inspect", { maxItems: 2, maxDepth: 2 })).resolves.toMatchObject({
root: { id: "root", name: "Root", parentId: null, depth: 0, isBin: true },
count: 2,
items: [
{ id: "bin-1", name: "Rushes", parentId: "root", depth: 1, isBin: true },
{ id: "clip-1", name: "Interview.mov", parentId: "bin-1", depth: 2, isBin: false },
],
truncated: false,
itemLimitReached: false,
depthLimitApplied: false,
verificationBoundary: "bounded_project_tree_item_readback",
});
expect(value.project.executeTransaction).not.toHaveBeenCalled();
const limited = advancedHost();
limited.bin.getItems.mockClear();
await expect(limited.registry.dispatch("projectTree.inspect", { maxItems: 1, maxDepth: 2 })).resolves.toMatchObject({
count: 1, items: [{ id: "bin-1" }], truncated: true, itemLimitReached: true, depthLimitApplied: false,
});
expect(limited.bin.getItems).not.toHaveBeenCalled();
const depthLimited = advancedHost();
depthLimited.root.getItems.mockClear();
await expect(depthLimited.registry.dispatch("projectTree.inspect", { maxDepth: 0 })).resolves.toMatchObject({
count: 0, truncated: true, itemLimitReached: false, depthLimitApplied: true,
});
expect(depthLimited.root.getItems).not.toHaveBeenCalled();
await expect(value.registry.dispatch("projectTree.inspect", { maxItems: 513 })).rejects.toThrow(/maxItems must be an integer from 1 to 512/);
await expect(value.registry.dispatch("projectTree.inspect", { unexpected: true })).rejects.toThrow(/Unknown argument/);
});
it("removes only explicit reviewed marker snapshots in one transaction and replays the operation id", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("markers.add", {
name: "Remove", startSeconds: 4, durationSeconds: 1.5, operationId: "marker-batch-fixture",
})).resolves.toMatchObject({ added: true, marker: { guid: "marker-2" } });
const transactionCount = value.project.executeTransaction.mock.calls.length;
const args = {
confirmDestructive: true,
markerSnapshots: [
{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 },
{ markerGuid: "marker-2", expectedName: "Remove", expectedStartSeconds: 4, expectedDurationSeconds: 1.5 },
],
operationId: "marker-batch-remove",
};
await expect(value.registry.dispatch("markers.removeMany", args)).resolves.toMatchObject({
requested: 2, removed: 2, markerGuids: ["marker-1", "marker-2"], remainingTargetGuids: [],
remainingCount: 0, outcome: "verified", verified: true, verificationBoundary: "marker_guid_absence_readback",
});
await expect(value.registry.dispatch("markers.removeMany", args)).resolves.toMatchObject({
requested: 2, removed: 2, replayed: true,
});
expect(value.markers.createRemoveMarkerAction).toHaveBeenCalledTimes(2);
expect(value.project.executeTransaction).toHaveBeenCalledTimes(transactionCount + 1);
});
it("rejects unconfirmed, duplicate, and stale marker batches before creating actions", async () => {
const value = advancedHost();
const snapshot = { markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 0, expectedDurationSeconds: 0 };
await expect(value.registry.dispatch("markers.removeMany", { markerSnapshots: [snapshot] }))
.rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
await expect(value.registry.dispatch("markers.removeMany", {
confirmDestructive: true, markerSnapshots: [snapshot, snapshot],
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("markers.removeMany", {
confirmDestructive: true,
markerSnapshots: [{ ...snapshot, expectedName: "Changed elsewhere" }],
})).rejects.toMatchObject({ code: "UXP_STALE_MARKER" });
expect(value.markers.createRemoveMarkerAction).not.toHaveBeenCalled();
expect(value.project.lockedAccess).not.toHaveBeenCalled();
expect(value.project.executeTransaction).not.toHaveBeenCalled();
});
it("validates marker batch confirmation and bounds before resolving Premiere state", async () => {
const value = advancedHost();
const snapshot = { markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 };
await expect(value.registry.dispatch("markers.removeMany", { markerSnapshots: [snapshot] }))
.rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
await expect(value.registry.dispatch("markers.removeMany", {
confirmDestructive: true, markerSnapshots: Array.from({ length: 129 }, () => snapshot),
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.ppro.Project.getActiveProject).not.toHaveBeenCalled();
expect(value.ppro.Markers.getMarkers).not.toHaveBeenCalled();
});
it("keeps batch-only marker arguments scoped to removeMany", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("markers.add", { name: "Unexpected", confirmDestructive: true }))
.rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("markers.update", {
markerGuid: "marker-1", name: "Unexpected", markerSnapshots: [],
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("markers.remove", { markerGuid: "marker-1", confirmDestructive: true }))
.rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.ppro.Project.getActiveProject).not.toHaveBeenCalled();
expect(value.ppro.Markers.getMarkers).not.toHaveBeenCalled();
});
it("serializes marker updates behind a batch removal snapshot and commit", async () => {
const value = advancedHost();
let releaseNameRead: () => void = () => undefined;
const nameRead = new Promise<void>((resolve) => { releaseNameRead = resolve; });
const marker = value.markerValues[0];
marker.getName.mockImplementationOnce(async () => {
await nameRead;
return marker.state.name;
});
const removing = value.registry.dispatch("markers.removeMany", {
confirmDestructive: true,
markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }],
});
await Promise.resolve();
const updating = value.registry.dispatch("markers.update", { markerGuid: "marker-1", name: "Changed concurrently" });
await Promise.resolve();
expect(marker.createSetNameAction).not.toHaveBeenCalled();
releaseNameRead();
await expect(removing).resolves.toMatchObject({ outcome: "verified", removed: 1 });
await expect(updating).rejects.toMatchObject({ code: "UXP_TARGET_NOT_FOUND" });
expect(marker.createSetNameAction).not.toHaveBeenCalled();
});
it("reports marker batch deletion as committed-unverified when GUID absence cannot be read back", async () => {
const value = advancedHost();
value.markers.createRemoveMarkerAction.mockImplementation(() => ({ apply: () => undefined }));
await expect(value.registry.dispatch("markers.removeMany", {
confirmDestructive: true,
markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }],
})).resolves.toMatchObject({
requested: 1, removed: null, remainingTargetGuids: ["marker-1"],
outcome: "committed_unverified", verified: false, verificationBoundary: "marker_guid_absence_readback",
});
});
it("reads removal fields only for requested markers and confirms absence by GUID without unrelated getters", async () => {
const value = advancedHost();
const target = value.markerValues[0];
const unrelated = {
...target,
guid: "marker-unrelated",
getName: vi.fn(async () => { throw new Error("unrelated name getter"); }),
getType: vi.fn(async () => { throw new Error("unrelated type getter"); }),
getComments: vi.fn(async () => { throw new Error("unrelated comments getter"); }),
getColorIndex: vi.fn(async () => { throw new Error("unrelated color getter"); }),
getStart: vi.fn(async () => { throw new Error("unrelated start getter"); }),
getDuration: vi.fn(async () => { throw new Error("unrelated duration getter"); }),
};
value.markerValues.push(unrelated);
await expect(value.registry.dispatch("markers.removeMany", {
confirmDestructive: true,
markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }],
})).resolves.toMatchObject({
requested: 1, removed: 1, remainingCount: 1, remainingTargetGuids: [], outcome: "verified",
});
expect(target.getName).toHaveBeenCalledOnce();
expect(target.getStart).toHaveBeenCalledOnce();
expect(target.getDuration).toHaveBeenCalledOnce();
expect(unrelated.getName).not.toHaveBeenCalled();
expect(unrelated.getType).not.toHaveBeenCalled();
expect(unrelated.getComments).not.toHaveBeenCalled();
expect(unrelated.getColorIndex).not.toHaveBeenCalled();
expect(unrelated.getStart).not.toHaveBeenCalled();
expect(unrelated.getDuration).not.toHaveBeenCalled();
});
it("serializes marker update and batch deletion by owner so changed snapshots fail stale without deletion", async () => {
const value = advancedHost();
const marker = value.markerValues[0];
let releaseUpdate = () => undefined;
let enteredUpdate = () => undefined;
const updateGate = new Promise<void>((resolve) => { releaseUpdate = resolve; });
const updateEntered = new Promise<void>((resolve) => { enteredUpdate = resolve; });
let enteredRemovalContext = () => undefined;
const removalContextEntered = new Promise<void>((resolve) => { enteredRemovalContext = resolve; });
let markerCollectionRequests = 0;
value.ppro.Markers.getMarkers.mockImplementation(async () => {
markerCollectionRequests += 1;
if (markerCollectionRequests === 2) enteredRemovalContext();
return value.markers;
});
let pauseFirstNameRead = true;
marker.getName.mockImplementation(async () => {
if (pauseFirstNameRead) {
pauseFirstNameRead = false;
enteredUpdate();
await updateGate;
}
return marker.state.name;
});
const update = value.registry.dispatch("markers.update", {
markerGuid: "marker-1", expectedName: "Beat", name: "Renamed", operationId: "marker-update-concurrent",
});
await updateEntered;
const removal = value.registry.dispatch("markers.removeMany", {
confirmDestructive: true,
markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }],
operationId: "marker-remove-concurrent",
});
await removalContextEntered;
await Promise.resolve();
await Promise.resolve();
expect(marker.getName).toHaveBeenCalledTimes(1);
releaseUpdate();
await expect(update).resolves.toMatchObject({ updated: true, outcome: "verified", marker: { name: "Renamed" } });
await expect(removal).rejects.toMatchObject({ code: "UXP_STALE_MARKER" });
expect(value.markers.createRemoveMarkerAction).not.toHaveBeenCalled();
expect(value.markerValues).toHaveLength(1);
expect(value.markerValues[0].state.name).toBe("Renamed");
});
it("creates a single-source silence-cut stringout without mutating an existing sequence", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("silence.deriveSequence", {
sourceProjectItemId: "clip-1", name: "Interview Tight", confirmNonUndoable: true,
operationId: "silence-stringout", keepRanges: [
{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 },
{ startSeconds: 4, endSeconds: 10, startFrame: 120, endFrame: 300 },
],
})).resolves.toMatchObject({
created: true, partial: false, outcome: "committed_unverified", originalChanged: false,
createdSubclips: [{ name: "Interview Tight Keep 1" }, { name: "Interview Tight Keep 2" }],
insertedProjectItemIds: expect.arrayContaining([expect.stringMatching(/^subclip-/)]),
sequence: { name: "Interview Tight" },
});
expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(2);
expect(value.project.createSequenceFromMedia).toHaveBeenCalledTimes(1);
expect(value.editor.createInsertProjectItemAction).toHaveBeenCalledTimes(1);
expect(value.sequence.createCloneAction).not.toHaveBeenCalled();
});
it("caches a partial subclip transaction receipt so retry cannot duplicate artifacts", async () => {
const value = advancedHost();
value.clip.createSubClipAction
.mockImplementationOnce((name: string) => ({ apply: () => {
const created = { ...value.clip, id: "partial-subclip", name, getId: vi.fn(async () => "partial-subclip") };
value.bin.children?.push(created);
} }))
.mockImplementationOnce(() => ({ apply: () => { throw new Error("host rejected second action"); } }));
const args = {
sourceProjectItemId: "clip-1", name: "Partial Tight", confirmNonUndoable: true, operationId: "partial-stringout",
keepRanges: [
{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 },
{ startSeconds: 4, endSeconds: 10, startFrame: 120, endFrame: 300 },
],
};
await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({
partial: true, outcome: "committed_unverified", sequence: null,
verificationBoundary: "derived_subclip_partial_transaction_receipt",
});
await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, replayed: true });
expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(2);
expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled();
});
it("resolves the destination before creating subclips", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("silence.deriveSequence", {
sourceProjectItemId: "clip-1", targetBinId: "missing-bin", name: "Invalid Target",
confirmNonUndoable: true, operationId: "invalid-target", keepRanges: [
{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 },
],
})).rejects.toThrow("projectItemId was not found");
expect(value.clip.createSubClipAction).not.toHaveBeenCalled();
expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled();
});
it("does not cache failures that occur before a subclip transaction is attempted", async () => {
const value = advancedHost();
const implementation = value.clip.createSubClipAction.getMockImplementation();
value.clip.createSubClipAction.mockImplementationOnce(() => { throw new Error("cannot construct action"); });
const args = {
sourceProjectItemId: "clip-1", name: "Retry Tight", confirmNonUndoable: true,
operationId: "pre-transaction-retry", keepRanges: [
{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 },
],
};
await expect(value.registry.dispatch("silence.deriveSequence", args)).rejects.toThrow("cannot construct action");
value.clip.createSubClipAction.mockImplementation(implementation!);
await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({
created: true, partial: false,
});
expect(value.project.createSequenceFromMedia).toHaveBeenCalledTimes(1);
});
it("rejects a silence stringout before overflowing the destination bin", async () => {
const value = advancedHost();
value.bin.children?.push(...Array.from({ length: 1023 }, () => value.clip));
await expect(value.registry.dispatch("silence.deriveSequence", {
sourceProjectItemId: "clip-1", name: "Over Capacity", confirmNonUndoable: true, operationId: "over-capacity",
keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }],
})).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(value.clip.createSubClipAction).not.toHaveBeenCalled();
});
it("caches a partial subclip receipt instead of overflowing the sequence collection", async () => {
const value = advancedHost();
value.sequences.push(...Array.from({ length: 1023 }, (_, index) => ({ guid: `sequence-${index + 2}`, name: `Existing ${index + 2}` })));
await expect(value.registry.dispatch("silence.deriveSequence", {
sourceProjectItemId: "clip-1", name: "Sequence Capacity", confirmNonUndoable: true, operationId: "sequence-capacity",
keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }],
})).resolves.toMatchObject({
partial: true, verificationBoundary: "derived_sequence_capacity_preflight", sequence: null,
});
expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled();
});
it("caches a partial receipt when a post-mutation subclip snapshot rejects", async () => {
const value = advancedHost();
let idReads = 0;
value.clip.createSubClipAction.mockImplementationOnce((name: string) => ({ apply: () => {
const created = {
...value.clip, id: "subclip-snapshot-failure", name,
getId: vi.fn(async () => { idReads += 1; if (idReads === 1) return "subclip-snapshot-failure"; throw new Error("snapshot unavailable"); }),
};
value.bin.children?.push(created);
} }));
value.ppro.SequenceEditor.getEditor.mockImplementationOnce(() => { throw new Error("editor unavailable"); });
const args = {
sourceProjectItemId: "clip-1", name: "Snapshot Tight", confirmNonUndoable: true, operationId: "snapshot-failure",
keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }],
};
await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({
partial: true, verificationBoundary: "derived_sequence_partial_insert_receipt",
createdSubclips: [{ id: "", name: "Snapshot Tight Keep 1" }],
});
await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, replayed: true });
expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(1);
});
it("reconciles a sequence created before its host call rejects", async () => {
const value = advancedHost();
value.project.createSequenceFromMedia.mockImplementationOnce(async (name: string) => {
value.sequences.push({ guid: "sequence-after-error", name });
throw new Error("host rejected after creation");
});
await expect(value.registry.dispatch("silence.deriveSequence", {
sourceProjectItemId: "clip-1", name: "Recovered Tight", confirmNonUndoable: true, operationId: "sequence-reconcile",
keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }],
})).resolves.toMatchObject({
partial: true, verificationBoundary: "derived_sequence_host_reconciliation",
sequence: { id: "sequence-after-error", name: "Recovered Tight" },
});
});
it("fails the silence-stringout capability probe when required host primitives are absent", async () => {
const value = advancedHost();
value.ppro.TickTime.createWithSeconds = undefined;
const capabilities = await value.registry.capabilities();
expect(capabilities.commands["silence.deriveSequence"]).toMatchObject({ supported: false });
});
it("rejects invalid beat grids before mutation and reports contradictory readback as unverified", async () => {
const value = advancedHost();
for (const args of [
{ beatTimesSeconds: [2, 1] },
{ beatTimesSeconds: [1, 1] },
{ beatTimesSeconds: [1], offsetSeconds: -2 },
]) {
await expect(value.registry.dispatch("markers.addBeatGrid", args)).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
}
expect(value.project.lockedAccess).not.toHaveBeenCalled();
value.markerValues.push(...Array.from({ length: 2046 }, () => value.markerValues[0]));
await expect(value.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1, 2] }))
.rejects.toMatchObject({ code: "UXP_COLLECTION_LIMIT" });
expect(value.project.lockedAccess).not.toHaveBeenCalled();
const emptyGuid = advancedHost();
emptyGuid.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => {
emptyGuid.markerValues.push({
...emptyGuid.markerValues[0], guid: "", getName: vi.fn(async () => name), getType: vi.fn(async () => type),
getStart: vi.fn(async () => ({ seconds: start.seconds })),
});
} }));
await expect(emptyGuid.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] }))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false });
const wrongTime = advancedHost();
wrongTime.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => {
wrongTime.markerValues.push({
...wrongTime.markerValues[0], guid: "wrong-time-guid", getName: vi.fn(async () => name), getType: vi.fn(async () => type),
getStart: vi.fn(async () => ({ seconds: start.seconds + 1 })),
});
} }));
await expect(wrongTime.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] }))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false });
const wrongName = advancedHost();
wrongName.markers.createAddMarkerAction.mockImplementation((_name: string, type: string, start: { seconds: number }) => ({ apply: () => {
wrongName.markerValues.push({
...wrongName.markerValues[0], guid: "wrong-name-guid", getName: vi.fn(async () => "Unexpected"), getType: vi.fn(async () => type),
getStart: vi.fn(async () => ({ seconds: start.seconds })),
});
} }));
await expect(wrongName.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] }))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false });
const missingZeroTime = advancedHost();
missingZeroTime.markers.createAddMarkerAction.mockImplementation((name: string, type: string) => ({ apply: () => {
missingZeroTime.markerValues.push({
...missingZeroTime.markerValues[0], guid: "missing-zero-time-guid", getName: vi.fn(async () => name),
getType: vi.fn(async () => type), getStart: vi.fn(async () => ({})),
});
} }));
await expect(missingZeroTime.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [0] }))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false });
expect(missingZeroTime.markerValues[0].getName).not.toHaveBeenCalled();
expect(missingZeroTime.markerValues[0].getStart).not.toHaveBeenCalled();
const getterFailure = advancedHost();
getterFailure.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => {
getterFailure.markerValues.push({
...getterFailure.markerValues[0], guid: "getter-failure-guid", getName: vi.fn(async () => { throw new Error("readback failed"); }),
getType: vi.fn(async () => type), getStart: vi.fn(async () => ({ seconds: start.seconds })),
});
} }));
const getterFailureArgs = { beatTimesSeconds: [1], operationId: "getter-failure-grid" };
await expect(getterFailure.registry.dispatch("markers.addBeatGrid", getterFailureArgs))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false, added: null, afterCount: null });
await expect(getterFailure.registry.dispatch("markers.addBeatGrid", getterFailureArgs))
.resolves.toMatchObject({ outcome: "committed_unverified", verified: false });
expect(getterFailure.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("updates sequence settings, imports workspace media, and automates a typed effect parameter", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("sequenceSettings.update", {
updates: { maximumBitDepth: true, videoFrameRate: 24, videoWidth: 1920, videoHeight: 1080 },
operationId: "settings-update",
})).resolves.toMatchObject({
updated: true, outcome: "verified",
after: { maximumBitDepth: true, videoFrameRate: 24, videoFrame: { width: 1920, height: 1080 } },
});
await expect(value.registry.dispatch("project.import", {
mode: "files", paths: ["D:/Approved/broll.mov"], targetBinId: "bin-1",
})).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
await expect(value.registry.dispatch("project.import", {
mode: "files", paths: ["D:/Approved/broll.mov"], targetBinId: "bin-1",
confirmNonUndoable: true, operationId: "import-files",
})).resolves.toMatchObject({
imported: true, outcome: "committed_unverified", verified: false, requested: 1,
observedAddedCount: 1, addedItemIds: [expect.stringMatching(/^import-/)],
});
await expect(value.registry.dispatch("parameters.set", {
mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0,
expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity", value: 80,
operationId: "parameter-set",
})).resolves.toMatchObject({ updated: true, outcome: "verified", after: { value: 80 } });
expect(value.workspace.assertPathAllowed).toHaveBeenCalledWith("D:/Approved/broll.mov", {
label: "paths[0]", kind: "file",
});
});
it("double-reads a static PointF parameter and applies one reviewed point with serialized readback and replay", async () => {
const value = advancedHost();
value.parameterState.value = { x: 960, y: 540 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const inspected = await value.registry.dispatch("parameters.point.inspect", target);
expect(inspected).toMatchObject({
projectId: "project-1", sequenceId: "sequence-1", timeVarying: false,
point: { x: 960, y: 540 }, componentId: "ADBE Opacity", paramName: "Opacity",
});
expect(value.project.executeTransaction).not.toHaveBeenCalled();
const input = { ...target, expectedSnapshot: inspected, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-parameter-replay" };
await expect(value.registry.dispatch("parameters.point.set", input)).resolves.toMatchObject({
operationId: "point-parameter-replay", updated: true, outcome: "verified",
before: { point: { x: 960, y: 540 } }, after: { point: { x: 1100, y: 600 } },
verificationBoundary: "point_parameter_readback",
});
await expect(value.registry.dispatch("parameters.point.set", input)).resolves.toMatchObject({ replayed: true, after: { point: { x: 1100, y: 600 } } });
expect(value.parameter.createKeyframe).toHaveBeenCalledWith(expect.objectContaining({ x: 1100, y: 600 }));
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("double-reads one animated PointF parameter's native endpoint displacement without mutating Premiere", async () => {
const value = advancedHost();
value.parameterState.varying = true;
const nativePoints: Array<{ x: number; y: number; distanceTo: ReturnType<typeof vi.fn> }> = [];
const pointAt = (x: number, y: number) => {
const point = { x, y, distanceTo: vi.fn((other: { x: number; y: number }) => Math.hypot(x - other.x, y - other.y)) };
nativePoints.push(point);
return point;
};
value.parameter.getValueAtTime.mockImplementation(async (time: { seconds: number }) => time.seconds === 2 ? pointAt(0, 0) : pointAt(3, 4));
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
await expect(value.registry.dispatch("parameters.point.displacement.inspect", {
...target, startSeconds: 2, endSeconds: 5,
})).resolves.toMatchObject({
projectId: "project-1", sequenceId: "sequence-1", timeVarying: true,
startSeconds: 2, endSeconds: 5, startPoint: { x: 0, y: 0 }, endPoint: { x: 3, y: 4 }, straightLineDistance: 5,
});
expect(nativePoints[0].distanceTo).toHaveBeenCalledWith(expect.objectContaining({ x: 3, y: 4 }));
expect(value.project.executeTransaction).not.toHaveBeenCalled();
});
it("fails closed when PointF displacement is static, malformed, stale, or has an invalid interval", async () => {
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity", startSeconds: 2, endSeconds: 5 };
const staticPoint = advancedHost();
staticPoint.parameter.getValueAtTime.mockResolvedValue({ x: 0, y: 0, distanceTo: () => 0 });
await expect(staticPoint.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" });
const malformedPoint = advancedHost();
malformedPoint.parameterState.varying = true;
malformedPoint.parameter.getValueAtTime.mockResolvedValue({ x: 0, y: 0 });
await expect(malformedPoint.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" });
const changing = advancedHost();
changing.parameterState.varying = true;
let reads = 0;
changing.parameter.getValueAtTime.mockImplementation(async (time: { seconds: number }) => {
reads += 1;
const endX = reads >= 4 ? 4 : 3;
const x = time.seconds === 2 ? 0 : endX;
return { x, y: time.seconds === 2 ? 0 : 4, distanceTo: (other: { x: number; y: number }) => Math.hypot(x - other.x, (time.seconds === 2 ? 0 : 4) - other.y) };
});
await expect(changing.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" });
await expect(changing.registry.dispatch("parameters.point.displacement.inspect", { ...target, endSeconds: 2 })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
});
it("fails closed on stale, animated, unavailable, or changing PointF parameter state", async () => {
const value = advancedHost();
value.parameterState.value = { x: 960, y: 540 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const expectedSnapshot = await value.registry.dispatch("parameters.point.inspect", target);
value.parameterState.value = { x: 961, y: 540 };
await expect(value.registry.dispatch("parameters.point.set", {
...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-stale",
})).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" });
expect(value.project.executeTransaction).not.toHaveBeenCalled();
await expect(value.registry.dispatch("parameters.point.set", {
...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.project.executeTransaction).not.toHaveBeenCalled();
const animated = advancedHost();
animated.parameterState.value = { x: 960, y: 540 };
animated.parameterState.varying = true;
const animatedSnapshot = await animated.registry.dispatch("parameters.point.inspect", target);
await expect(animated.registry.dispatch("parameters.point.set", {
...target, expectedSnapshot: animatedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-animated",
})).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" });
expect(animated.project.executeTransaction).not.toHaveBeenCalled();
const changing = advancedHost();
changing.parameterState.value = { x: 960, y: 540 };
let reads = 0;
changing.parameter.getStartValue.mockImplementation(async () => {
reads += 1;
return { value: reads === 1 ? { x: 960, y: 540 } : { x: 961, y: 540 } };
});
await expect(changing.registry.dispatch("parameters.point.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" });
const unavailable = advancedHost();
Reflect.deleteProperty(unavailable.ppro, "PointF");
const capabilities = await unavailable.registry.capabilities();
expect(capabilities.commands["parameters.point.inspect"]).toMatchObject({ supported: false });
expect(capabilities.commands["parameters.point.displacement.inspect"]).toMatchObject({ supported: false });
expect(capabilities.commands["parameters.point.set"]).toMatchObject({ supported: false });
});
it("serializes distinct PointF updates so a later stale snapshot cannot overwrite the first", async () => {
const value = advancedHost();
value.parameterState.value = { x: 960, y: 540 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const expectedSnapshot = await value.registry.dispatch("parameters.point.inspect", target);
let releaseRead: (() => void) | undefined;
const firstReadReleased = new Promise<void>((resolve) => { releaseRead = resolve; });
let signalRead: (() => void) | undefined;
const firstReadStarted = new Promise<void>((resolve) => { signalRead = resolve; });
let reads = 0;
value.parameter.getStartValue.mockImplementation(async () => {
reads += 1;
if (reads === 1) {
signalRead?.();
await firstReadReleased;
}
return { value: value.parameterState.value };
});
const first = value.registry.dispatch("parameters.point.set", {
...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-first",
});
await firstReadStarted;
const second = value.registry.dispatch("parameters.point.set", {
...target, expectedSnapshot, point: { x: 1200, y: 700 }, confirmSetPoint: true, operationId: "point-second",
});
releaseRead?.();
await expect(first).resolves.toMatchObject({ outcome: "verified", after: { point: { x: 1100, y: 600 } } });
await expect(second).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" });
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
expect(value.parameterState.value).toMatchObject({ x: 1100, y: 600 });
});
it("double-reads a static Color parameter and applies one reviewed RGBA value with replay", async () => {
const value = advancedHost();
value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const inspected = await value.registry.dispatch("parameters.color.inspect", target);
expect(inspected).toMatchObject({
projectId: "project-1", sequenceId: "sequence-1", timeVarying: false,
color: { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 },
});
const input = {
...target, expectedSnapshot: inspected, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 },
confirmSetColor: true, operationId: "color-parameter-replay",
};
await expect(value.registry.dispatch("parameters.color.set", input)).resolves.toMatchObject({
operationId: "color-parameter-replay", updated: true, outcome: "verified",
before: { color: { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 } },
after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } },
verificationBoundary: "color_parameter_readback",
});
await expect(value.registry.dispatch("parameters.color.set", input)).resolves.toMatchObject({
replayed: true, after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } },
});
expect(value.parameter.createKeyframe).toHaveBeenCalledWith(expect.objectContaining({ red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }));
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("fails closed on stale, animated, unavailable, or changing Color parameter state", async () => {
const value = advancedHost();
value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const expectedSnapshot = await value.registry.dispatch("parameters.color.inspect", target);
value.parameterState.value = { red: 0.2, green: 0.2, blue: 0.3, alpha: 1 };
await expect(value.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 },
confirmSetColor: true, operationId: "color-stale",
})).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" });
expect(value.project.executeTransaction).not.toHaveBeenCalled();
await expect(value.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, operationId: "color-unconfirmed",
})).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
expect(value.project.executeTransaction).not.toHaveBeenCalled();
const animated = advancedHost();
animated.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 };
animated.parameterState.varying = true;
const animatedSnapshot = await animated.registry.dispatch("parameters.color.inspect", target);
await expect(animated.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot: animatedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 },
confirmSetColor: true, operationId: "color-animated",
})).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" });
expect(animated.project.executeTransaction).not.toHaveBeenCalled();
const changing = advancedHost();
changing.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 };
let reads = 0;
changing.parameter.getStartValue.mockImplementation(async () => {
reads += 1;
return { value: reads === 1 ? { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 } : { red: 0.2, green: 0.2, blue: 0.3, alpha: 1 } };
});
await expect(changing.registry.dispatch("parameters.color.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" });
const unavailable = advancedHost();
Reflect.deleteProperty(unavailable.ppro, "Color");
const capabilities = await unavailable.registry.capabilities();
expect(capabilities.commands["parameters.color.inspect"]).toMatchObject({ supported: false });
expect(capabilities.commands["parameters.color.set"]).toMatchObject({ supported: false });
});
it("serializes distinct Color updates so a later stale snapshot cannot overwrite the first", async () => {
const value = advancedHost();
value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 };
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
const expectedSnapshot = await value.registry.dispatch("parameters.color.inspect", target);
let releaseRead: (() => void) | undefined;
const firstReadReleased = new Promise<void>((resolve) => { releaseRead = resolve; });
let signalRead: (() => void) | undefined;
const firstReadStarted = new Promise<void>((resolve) => { signalRead = resolve; });
let reads = 0;
value.parameter.getStartValue.mockImplementation(async () => {
reads += 1;
if (reads === 1) {
signalRead?.();
await firstReadReleased;
}
return { value: value.parameterState.value };
});
const first = value.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 },
confirmSetColor: true, operationId: "color-first",
});
await firstReadStarted;
const second = value.registry.dispatch("parameters.color.set", {
...target, expectedSnapshot, color: { red: 0.4, green: 0.5, blue: 0.6, alpha: 0.7 },
confirmSetColor: true, operationId: "color-second",
});
releaseRead?.();
await expect(first).resolves.toMatchObject({ outcome: "verified", after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } } });
await expect(second).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" });
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
expect(value.parameterState.value).toMatchObject({ red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 });
});
it("guards, serializes, replays, and reads back native sequence display-format updates", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("sequence.displayFormat.inspect", {})).resolves.toMatchObject({
sequence: { id: "sequence-1" },
displayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 20 },
supportedDisplayFormats: {
audio: [{ constant: "AUDIO_DISPLAY_FORMAT_MILISECONDS", code: 2 }, { constant: "AUDIO_DISPLAY_FORMAT_SAMPLE_RATE", code: 1 }],
video: expect.arrayContaining([{ constant: "VIDEO_DISPLAY_FORMAT_FRAMES", code: 26 }]),
},
});
const update = {
expectedSequenceGuid: "sequence-1",
expectedDisplayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 20 },
updates: { audioDisplayFormat: 2, videoDisplayFormat: 26 },
operationId: "display-format-replay",
};
await expect(value.registry.dispatch("sequence.displayFormat.update", update)).resolves.toMatchObject({
updated: true, outcome: "verified", before: { audioDisplayFormat: 1, videoDisplayFormat: 20 },
after: { audioDisplayFormat: 2, videoDisplayFormat: 26 },
operation: { mutatesProject: true, undo: { supported: true }, cancellation: { supported: false } },
});
await expect(value.registry.dispatch("sequence.displayFormat.update", update)).resolves.toMatchObject({
replayed: true, operationId: "display-format-replay", after: { audioDisplayFormat: 2, videoDisplayFormat: 26 },
});
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
await expect(value.registry.dispatch("sequence.displayFormat.update", {
expectedSequenceGuid: "sequence-1",
expectedDisplayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 26 },
updates: { audioDisplayFormat: 1 },
})).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE_DISPLAY_FORMAT" });
await expect(value.registry.dispatch("sequence.displayFormat.update", {
expectedSequenceGuid: "sequence-1",
expectedDisplayFormats: { audioDisplayFormat: 2, videoDisplayFormat: 26 },
updates: { audioDisplayFormat: 999 },
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
const concurrent = advancedHost();
let releaseFirstSnapshot: (() => void) | undefined;
const firstSnapshotReleased = new Promise<void>((resolve) => { releaseFirstSnapshot = resolve; });
let signalFirstSnapshot: (() => void) | undefined;
const firstSnapshotStarted = new Promise<void>((resolve) => { signalFirstSnapshot = resolve; });
let audioReads = 0;
concurrent.settings.getAudioDisplayFormat.mockImplementation(async () => {
audioReads += 1;
if (audioReads === 1) {
signalFirstSnapshot?.();
await firstSnapshotReleased;
}
return { type: concurrent.settingsState.audioDisplayFormat };
});
const expected = { audioDisplayFormat: 1, videoDisplayFormat: 20 };
const first = concurrent.registry.dispatch("sequence.displayFormat.update", {
expectedSequenceGuid: "sequence-1", expectedDisplayFormats: expected,
updates: { audioDisplayFormat: 2 }, operationId: "display-format-first",
});
await firstSnapshotStarted;
const second = concurrent.registry.dispatch("sequence.displayFormat.update", {
expectedSequenceGuid: "sequence-1", expectedDisplayFormats: expected,
updates: { videoDisplayFormat: 26 }, operationId: "display-format-second",
});
releaseFirstSnapshot?.();
await expect(first).resolves.toMatchObject({ outcome: "verified", after: { audioDisplayFormat: 2, videoDisplayFormat: 20 } });
await expect(second).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE_DISPLAY_FORMAT" });
expect(concurrent.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("verifies relative track moves while keeping SequenceEditor transaction limits explicit", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("trackItem.update", {
mediaType: "video", trackIndex: 0, clipIndex: 0, moveBySeconds: 2,
disabled: true, operationId: "track-move",
})).resolves.toMatchObject({
updated: true, outcome: "verified",
before: { startSeconds: 10, endSeconds: 20, disabled: false },
after: { startSeconds: 12, endSeconds: 22, disabled: true },
});
await expect(value.registry.dispatch("timeline.insert", {
projectItemId: "clip-1", timeSeconds: 5, videoTrackIndex: 0, audioTrackIndex: 0,
operationId: "timeline-insert",
})).resolves.toMatchObject({
inserted: true, outcome: "committed_unverified", verified: false,
verificationBoundary: "sequence_editor_transaction",
operation: { mutatesProject: true, undo: { supported: true } },
});
await expect(value.registry.dispatch("timeline.mogrtLibrary", {
libraryName: "Brand", elementName: "Lower Third", timeSeconds: 5,
videoTrackIndex: 0, audioTrackIndex: 0, confirmNonUndoable: true,
})).resolves.toMatchObject({
inserted: 1, source: "library", outcome: "committed_unverified", verified: false,
verificationBoundary: "sequence_editor_host_return",
});
expect(value.project.executeTransaction).toHaveBeenCalledTimes(2);
});
it("reads a bounded documented UXP timeline snapshot without falling back to CEP or mutating Premiere", async () => {
const value = advancedHost();
const allSnapshot = await value.registry.dispatch("timeline.structure.inspect", {
expectedSequenceId: "sequence-1", mediaType: "all", includeEmptyTracks: true, maxItems: 2,
});
expect(allSnapshot).toMatchObject({
sequence: { id: "sequence-1", name: "Assembly" }, mediaType: "all", trackCounts: { video: 1, audio: 1 },
trackCount: 2, itemCount: 2, emptyTracksOmitted: 0, verificationBoundary: "bounded_track_item_readback",
tracks: [
{ mediaType: "video", trackIndex: 0, itemCount: 1, items: [{ name: "Interview V", startSeconds: 10, endSeconds: 20, speed: 1, reversed: false, disabled: false }] },
{ mediaType: "audio", trackIndex: 0, itemCount: 1, items: [{ name: "Interview V", startSeconds: 10, endSeconds: 20, speed: 1, reversed: false, disabled: false }] },
],
});
expect(allSnapshot.trackCounts).toEqual({ video: 1, audio: 1 });
expect(value.trackItem.getProjectItem).not.toHaveBeenCalled();
expect(value.project.lockedAccess).not.toHaveBeenCalled();
expect(value.project.executeTransaction).not.toHaveBeenCalled();
const sourceOptIn = advancedHost();
const sourceSnapshot = await sourceOptIn.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true,
});
expect(sourceSnapshot.tracks).toMatchObject([
{ mediaType: "video", trackIndex: 0, items: [{ sourceProjectItemId: "clip-1" }] },
]);
expect(sourceOptIn.trackItem.getProjectItem).toHaveBeenCalledTimes(1);
expect(sourceOptIn.clip.getId).toHaveBeenCalledTimes(1);
expect(sourceOptIn.clip.isSequence).not.toHaveBeenCalled();
expect(sourceOptIn.clip.isMergedClip).not.toHaveBeenCalled();
expect(sourceOptIn.clip.isMulticamClip).not.toHaveBeenCalled();
expect(sourceOptIn.clip.isOffline).not.toHaveBeenCalled();
expect(sourceOptIn.clip.getSequence).not.toHaveBeenCalled();
expect(sourceOptIn.project.lockedAccess).not.toHaveBeenCalled();
expect(sourceOptIn.project.executeTransaction).not.toHaveBeenCalled();
const classifiedSource = advancedHost();
classifiedSource.clip.isSequence?.mockResolvedValueOnce(true);
classifiedSource.clip.isMergedClip?.mockResolvedValueOnce(true);
classifiedSource.clip.isMulticamClip?.mockResolvedValueOnce(false);
classifiedSource.clip.isOffline?.mockRejectedValueOnce(new Error("offline status unavailable"));
await expect(classifiedSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
})).resolves.toMatchObject({
tracks: [{ mediaType: "video", trackIndex: 0, items: [{
sourceProjectItemId: "clip-1",
sourceProjectItemClassification: {
isSequence: true, isMergedClip: true, isMulticamClip: false, isOffline: null,
},
}] }],
});
expect(classifiedSource.trackItem.getProjectItem).toHaveBeenCalledTimes(1);
expect(classifiedSource.clip.getId).toHaveBeenCalledTimes(1);
expect(classifiedSource.ppro.ClipProjectItem.cast).toHaveBeenCalledWith(classifiedSource.clip);
expect(classifiedSource.clip.isSequence).toHaveBeenCalledTimes(1);
expect(classifiedSource.clip.isMergedClip).toHaveBeenCalledTimes(1);
expect(classifiedSource.clip.isMulticamClip).toHaveBeenCalledTimes(1);
expect(classifiedSource.clip.isOffline).toHaveBeenCalledTimes(1);
expect(classifiedSource.clip.getSequence).not.toHaveBeenCalled();
expect(classifiedSource.project.lockedAccess).not.toHaveBeenCalled();
expect(classifiedSource.project.executeTransaction).not.toHaveBeenCalled();
const contentTypeSource = advancedHost();
await expect(contentTypeSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true,
})).resolves.toMatchObject({
tracks: [{ mediaType: "video", trackIndex: 0, items: [{
sourceProjectItemId: "clip-1", sourceProjectItemContentType: "media",
}] }],
});
expect(contentTypeSource.trackItem.getProjectItem).toHaveBeenCalledTimes(1);
expect(contentTypeSource.ppro.ClipProjectItem.cast).toHaveBeenCalledWith(contentTypeSource.clip);
expect(contentTypeSource.clip.getContentType).toHaveBeenCalledTimes(1);
expect(contentTypeSource.clip.isSequence).not.toHaveBeenCalled();
expect(contentTypeSource.clip.isMergedClip).not.toHaveBeenCalled();
expect(contentTypeSource.clip.isMulticamClip).not.toHaveBeenCalled();
expect(contentTypeSource.clip.isOffline).not.toHaveBeenCalled();
expect(contentTypeSource.clip.getSequence).not.toHaveBeenCalled();
expect(contentTypeSource.project.lockedAccess).not.toHaveBeenCalled();
expect(contentTypeSource.project.executeTransaction).not.toHaveBeenCalled();
const unknownContentType = advancedHost();
unknownContentType.clip.getContentType?.mockResolvedValueOnce(99);
await expect(unknownContentType.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true,
})).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] });
const unavailableContentType = advancedHost();
unavailableContentType.clip.getContentType?.mockRejectedValueOnce(new Error("content type unavailable"));
await expect(unavailableContentType.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true,
})).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] });
const missingContentTypeEnum = advancedHost();
missingContentTypeEnum.ppro.Constants.ContentType = undefined;
await expect(missingContentTypeEnum.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true,
})).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] });
expect(missingContentTypeEnum.clip.getContentType).not.toHaveBeenCalled();
const nestedSource = advancedHost();
nestedSource.clip.isSequence?.mockResolvedValueOnce(true);
await expect(nestedSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
includeSourceNestedSequenceIdentity: true,
})).resolves.toMatchObject({ tracks: [{ items: [{
sourceProjectItemId: "clip-1", sourceNestedSequenceId: "sequence-1",
sourceProjectItemClassification: { isSequence: true },
}] }] });
expect(nestedSource.clip.getSequence).toHaveBeenCalledTimes(1);
expect(nestedSource.project.lockedAccess).not.toHaveBeenCalled();
expect(nestedSource.project.executeTransaction).not.toHaveBeenCalled();
const nonSequenceNestedSource = advancedHost();
await expect(nonSequenceNestedSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
includeSourceNestedSequenceIdentity: true,
})).resolves.toMatchObject({ tracks: [{ items: [{
sourceNestedSequenceId: null, sourceProjectItemClassification: { isSequence: false },
}] }] });
expect(nonSequenceNestedSource.clip.getSequence).not.toHaveBeenCalled();
const unavailableNestedSource = advancedHost();
unavailableNestedSource.clip.isSequence?.mockResolvedValueOnce(true);
unavailableNestedSource.clip.getSequence?.mockRejectedValueOnce(new Error("nested sequence unavailable"));
await expect(unavailableNestedSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
includeSourceNestedSequenceIdentity: true,
})).resolves.toMatchObject({ tracks: [{ items: [{ sourceNestedSequenceId: null }] }] });
expect(unavailableNestedSource.clip.getSequence).toHaveBeenCalledTimes(1);
const malformedNestedSource = advancedHost();
malformedNestedSource.clip.isSequence?.mockResolvedValueOnce(true);
malformedNestedSource.clip.getSequence?.mockResolvedValueOnce({ guid: {} });
await expect(malformedNestedSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
includeSourceNestedSequenceIdentity: true,
})).resolves.toMatchObject({ tracks: [{ items: [{ sourceNestedSequenceId: null }] }] });
const unavailableSource = advancedHost();
unavailableSource.trackItem.getProjectItem.mockRejectedValueOnce(new Error("source item unavailable"));
await expect(unavailableSource.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true,
})).resolves.toMatchObject({ tracks: [{ items: [{
sourceProjectItemId: null, sourceProjectItemClassification: null,
}] }] });
expect(unavailableSource.ppro.ClipProjectItem.cast).not.toHaveBeenCalled();
const videoOnly = advancedHost();
const videoOnlySnapshot = await videoOnly.registry.dispatch("timeline.structure.inspect", { mediaType: "video" });
expect(videoOnlySnapshot.trackCounts).toEqual({ video: 1 });
expect(videoOnly.sequence.getAudioTrackCount).not.toHaveBeenCalled();
const audioOnly = advancedHost();
const audioOnlySnapshot = await audioOnly.registry.dispatch("timeline.structure.inspect", { mediaType: "audio" });
expect(audioOnlySnapshot.trackCounts).toEqual({ audio: 1 });
expect(audioOnly.sequence.getVideoTrackCount).not.toHaveBeenCalled();
await expect(value.registry.dispatch("timeline.structure.inspect", {
expectedSequenceId: "another-sequence",
})).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
trackIndices: [0, 0],
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
includeSourceProjectItems: "yes",
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
includeSourceProjectItemClassification: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
includeSourceProjectItemContentType: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
includeSourceNestedSequenceIdentity: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
includeSourceProjectItems: true, includeSourceNestedSequenceIdentity: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("timeline.structure.inspect", {
mediaType: "all", maxItems: 1,
})).rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" });
expect(value.project.lockedAccess).not.toHaveBeenCalled();
});
it("fails closed before reading a broad unfiltered timeline and advertises the probe boundary", async () => {
const large = advancedHost();
large.sequence.getVideoTrackCount.mockResolvedValue(65);
await expect(large.registry.dispatch("timeline.structure.inspect", { mediaType: "video" }))
.rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" });
expect(large.sequence.getVideoTrack).not.toHaveBeenCalled();
const combined = advancedHost();
combined.sequence.getVideoTrackCount.mockResolvedValue(40);
combined.sequence.getAudioTrackCount.mockResolvedValue(25);
await expect(combined.registry.dispatch("timeline.structure.inspect", { mediaType: "all" }))
.rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" });
expect(combined.sequence.getVideoTrack).not.toHaveBeenCalled();
expect(combined.sequence.getAudioTrack).not.toHaveBeenCalled();
const outOfRange = advancedHost();
outOfRange.sequence.getVideoTrackCount.mockResolvedValue(1);
await expect(outOfRange.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", trackIndices: [1],
})).rejects.toMatchObject({ code: "UXP_TARGET_NOT_FOUND" });
expect(outOfRange.sequence.getVideoTrack).not.toHaveBeenCalled();
const unordered = advancedHost();
unordered.sequence.getVideoTrackCount.mockResolvedValue(2);
const orderedSnapshot = await unordered.registry.dispatch("timeline.structure.inspect", {
mediaType: "video", trackIndices: [1, 0],
});
expect(orderedSnapshot.tracks.map((track: { trackIndex: number }) => track.trackIndex)).toEqual([0, 1]);
expect(unordered.sequence.getVideoTrack.mock.calls.map(([trackIndex]: [number]) => trackIndex)).toEqual([0, 1]);
const unavailable = advancedHost();
unavailable.ppro.Constants.TrackItemType = undefined;
const capabilities = await unavailable.registry.capabilities();
expect(capabilities.commands["timeline.structure.inspect"]).toMatchObject({ supported: false, readOnly: true });
});
it("creates an atomic L-cut with synchronized timeline and source edges", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("trackItem.splitEdit", {
kind: "l_cut", audioTrackIndex: 0, audioClipIndex: 0, videoTrackIndex: 0, videoClipIndex: 0,
extensionSeconds: 2, operationId: "l-cut",
})).resolves.toMatchObject({
splitEdit: "l_cut", extensionSeconds: 2, outcome: "verified",
before: { audio: { endSeconds: 20, outSeconds: 10 }, video: { endSeconds: 20 } },
after: { endSeconds: 22, outSeconds: 12 },
});
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("uses complete keyframe preflight/readback and reports absent removals as no-ops", async () => {
const value = advancedHost();
value.parameterState.keyframes = [1, 2, 3];
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0 };
await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 2 }))
.resolves.toMatchObject({ removed: true, removalRequested: true, outcome: "verified" });
await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 2 }))
.resolves.toMatchObject({ removed: false, unchanged: true, outcome: "verified", operation: { mutatesProject: false } });
value.parameterState.keyframes = [1, 2, 3];
await expect(value.registry.dispatch("parameters.keyframeRemoveRange", { ...target, timeSeconds: 1.5, endSeconds: 2.5 }))
.resolves.toMatchObject({ removed: true, removalRequested: true, outcome: "verified" });
await expect(value.registry.dispatch("parameters.keyframeRemoveRange", { ...target, timeSeconds: 1.5, endSeconds: 2.5 }))
.resolves.toMatchObject({ removed: false, unchanged: true, outcome: "verified" });
value.parameterState.keyframes = Array.from({ length: 257 }, (_, index) => index);
await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 1 }))
.rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
});
it("serializes guarded parameter animation-mode updates and verifies the native readback", async () => {
const value = advancedHost();
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedSequenceId: "sequence-1" };
let enterSnapshot: () => void = function () {};
let releaseSnapshot: () => void = function () {};
const enteredSnapshot = new Promise<void>((resolve) => { enterSnapshot = resolve; });
const resumeSnapshot = new Promise<void>((resolve) => { releaseSnapshot = resolve; });
value.parameter.areKeyframesSupported.mockImplementationOnce(async () => {
enterSnapshot();
await resumeSnapshot;
return true;
});
const enable = value.registry.dispatch("parameters.timeVarying.set", {
...target, expectedTimeVarying: false, expectedKeyframeTimesSeconds: [], timeVarying: true, operationId: "enable-animation",
});
await enteredSnapshot;
const disable = value.registry.dispatch("parameters.timeVarying.set", {
...target, expectedTimeVarying: true, expectedKeyframeTimesSeconds: [], timeVarying: false,
confirmDisableTimeVarying: true, operationId: "disable-animation",
});
releaseSnapshot();
await expect(enable).resolves.toMatchObject({ updated: true, requestedTimeVarying: true, outcome: "verified", after: { timeVarying: true } });
await expect(disable).resolves.toMatchObject({ updated: true, requestedTimeVarying: false, outcome: "verified", after: { timeVarying: false } });
expect(value.parameter.createSetTimeVaryingAction).toHaveBeenNthCalledWith(1, true);
expect(value.parameter.createSetTimeVaryingAction).toHaveBeenNthCalledWith(2, false);
expect(value.project.executeTransaction).toHaveBeenCalledTimes(2);
});
it("fails closed on incomplete or stale animation-mode preconditions before it creates an action", async () => {
const value = advancedHost();
value.parameterState.varying = true;
value.parameterState.keyframes = [1, 2];
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedSequenceId: "sequence-1", expectedTimeVarying: true, timeVarying: false };
await expect(value.registry.dispatch("parameters.timeVarying.set", {
...target, expectedKeyframeTimesSeconds: [1], confirmDisableTimeVarying: true, operationId: "stale-animation",
})).rejects.toMatchObject({ code: "UXP_STALE_PARAMETER" });
await expect(value.registry.dispatch("parameters.timeVarying.set", {
...target, expectedKeyframeTimesSeconds: [1, 2], operationId: "unconfirmed-animation",
})).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
expect(value.parameter.createSetTimeVaryingAction).not.toHaveBeenCalled();
await expect(value.registry.dispatch("parameters.timeVarying.set", {
...target, expectedKeyframeTimesSeconds: [1, 2], confirmDisableTimeVarying: true, operationId: "confirmed-animation",
})).resolves.toMatchObject({ updated: true, after: { timeVarying: false, keyframeTimesSeconds: [1, 2] }, outcome: "verified" });
});
it("replays a completed guarded animation-mode operation without creating another action", async () => {
const value = advancedHost();
const input = {
mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0,
expectedSequenceId: "sequence-1", expectedTimeVarying: false, expectedKeyframeTimesSeconds: [],
timeVarying: true, operationId: "replay-animation-mode",
};
const first = await value.registry.dispatch("parameters.timeVarying.set", input);
expect(first).toMatchObject({ updated: true, after: { timeVarying: true } });
expect(first).not.toHaveProperty("replayed");
await expect(value.registry.dispatch("parameters.timeVarying.set", input)).resolves.toMatchObject({
updated: true, after: { timeVarying: true }, replayed: true,
});
expect(value.parameter.createSetTimeVaryingAction).toHaveBeenCalledTimes(1);
expect(value.project.executeTransaction).toHaveBeenCalledTimes(1);
});
it("locates one exact, directional, or bounded-nearest keyframe with native position and interpolation readback", async () => {
const value = advancedHost();
value.parameterState.keyframes = [1, 3, 7];
const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" };
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "at", timeSeconds: 3 })).resolves.toMatchObject({
sequenceId: "sequence-1", direction: "at", referenceSeconds: 3, found: true,
keyframe: { positionSeconds: 3, temporalInterpolationMode: 1 },
});
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 3 })).resolves.toMatchObject({
direction: "next", found: true, keyframe: { positionSeconds: 7, temporalInterpolationMode: 1 },
});
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "previous", timeSeconds: 3 })).resolves.toMatchObject({
direction: "previous", found: true, keyframe: { positionSeconds: 1, temporalInterpolationMode: 1 },
});
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 7 })).resolves.toMatchObject({
direction: "next", found: false, keyframe: null,
});
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 2, endSeconds: 5 })).resolves.toMatchObject({
direction: "nearest", referenceSeconds: 2, rangeEndSeconds: 5, found: true,
keyframe: { positionSeconds: 3, temporalInterpolationMode: 1 },
});
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 3 }))
.rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 5, endSeconds: 2 }))
.rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 3, endSeconds: 5 }))
.rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.parameter.getKeyframePtr).toHaveBeenCalledWith({ seconds: 3 });
expect(value.parameter.findNextKeyframe).toHaveBeenCalledWith({ seconds: 3 });
expect(value.parameter.findPreviousKeyframe).toHaveBeenCalledWith({ seconds: 3 });
expect(value.parameter.findNearestKeyframe).toHaveBeenCalledWith({ seconds: 2 }, { seconds: 5 });
const staleTarget = advancedHost();
staleTarget.parameterState.keyframes = [3];
await expect(staleTarget.registry.dispatch("parameters.keyframe.inspect", {
...target, expectedParamName: "Stale Opacity", direction: "at", timeSeconds: 3,
})).rejects.toMatchObject({ code: "UXP_STALE_PARAMETER" });
expect(staleTarget.parameter.getKeyframePtr).not.toHaveBeenCalled();
});
it("keeps direct sequence actions unverified with stable result keys and probes host methods", async () => {
const value = advancedHost();
for (const [command, resultField] of [
["sequences.activate", "activated"],
["sequences.open", "opened"],
["sequences.close", "closed"],
]) {
await expect(value.registry.dispatch(command, { sequenceId: "sequence-1" }))
.resolves.toMatchObject({ [resultField]: true, outcome: "committed_unverified", verified: false, verificationBoundary: "host_return" });
}
await expect(value.registry.dispatch("sequences.open", { sequenceId: "sequence-1" })).resolves.not.toHaveProperty("opend");
await expect(value.registry.dispatch("sequenceSettings.update", {
updates: { videoFrameRate: 241 },
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
delete (value.project as unknown as Record<string, unknown>).importAEComps;
await expect(value.registry.capabilities()).resolves.toMatchObject({
commands: { "project.import": { supported: false } },
});
const missingClose = advancedHost();
Reflect.deleteProperty(missingClose.project, "closeSequence");
await expect(missingClose.registry.capabilities()).resolves.toMatchObject({
commands: {
"sequences.activate": { supported: true },
"sequences.open": { supported: true },
"sequences.close": { supported: false },
},
});
await expect(value.registry.dispatch("sequences.createFromMedia", {
name: "New Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true,
})).resolves.toMatchObject({
created: true, outcome: "committed_unverified", verified: false,
verificationBoundary: "create_sequence_host_return",
});
await expect(value.registry.dispatch("sequences.subsequence", {
sequenceId: "sequence-1", confirmNonUndoable: true,
})).resolves.toMatchObject({
created: true, outcome: "committed_unverified", verified: false,
verificationBoundary: "create_subsequence_host_return",
});
});
it("creates an empty sequence only after confirmation, then verifies its identity and replays its receipt", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("sequences.createEmpty", { name: "Empty Assembly" }))
.rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
expect(value.project.createSequence).not.toHaveBeenCalled();
await expect(value.registry.dispatch("sequences.createEmpty", {
name: "Empty Assembly", confirmNonUndoable: true,
})).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" });
expect(value.project.createSequence).not.toHaveBeenCalled();
await expect(value.registry.dispatch("sequences.createEmpty", {
name: "Empty Assembly", confirmNonUndoable: true, operationId: "empty-sequence-1",
})).resolves.toMatchObject({
created: true, partial: false, outcome: "verified", verified: true,
verificationBoundary: "create_empty_sequence_identity_readback",
sequence: { id: "sequence-empty-1", name: "Empty Assembly" },
operation: {
mutatesProject: true,
verification: { status: "verified", boundary: "create_empty_sequence_identity_readback" },
undo: { supported: false },
cancellation: { supported: false },
},
});
await expect(value.registry.dispatch("sequences.createEmpty", {
name: "Empty Assembly", confirmNonUndoable: true, operationId: "empty-sequence-1",
})).resolves.toMatchObject({ replayed: true, sequence: { id: "sequence-empty-1" } });
expect(value.project.createSequence).toHaveBeenCalledTimes(1);
});
it("returns an unverified cached receipt if empty creation changes the project before rejecting", async () => {
const value = advancedHost();
value.project.createSequence.mockImplementationOnce(async (name: string) => {
value.sequences.push({ guid: "sequence-empty-after-error", name });
throw new Error("host rejected after creation");
});
const args = { name: "Recovered Empty", confirmNonUndoable: true, operationId: "empty-sequence-error" };
await expect(value.registry.dispatch("sequences.createEmpty", args)).resolves.toMatchObject({
created: true, partial: true, outcome: "committed_unverified", verified: false,
verificationBoundary: "create_empty_sequence_host_reconciliation",
sequence: { id: "sequence-empty-after-error", name: "Recovered Empty" },
operation: { cancellation: { supported: false } },
});
await expect(value.registry.dispatch("sequences.createEmpty", args)).resolves.toMatchObject({ replayed: true, partial: true });
expect(value.project.createSequence).toHaveBeenCalledTimes(1);
});
it("caches partial receipts when direct sequence creation mutates before rejecting or loses identity", async () => {
const rejected = advancedHost();
rejected.project.createSequenceFromMedia.mockImplementationOnce(async (name: string) => {
rejected.sequences.push({ guid: "sequence-created-before-error", name });
throw new Error("host rejected after creation");
});
await expect(rejected.registry.dispatch("sequences.createFromMedia", {
name: "Recovered Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-host-error",
})).resolves.toMatchObject({
created: true, partial: true, verificationBoundary: "create_sequence_host_reconciliation",
sequence: { id: "sequence-created-before-error", name: "Recovered Assembly" },
});
await expect(rejected.registry.dispatch("sequences.createFromMedia", {
name: "Recovered Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-host-error",
})).resolves.toMatchObject({ partial: true, replayed: true });
const missingIdentity = advancedHost();
missingIdentity.project.createSequenceFromMedia.mockResolvedValueOnce({ name: "Unknown identity" });
await expect(missingIdentity.registry.dispatch("sequences.createFromMedia", {
name: "Unknown identity", projectItemIds: ["clip-1"], confirmNonUndoable: true,
})).resolves.toMatchObject({
created: true, partial: true, verificationBoundary: "create_sequence_identity_readback", sequence: { id: "" },
});
});
it("reconciles a subsequence that exists when the direct host call rejects", async () => {
const value = advancedHost();
value.sequence.createSubsequence.mockImplementationOnce(async () => {
value.sequences.push({ guid: "subsequence-created-before-error", name: "Recovered Subsequence" });
throw new Error("host rejected after creation");
});
await expect(value.registry.dispatch("sequences.subsequence", {
sequenceId: "sequence-1", confirmNonUndoable: true, operationId: "subsequence-host-error",
})).resolves.toMatchObject({
created: true, partial: true, verificationBoundary: "create_subsequence_host_reconciliation",
sequence: { id: "subsequence-created-before-error", name: "Recovered Subsequence" },
});
});
it("surfaces direct sequence rejections when no created sequence can be reconciled", async () => {
const sequenceCreation = advancedHost();
sequenceCreation.project.createSequenceFromMedia.mockRejectedValueOnce(new Error("host rejected"));
await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", {
name: "Rejected Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true,
})).rejects.toMatchObject({ code: "UXP_HOST_REJECTED" });
const subsequenceCreation = advancedHost();
subsequenceCreation.sequence.createSubsequence.mockRejectedValueOnce(new Error("host rejected"));
await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", {
sequenceId: "sequence-1", confirmNonUndoable: true,
})).rejects.toMatchObject({ code: "UXP_HOST_REJECTED" });
});
it("caches partial direct receipts when post-rejection reconciliation cannot be read", async () => {
const sequenceCreation = advancedHost();
let sequenceReads = 0;
sequenceCreation.project.getSequences.mockImplementation(async () => {
sequenceReads++;
if (sequenceReads > 1) throw new Error("sequence readback unavailable");
return sequenceCreation.sequences;
});
sequenceCreation.project.createSequenceFromMedia.mockRejectedValueOnce(new Error("host rejected"));
const sequenceArgs = {
name: "Unverified Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-readback-failed",
};
await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", sequenceArgs)).resolves.toMatchObject({
created: false, partial: true, verificationBoundary: "create_sequence_reconciliation_readback_failed",
});
await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", sequenceArgs)).resolves.toMatchObject({ partial: true, replayed: true });
const subsequenceCreation = advancedHost();
let subsequenceReads = 0;
subsequenceCreation.project.getSequences.mockImplementation(async () => {
subsequenceReads++;
if (subsequenceReads > 2) throw new Error("sequence readback unavailable");
return subsequenceCreation.sequences;
});
subsequenceCreation.sequence.createSubsequence.mockRejectedValueOnce(new Error("host rejected"));
const subsequenceArgs = {
sequenceId: "sequence-1", confirmNonUndoable: true, operationId: "subsequence-readback-failed",
};
await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", subsequenceArgs)).resolves.toMatchObject({
created: false, partial: true, verificationBoundary: "create_subsequence_reconciliation_readback_failed",
});
await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", subsequenceArgs)).resolves.toMatchObject({ partial: true, replayed: true });
});
it("bounds ID-targeted sequence lookup before invoking the host mutation", async () => {
const value = advancedHost();
value.project.getSequences.mockResolvedValue(Array.from({ length: 1025 }, (_, index) => ({
guid: `sequence-${index}`,
name: `Sequence ${index}`,
})));
await expect(value.registry.dispatch("sequences.close", { sequenceId: "sequence-1024" }))
.rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(value.project.closeSequence).not.toHaveBeenCalled();
await expect(value.registry.dispatch("sequences.delete", { confirmNonUndoable: true }))
.rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(value.project.deleteSequence).not.toHaveBeenCalled();
});
it("rejects bounded collection additions before starting a host mutation", async () => {
const markerValue = advancedHost();
markerValue.markers.getMarkers.mockReturnValue(Array.from({ length: 2048 }, () => markerValue.markerValues[0]));
await expect(markerValue.registry.dispatch("markers.add", {
name: "Over capacity", operationId: "marker-capacity",
})).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(markerValue.markers.createAddMarkerAction).not.toHaveBeenCalled();
expect(markerValue.project.lockedAccess).not.toHaveBeenCalled();
const binValue = advancedHost();
binValue.bin.children = Array.from({ length: 1024 }, () => binValue.clip);
await expect(binValue.registry.dispatch("bins.create", {
parentBinId: "bin-1", name: "Over capacity", operationId: "bin-capacity",
})).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(binValue.bin.createBinAction).not.toHaveBeenCalled();
expect(binValue.project.lockedAccess).not.toHaveBeenCalled();
const smartBinValue = advancedHost();
smartBinValue.bin.children = Array.from({ length: 1024 }, () => smartBinValue.clip);
await expect(smartBinValue.registry.dispatch("bins.createSmart", {
parentBinId: "bin-1", name: "Over capacity", searchQuery: "label:red",
operationId: "smart-bin-capacity",
})).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(smartBinValue.bin.createSmartBinAction).not.toHaveBeenCalled();
expect(smartBinValue.project.lockedAccess).not.toHaveBeenCalled();
const sequenceValue = advancedHost();
sequenceValue.project.getSequences.mockResolvedValue(Array.from({ length: 1024 }, (_, index) => ({
guid: `sequence-${index + 1}`,
name: `Sequence ${index + 1}`,
})));
await expect(sequenceValue.registry.dispatch("sequences.clone", {
operationId: "sequence-capacity",
})).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" });
expect(sequenceValue.sequence.createCloneAction).not.toHaveBeenCalled();
expect(sequenceValue.project.lockedAccess).not.toHaveBeenCalled();
});
it("serializes distinct append operations against each target capacity", async () => {
const markerValue = advancedHost();
markerValue.markerValues.push(...Array.from({ length: 2046 }, () => markerValue.markerValues[0]));
const markerResults = await Promise.allSettled([
markerValue.registry.dispatch("markers.add", { name: "First", operationId: "marker-first" }),
markerValue.registry.dispatch("markers.add", { name: "Second", operationId: "marker-second" }),
]);
expect(markerResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]);
expect(markerResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } });
expect(markerValue.markers.createAddMarkerAction).toHaveBeenCalledOnce();
expect(markerValue.markerValues).toHaveLength(2048);
const binValue = advancedHost();
binValue.bin.children = Array.from({ length: 1023 }, () => binValue.clip);
const binResults = await Promise.allSettled([
binValue.registry.dispatch("bins.create", {
parentBinId: "bin-1", name: "Regular", operationId: "bin-first",
}),
binValue.registry.dispatch("bins.createSmart", {
parentBinId: "bin-1", name: "Smart", searchQuery: "label:red", operationId: "bin-second",
}),
]);
expect(binResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]);
expect(binResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } });
expect((binValue.bin.createBinAction?.mock.calls.length || 0) + (binValue.bin.createSmartBinAction?.mock.calls.length || 0)).toBe(1);
expect(binValue.bin.children).toHaveLength(1024);
const sequenceValue = advancedHost();
sequenceValue.sequences.push(...Array.from({ length: 1022 }, (_, index) => ({
guid: `existing-sequence-${index}`,
name: `Existing Sequence ${index}`,
})));
const sequenceResults = await Promise.allSettled([
sequenceValue.registry.dispatch("sequences.clone", { operationId: "sequence-first" }),
sequenceValue.registry.dispatch("sequences.clone", { operationId: "sequence-second" }),
]);
expect(sequenceResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]);
expect(sequenceResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } });
expect(sequenceValue.sequence.createCloneAction).toHaveBeenCalledOnce();
expect(sequenceValue.sequences).toHaveLength(1024);
const emptySequenceValue = advancedHost();
emptySequenceValue.sequences.push(...Array.from({ length: 1022 }, (_, index) => ({
guid: `existing-empty-sequence-${index}`,
name: `Existing Empty Sequence ${index}`,
})));
const emptyResults = await Promise.allSettled([
emptySequenceValue.registry.dispatch("sequences.createEmpty", {
name: "First Empty", confirmNonUndoable: true, operationId: "empty-first",
}),
emptySequenceValue.registry.dispatch("sequences.createEmpty", {
name: "Second Empty", confirmNonUndoable: true, operationId: "empty-second",
}),
]);
expect(emptyResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]);
expect(emptyResults.find((result) => result.status === "rejected"))
.toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } });
expect(emptySequenceValue.project.createSequence).toHaveBeenCalledOnce();
expect(emptySequenceValue.sequences).toHaveLength(1024);
});
it("clones sequences with identity readback and gates AME writes on explicit confirmation", async () => {
const value = advancedHost();
await expect(value.registry.dispatch("sequences.clone", {
sequenceId: "sequence-1", operationId: "sequence-clone",
})).resolves.toMatchObject({
cloned: true, outcome: "verified", source: { id: "sequence-1" },
sequence: { id: "sequence-2", name: "Assembly Copy" },
});
await expect(value.registry.dispatch("encoder.sequence", {
sequenceId: "sequence-1", exportType: "queueToAme",
outputFile: "D:/Approved/output.mp4", presetFile: "D:/Approved/h264.epr",
})).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" });
await expect(value.registry.dispatch("encoder.sequence", {
sequenceId: "sequence-1", exportType: "queueToAme",
outputFile: "D:/Approved/output.mp4", presetFile: "D:/Approved/h264.epr",
confirmExternalWrite: true, operationId: "encode-sequence",
})).resolves.toMatchObject({
queued: true, kind: "sequence", outcome: "committed_unverified", verified: false,
verificationBoundary: "encoder_host_return",
encodeJob: { jobId: "encode-sequence", state: "accepted", terminal: false },
});
expect(value.manager.exportSequence).toHaveBeenCalledWith(
expect.objectContaining({ guid: "sequence-1" }), "ame",
"D:/Approved/output.mp4", "D:/Approved/h264.epr", true,
);
value.events.recordHostEvent({ category: "encoder", name: "encoder.queued" });
value.events.recordHostEvent({ category: "encoder", name: "encoder.complete" });
await expect(value.registry.dispatch("encoder.wait", {
jobId: "encode-sequence", timeoutMs: 100,
})).resolves.toMatchObject({
timedOut: false,
job: { state: "completed", terminal: true, verificationBoundary: "encoder_terminal_event_only" },
});
const noProject = advancedHost();
noProject.ppro.Project.getActiveProject.mockResolvedValue(null);
await expect(noProject.registry.dispatch("encoder.preflight", {})).resolves.toEqual({
ameInstalled: true, extension: null, sequenceId: null,
});
});
});