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
This commit is contained in:
+210
@@ -0,0 +1,210 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const Events = require("../../uxp-plugin/events.cjs");
|
||||
|
||||
describe("UXP host event journal", () => {
|
||||
it("maps only documented stable SnapEvent constants to passive timeline receipts", () => {
|
||||
const definitions = Events.createTimelineSnapEventDefinitions({
|
||||
EVENT_SNAP_TO_KEYFRAME: "snap-keyframe",
|
||||
EVENT_SNAP_TO_TRACKITEM: "snap-track-item",
|
||||
EVENT_SNAP_TO_GUIDES: "snap-guides",
|
||||
EVENT_SNAP_RAZOR_TO_PLAYHEAD: "snap-razor-playhead",
|
||||
EVENT_SNAP_RAZOR_TO_MARKER: "snap-razor-marker",
|
||||
EVENT_SNAP_PLAYHEAD_TO_TRACKITEM_EDGE: "snap-playhead-edge",
|
||||
UNDOCUMENTED_EVENT: "must-not-register",
|
||||
});
|
||||
|
||||
expect(definitions).toEqual([
|
||||
{ category: "timeline", name: "timeline.snap.keyframe", eventName: "snap-keyframe", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "timeline", name: "timeline.snap.trackItem", eventName: "snap-track-item", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "timeline", name: "timeline.snap.guides", eventName: "snap-guides", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "timeline", name: "timeline.snap.razor.playhead", eventName: "snap-razor-playhead", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "timeline", name: "timeline.snap.razor.marker", eventName: "snap-razor-marker", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "timeline", name: "timeline.snap.playhead.trackItemEdge", eventName: "snap-playhead-edge", stateInvalidating: false, coalesceKey: null },
|
||||
]);
|
||||
expect(Events.createTimelineSnapEventDefinitions({
|
||||
EVENT_SNAP_TO_KEYFRAME: 1,
|
||||
EVENT_SNAP_TO_TRACKITEM: "",
|
||||
})).toEqual([]);
|
||||
expect(Events.createTimelineSnapEventDefinitions(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it("filters timeline SnapEvent receipts without exposing host payload fields", () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
journal.recordHostEvent({
|
||||
category: "timeline",
|
||||
name: "timeline.snap.keyframe",
|
||||
detail: { state: 4, target: "private clip", path: "D:/private.prproj" },
|
||||
});
|
||||
|
||||
expect(journal.list({ categories: ["timeline"], eventNames: ["timeline.snap.keyframe"] }))
|
||||
.toMatchObject({ events: [{ category: "timeline", name: "timeline.snap.keyframe", detail: { state: 4 } }] });
|
||||
});
|
||||
|
||||
it("maps only documented root operation-boundary events and coalesces drag-over receipts", () => {
|
||||
const definitions = Events.createOperationBoundaryEventDefinitions({
|
||||
EVENT_CLIP_EXTEND_REACHED: "clip-extend-reached",
|
||||
EVENT_EFFECT_DRAG_OVER: "effect-drag-over",
|
||||
EVENT_EXPORT_MEDIA_COMPLETE: "already-handled-completion",
|
||||
});
|
||||
|
||||
expect(definitions).toEqual([
|
||||
{ category: "operation", name: "operation.clip.extend.reached", eventName: "clip-extend-reached", stateInvalidating: false, coalesceKey: null },
|
||||
{ category: "operation", name: "operation.effect.drag.over", eventName: "effect-drag-over", stateInvalidating: false, coalesceKey: "operation.effect.drag.over" },
|
||||
]);
|
||||
expect(Events.createOperationBoundaryEventDefinitions({
|
||||
EVENT_CLIP_EXTEND_REACHED: 1,
|
||||
EVENT_EFFECT_DRAG_OVER: "",
|
||||
})).toEqual([]);
|
||||
expect(Events.createOperationBoundaryEventDefinitions(null)).toEqual([]);
|
||||
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
journal.recordHostEvent({
|
||||
category: "operation", name: definitions[1].name, coalesceKey: definitions[1].coalesceKey,
|
||||
detail: { state: 1, path: "D:/private.prproj" },
|
||||
});
|
||||
journal.recordHostEvent({
|
||||
category: "operation", name: definitions[1].name, coalesceKey: definitions[1].coalesceKey,
|
||||
detail: { state: 2, target: "private clip" },
|
||||
});
|
||||
expect(journal.list({ eventNames: ["operation.effect.drag.over"] })).toMatchObject({
|
||||
latestRevision: 2,
|
||||
events: [{ category: "operation", name: "operation.effect.drag.over", detail: { state: 2 }, coalesced: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a bounded revisioned history and reports overflow", () => {
|
||||
let now = 1_700_000_000_000;
|
||||
const journal = Events.createEventJournal({ capacity: 16, now: () => now++ });
|
||||
for (let index = 0; index < 20; index += 1) {
|
||||
journal.append({ category: "project", name: "project.dirty", detail: { state: index } });
|
||||
}
|
||||
|
||||
expect(journal.status()).toMatchObject({ capacity: 16, size: 16, latestRevision: 20, oldestRevision: 5, dropped: 4 });
|
||||
expect(journal.list({ afterRevision: 1, limit: 3 })).toMatchObject({
|
||||
overflow: true,
|
||||
dropped: 4,
|
||||
events: [
|
||||
{ revision: 5, category: "project", name: "project.dirty" },
|
||||
{ revision: 6 },
|
||||
{ revision: 7 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("coalesces consecutive noisy progress receipts without exposing raw fields", () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
journal.append({
|
||||
category: "encoder", name: "encoder.progress", coalesceKey: "encoder.progress",
|
||||
detail: { progress: 0.1, path: "D:/secret.mov" },
|
||||
});
|
||||
journal.append({
|
||||
category: "encoder", name: "encoder.progress", coalesceKey: "encoder.progress",
|
||||
detail: { progress: 0.8, projectName: "Private" },
|
||||
});
|
||||
|
||||
expect(journal.list({})).toMatchObject({
|
||||
latestRevision: 2,
|
||||
events: [{ revision: 2, detail: { progress: 0.8 }, coalesced: 1 }],
|
||||
});
|
||||
expect(journal.list({ afterRevision: 1 })).toMatchObject({
|
||||
overflow: false,
|
||||
events: [{ revision: 2, coalesced: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
it("returns immediately when a filtered cursor has fallen behind evicted history", async () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
for (let index = 0; index < 20; index += 1) {
|
||||
journal.append({ category: "project", name: "project.dirty", detail: { state: index } });
|
||||
}
|
||||
|
||||
await expect(journal.wait({
|
||||
afterRevision: 1,
|
||||
categories: ["encoder"],
|
||||
timeoutMs: 60_000,
|
||||
})).resolves.toMatchObject({ overflow: true, timedOut: false, events: [] });
|
||||
});
|
||||
|
||||
it("settles a pending filtered waiter as soon as its cursor history is evicted", async () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
for (let index = 0; index < 16; index += 1) {
|
||||
journal.append({ category: "project", name: "project.dirty", detail: { state: index } });
|
||||
}
|
||||
const pending = journal.wait({
|
||||
afterRevision: 1,
|
||||
categories: ["encoder"],
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
|
||||
journal.append({ category: "project", name: "project.dirty" });
|
||||
journal.append({ category: "project", name: "project.dirty" });
|
||||
|
||||
await expect(pending).resolves.toMatchObject({ overflow: true, timedOut: false, events: [] });
|
||||
});
|
||||
|
||||
it("waits for a matching receipt and times out cleanly", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
const matching = journal.wait({ afterRevision: 0, categories: ["operation"], timeoutMs: 1000 });
|
||||
journal.append({ category: "project", name: "project.dirty" });
|
||||
journal.append({ category: "operation", name: "operation.import.complete", detail: { state: 0 } });
|
||||
await expect(matching).resolves.toMatchObject({
|
||||
timedOut: false,
|
||||
events: [{ category: "operation", name: "operation.import.complete", detail: { state: 0 } }],
|
||||
});
|
||||
|
||||
const timeout = journal.wait({ afterRevision: 2, eventNames: ["encoder.complete"], timeoutMs: 500 });
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
await expect(timeout).resolves.toMatchObject({ timedOut: true, events: [] });
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves pending waiters when the journal closes", async () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
const pending = journal.wait({ timeoutMs: 60000 });
|
||||
journal.close();
|
||||
await expect(pending).resolves.toMatchObject({ closed: true, events: [] });
|
||||
});
|
||||
|
||||
it("attributes encoder events only while exactly one job is active", async () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
const first = journal.beginEncodeJob({ kind: "sequence", operationId: "encode-one" });
|
||||
journal.markEncodeAccepted(first.jobId);
|
||||
journal.recordHostEvent({ category: "encoder", name: "encoder.queued" });
|
||||
journal.recordHostEvent({ category: "encoder", name: "encoder.progress", detail: { progress: 0.5 } });
|
||||
|
||||
expect(journal.listEncodeJobs({ jobId: first.jobId })).toMatchObject({
|
||||
correlation: "single-active-job-only",
|
||||
jobs: [{ jobId: "encode-one", state: "rendering", progress: 0.5, terminal: false }],
|
||||
});
|
||||
|
||||
const terminal = journal.waitForEncodeJob({ jobId: first.jobId, timeoutMs: 1000 });
|
||||
journal.recordHostEvent({ category: "encoder", name: "encoder.complete" });
|
||||
await expect(terminal).resolves.toMatchObject({
|
||||
timedOut: false,
|
||||
job: { state: "completed", terminal: true, terminalReason: "completed" },
|
||||
});
|
||||
});
|
||||
|
||||
it("does not guess job correlation when multiple encodes are active", () => {
|
||||
const journal = Events.createEventJournal({ capacity: 16 });
|
||||
journal.beginEncodeJob({ kind: "sequence", operationId: "encode-one" });
|
||||
journal.beginEncodeJob({ kind: "file", operationId: "encode-two" });
|
||||
const receipt = journal.recordHostEvent({ category: "encoder", name: "encoder.complete" });
|
||||
|
||||
expect(receipt).toMatchObject({ detail: { attributed: false } });
|
||||
expect(journal.listEncodeJobs({})).toMatchObject({
|
||||
unattributedEncoderEvents: 1,
|
||||
jobs: expect.arrayContaining([
|
||||
expect.objectContaining({ jobId: "encode-one", terminal: false }),
|
||||
expect.objectContaining({ jobId: "encode-two", terminal: false }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user