Files
jhonny-editor/code/tests/tools/competitor-gaps.test.ts
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

236 lines
12 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { BridgeOptions } from "../../src/bridge/file-bridge.js";
vi.mock("../../src/bridge/file-bridge.js", () => ({
sendCommand: vi.fn().mockResolvedValue({ success: true, data: {} }),
}));
import { sendCommand } from "../../src/bridge/file-bridge.js";
import { getCompetitorGapTools } from "../../src/tools/competitor-gaps.js";
const bridgeOptions: BridgeOptions = { tempDir: "/tmp/competitor-gap-tests", timeoutMs: 5000 };
const mockedSendCommand = vi.mocked(sendCommand);
describe("competitor-gap default-profile tools", () => {
beforeEach(() => vi.clearAllMocks());
it("exposes the requested competitor-parity names", () => {
const tools = getCompetitorGapTools(bridgeOptions);
expect(Object.keys(tools)).toEqual(expect.arrayContaining([
"import_edl",
"add_to_timeline_batch",
"crop_clip",
"setup_ducking",
"validate_project_for_export",
"read_sequence_captions",
"set_clip_properties_batch",
"detect_scene_edits",
]));
});
it("does not attempt an unattended EDL import", async () => {
const result = await getCompetitorGapTools(bridgeOptions).import_edl.handler({ file_path: "D:/cuts/spot.edl" });
expect(result.success).toBe(false);
expect(result.error).toContain("No mutation was attempted");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("rejects ambiguous batch insert positions before writing", async () => {
const result = await getCompetitorGapTools(bridgeOptions).add_to_timeline_batch.handler({
clips: [
{ item_id: "a", track_index: 0, start_seconds: 4 },
{ item_id: "b", track_index: 0, start_seconds: 4 },
],
});
expect(result.success).toBe(false);
expect(result.error).toContain("shares video track");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("preflights and reads back batch insertions", async () => {
await getCompetitorGapTools(bridgeOptions).add_to_timeline_batch.handler({
clips: [
{ item_id: "later", track_index: 1, start_seconds: 10, audio_track_index: 0 },
{ item_id: "first", track_index: 0, start_seconds: 2, audio_track_index: 0 },
],
});
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain("Preflight every mutable dependency");
expect(script).toContain("seq.insertClip");
expect(script).toContain("actualStart");
expect(script.indexOf('itemId: "first"')).toBeLessThan(script.indexOf('itemId: "later"'));
});
it("requires a meaningful crop change before adding an effect", async () => {
const result = await getCompetitorGapTools(bridgeOptions).crop_clip.handler({ node_id: "video-1" });
expect(result.success).toBe(false);
expect(result.error).toContain("requires at least one");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("uses the QE crop catalog only when needed and verifies requested values", async () => {
await getCompetitorGapTools(bridgeOptions).crop_clip.handler({ node_id: "video-1", left: 12, zoom: true });
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain('__getQeEffectCatalog("video")');
expect(script).toContain("addVideoEffect");
expect(script).toContain('name: "Left"');
expect(script).toContain("getValue");
});
it("rejects overlapping ducking windows before writing automation", async () => {
const result = await getCompetitorGapTools(bridgeOptions).setup_ducking.handler({
node_id: "music-1",
ducking_windows: [
{ start_seconds: 1, end_seconds: 3, ducked_db: -20 },
{ start_seconds: 2, end_seconds: 4, ducked_db: -24 },
],
});
expect(result.success).toBe(false);
expect(result.error).toContain("must not overlap");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("generates verified relative audio keyframes for non-overlapping windows", async () => {
await getCompetitorGapTools(bridgeOptions).setup_ducking.handler({
node_id: "music-1",
base_db: -4,
fade_seconds: 0.25,
ducking_windows: [{ start_seconds: 2, end_seconds: 4, ducked_db: -28 }],
});
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain("setup_ducking only supports audio");
expect(script).toContain("setTimeVarying");
expect(script).toContain("getValueAtTime");
expect(script).toContain("duckingWindowCount");
});
it("generates a non-mutating export readiness audit", async () => {
await getCompetitorGapTools(bridgeOptions).validate_project_for_export.handler({
sequence_id: "edit-1",
output_path: "D:/exports/final.mp4",
preset_path: "D:/presets/h264.epr",
});
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain("OFFLINE_MEDIA");
expect(script).toContain("TIMELINE_GAPS");
expect(script).toContain("readyForExport");
expect(script).not.toContain("encodeSequence");
});
it("makes caption-read limitations explicit instead of treating no tracks as no captions", async () => {
await getCompetitorGapTools(bridgeOptions).read_sequence_captions.handler({});
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain("captionReadSupported");
expect(script).toContain("do not prove this sequence has no captions");
});
it("fails property batch speed requests before touching any clip", async () => {
const result = await getCompetitorGapTools(bridgeOptions).set_clip_properties_batch.handler({
items: [{ node_id: "clip-1", speed: 150 }],
});
expect(result.success).toBe(false);
expect(result.error).toContain("speed");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("preflights, verifies, and restores batch clip properties on mismatch", async () => {
await getCompetitorGapTools(bridgeOptions).set_clip_properties_batch.handler({
items: [{ node_id: "clip-1", opacity: 80, scale: 110, position_x: 400, rotation: 12 }],
});
const script = mockedSendCommand.mock.calls[0][0];
expect(script).toContain("No batch mutation was attempted");
expect(script).toContain("restoreAll");
expect(script).toContain("No partial batch is reported as verified");
});
it("withholds unsafe CEP scene detection without an authenticated UXP bridge", async () => {
const result = await getCompetitorGapTools(bridgeOptions).detect_scene_edits.handler({
mode: "create_markers",
confirm_non_undoable: true,
});
expect(result.success).toBe(false);
expect(result.error).toContain("authenticated Premiere UXP bridge");
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("routes confirmed scene detection through UXP with a stable mode mapping", async () => {
const request = vi.fn().mockResolvedValue({ detected: true });
const bridge = { request } as any;
const result = await getCompetitorGapTools(bridgeOptions, bridge).detect_scene_edits.handler({
mode: "create_subclips",
confirm_non_undoable: true,
operation_id: "scene-42",
});
expect(result).toMatchObject({ success: true, data: { backend: "uxp" } });
expect(request).toHaveBeenCalledWith("sceneEdit.detect", { mode: "createSubclips", operationId: "scene-42" });
});
it("rejects malformed or unsafe inputs before generating a CEP command", async () => {
const tools = getCompetitorGapTools(bridgeOptions);
await expect(tools.add_to_timeline_batch.handler({ clips: [] })).resolves.toMatchObject({
success: false,
error: expect.stringContaining("clips must contain"),
});
await expect(tools.add_to_timeline_batch.handler({
clips: Array.from({ length: 33 }, (_, index) => ({ item_id: `clip-${index}` })),
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("between 1 and 32") });
await expect(tools.add_to_timeline_batch.handler({ clips: [{ item_id: "" }] })).resolves
.toMatchObject({ success: false, error: expect.stringContaining("item_id") });
await expect(tools.add_to_timeline_batch.handler({
clips: [{ item_id: "clip", track_index: -1 }],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("non-negative integer") });
await expect(tools.crop_clip.handler({ node_id: "clip", right: 101 })).resolves.toMatchObject({ success: false, error: expect.stringContaining("percentages") });
await expect(tools.setup_ducking.handler({
node_id: "music", fade_seconds: 0, ducking_windows: [],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("positive fade_seconds") });
await expect(tools.setup_ducking.handler({
node_id: "music", ducking_windows: Array.from({ length: 33 }, () => ({ start_seconds: 0, end_seconds: 1, ducked_db: -12 })),
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("0–32 ducking windows") });
await expect(tools.setup_ducking.handler({
node_id: "music", ducking_windows: [{ start_seconds: 3, end_seconds: 3, ducked_db: -12 }],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("greater than start_seconds") });
await expect(tools.set_clip_properties_batch.handler({ items: [] })).resolves.toMatchObject({ success: false, error: expect.stringContaining("between 1 and 16") });
await expect(tools.set_clip_properties_batch.handler({ items: [{ node_id: "" }] })).resolves
.toMatchObject({ success: false, error: expect.stringContaining("node_id") });
await expect(tools.set_clip_properties_batch.handler({
items: [{ node_id: "clip" }, { node_id: "clip", opacity: 80 }],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("must specify at least one") });
await expect(tools.set_clip_properties_batch.handler({
items: [{ node_id: "clip", opacity: -1 }],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("invalid property") });
await expect(tools.set_clip_properties_batch.handler({
items: [{ node_id: "clip", opacity: 80 }, { node_id: "clip", opacity: 60 }],
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("duplicate node_id") });
await expect(tools.detect_scene_edits.handler({ mode: "create_markers", confirm_non_undoable: false })).resolves
.toMatchObject({ success: false, error: expect.stringContaining("confirm_non_undoable") });
expect(mockedSendCommand).not.toHaveBeenCalled();
});
it("covers optional export, caption, and UXP-scene request variants", async () => {
const tools = getCompetitorGapTools(bridgeOptions);
await tools.validate_project_for_export.handler({ require_non_empty_timeline: false, check_gaps: false });
expect(String(mockedSendCommand.mock.calls[0][0])).toContain("requireNonEmptyTimeline: false");
expect(String(mockedSendCommand.mock.calls[0][0])).toContain("PRESET_NOT_PROVIDED");
vi.clearAllMocks();
await tools.read_sequence_captions.handler({ sequence_id: "sequence-1" });
expect(String(mockedSendCommand.mock.calls[0][0])).toContain('__findSequence("sequence-1")');
const rejectedBridge = { request: vi.fn().mockRejectedValue(new Error("UXP disconnected")) } as any;
await expect(getCompetitorGapTools(bridgeOptions, rejectedBridge).detect_scene_edits.handler({
mode: "apply_cuts", confirm_non_undoable: true,
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("UXP disconnected") });
await expect(getCompetitorGapTools(bridgeOptions, { request: vi.fn() } as any).detect_scene_edits.handler({
mode: "not-supported" as any, confirm_non_undoable: true,
})).resolves.toMatchObject({ success: false, error: expect.stringContaining("Unsupported scene-edit mode") });
const bridge = { request: vi.fn().mockResolvedValue({ applied: true }) } as any;
await getCompetitorGapTools(bridgeOptions, bridge).detect_scene_edits.handler({
mode: "create_markers", confirm_non_undoable: true,
});
expect(bridge.request).toHaveBeenCalledWith("sceneEdit.detect", { mode: "createMarkers" });
});
});