Files
jhonny-editor/bm/premiere-pro-mcp-main/tests/bridge/script-builder.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

177 lines
7.2 KiB
TypeScript
Executable File

import { describe, it, expect } from "vitest";
import { buildScript, escapeForExtendScript, buildToolScript, getHelpersSource, buildBootstrap, helpersFileName, HELPERS_VERSION } from "../../src/bridge/script-builder.js";
import { runInNewContext } from "node:vm";
describe("buildScript", () => {
it("wraps code in an IIFE with try/catch", () => {
const result = buildScript("return __result({ ok: true });");
expect(result).toContain("(function() {");
expect(result).toContain("return __result({ ok: true });");
expect(result).toContain("} catch(e) {");
expect(result).toContain("return __error(e.toString());");
expect(result).toContain("})();");
});
it("defines helper functions in the helpers source", () => {
const result = getHelpersSource();
expect(result).toContain("var TICKS_PER_SECOND = 254016000000;");
expect(result).toContain("function __ticksToSeconds(ticks)");
expect(result).toContain("function __secondsToTicks(seconds)");
expect(result).toContain("function __ticksToTimecode(ticks, fps)");
expect(result).toContain("function __pad(n)");
expect(result).toContain("function __findSequence(idOrName)");
expect(result).toContain("function __findProjectItem(nodeIdOrName, rootItem)");
expect(result).toContain("function __findClip(nodeId)");
expect(result).toContain("function __getAllClips(seq)");
expect(result).toContain("function __jsonStringify(obj)");
expect(result).toContain("function __result(data)");
expect(result).toContain("function __error(msg)");
});
it("preserves multi-line code blocks", () => {
const code = `var x = 1;
var y = 2;
return __result({ sum: x + y });`;
const result = buildScript(code);
expect(result).toContain("var x = 1;");
expect(result).toContain("var y = 2;");
expect(result).toContain("return __result({ sum: x + y });");
});
it("handles empty code", () => {
const result = buildScript("");
expect(result).toContain("(function() {");
expect(result).toContain("})();");
});
it("returns a string", () => {
expect(typeof buildScript("")).toBe("string");
});
});
describe("buildToolScript (alias)", () => {
it("is the same function as buildScript", () => {
expect(buildToolScript).toBe(buildScript);
});
it("produces identical output to buildScript", () => {
const code = "return __result({ test: true });";
expect(buildToolScript(code)).toBe(buildScript(code));
});
});
describe("escapeForExtendScript", () => {
it("escapes backslashes", () => {
expect(escapeForExtendScript("C:\\Users\\test")).toBe("C:\\\\Users\\\\test");
});
it("escapes double quotes", () => {
expect(escapeForExtendScript('say "hello"')).toBe('say \\"hello\\"');
});
it("escapes single quotes", () => {
expect(escapeForExtendScript("it's")).toBe("it\\'s");
});
it("escapes newlines", () => {
expect(escapeForExtendScript("line1\nline2")).toBe("line1\\nline2");
});
it("escapes carriage returns", () => {
expect(escapeForExtendScript("line1\rline2")).toBe("line1\\rline2");
});
it("escapes tabs", () => {
expect(escapeForExtendScript("col1\tcol2")).toBe("col1\\tcol2");
});
it("handles empty strings", () => {
expect(escapeForExtendScript("")).toBe("");
});
it("handles strings with no special characters", () => {
expect(escapeForExtendScript("hello world")).toBe("hello world");
});
it("handles multiple escape characters in one string", () => {
const input = 'C:\\path\\to\n"file"\t\'test\'';
const result = escapeForExtendScript(input);
expect(result).toBe('C:\\\\path\\\\to\\n\\"file\\"\\t\\\'test\\\'');
});
it("handles unicode characters (passes through)", () => {
expect(escapeForExtendScript("日本語")).toBe("日本語");
});
});
describe("generated script structure", () => {
it("bootstrap loads this exact helpers version via $.evalFile", () => {
const bootstrap = buildBootstrap("/tmp/x/" + helpersFileName());
expect(bootstrap).toContain(`__HELPERS_V !== "${HELPERS_VERSION}"`);
expect(bootstrap).toContain(`$.evalFile("/tmp/x/helpers_${HELPERS_VERSION}.jsx")`);
expect(getHelpersSource()).toContain(`var __HELPERS_V = "${HELPERS_VERSION}";`);
});
it("bootstrap escapes quotes and backslashes in the helpers path", () => {
const bootstrap = buildBootstrap('C:\\temp\\he"rs.jsx');
expect(bootstrap).toContain('$.evalFile("C:\\\\temp\\\\he\\"rs.jsx")');
});
it("__findProjectItem recursively searches bins", () => {
const result = getHelpersSource();
expect(result).toContain("if (item.type === 2)");
expect(result).toContain("var found = __findProjectItem(nodeIdOrName, item);");
});
it("normalizes numeric host IDs without changing exact name matching", () => {
const result = getHelpersSource();
expect(result).toContain("var wantedId = String(idOrName);");
expect(result).toContain("String(seq.sequenceID) === wantedId || seq.name === idOrName");
expect(result).toContain("var wantedId = String(nodeIdOrName);");
expect(result).toContain("String(item.nodeId) === wantedId || item.name === nodeIdOrName");
expect(result).toContain("var wantedId = String(nodeId);");
expect(result).toContain("String(clip.nodeId) === wantedId");
});
it("__findClip searches both video and audio tracks", () => {
const result = getHelpersSource();
expect(result).toContain("seq.videoTracks.numTracks");
expect(result).toContain("seq.audioTracks.numTracks");
expect(result).toContain('trackType: "video"');
expect(result).toContain('trackType: "audio"');
});
it("__jsonStringify handles all types", () => {
const result = getHelpersSource();
expect(result).toContain('if (obj === null) return "null"');
expect(result).toContain('if (typeof obj === "string")');
expect(result).toContain('if (typeof obj === "number"');
expect(result).toContain("if (obj instanceof Array)");
expect(result).toContain('if (typeof obj === "object")');
});
});
describe("helpers execute correctly in an ES3-like engine", () => {
it("__result works when JSON is undefined (polyfill must not recurse into itself)", () => {
// Regression: __jsonStringify once delegated to JSON.stringify while the JSON
// polyfill delegated back to __jsonStringify — infinite mutual recursion that
// stack-overran the shared ExtendScript engine on every tool response.
const sandbox: Record<string, unknown> = { JSON: undefined };
const out = runInNewContext(
getHelpersSource() + '\n__result({ connected: true, nested: { n: 1, arr: [1, "a", false, null] } });',
sandbox
);
expect(out).toBe('{"success":true,"data":{"connected":true,"nested":{"n":1,"arr":[1,"a",false,null]}}}');
});
it("a stale wrapper from an older helpers version gets replaced", () => {
// Simulate a polluted long-lived engine: JSON.stringify is our old-style wrapper.
const stale = { stringify: function badWrapper(o: unknown) { return "__jsonStringify" + String(o); } };
const sandbox: Record<string, unknown> = { JSON: stale };
runInNewContext(getHelpersSource(), sandbox);
const json = sandbox.JSON as { stringify: (o: unknown) => string; __mcpPolyfill?: boolean };
expect(json.__mcpPolyfill).toBe(true);
expect(json.stringify({ ok: 1 })).toBe('{"ok":1}');
});
});