Files
odysseus/tests/test_editor_control_js.py
T
pewdiepie-archdaemonandClaude Opus 5.5 5fac693c1c feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).

Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:57:35 +00:00

121 lines
3.8 KiB
Python

import json
import subprocess
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MODULE = (ROOT / "static/js/editor/editor-control.js").as_uri()
def run_node(body):
script = textwrap.dedent(
f"""
import {{ createEditorControl, EDITOR_CONTROL_VERSION }} from {json.dumps(MODULE)};
{body}
"""
)
result = subprocess.run(
["node", "--input-type=module", "-e", script],
cwd=ROOT,
text=True,
capture_output=True,
check=True,
)
return json.loads(result.stdout)
def test_editor_control_version_and_listing():
out = run_node(
"""
const control = createEditorControl({
handlers: {
'layers.list': { run: () => [], description: 'List layers' },
'layers.addRaster': () => ({ id: 'layer-1' }),
},
});
console.log(JSON.stringify({
version: EDITOR_CONTROL_VERSION,
installed: control.installed,
listed: control.list(),
described: control.describe('layers.list'),
missing: control.describe('nope'),
}));
"""
)
assert out["version"] == "1.0"
assert out["installed"] is True
assert out["listed"] == [
{"op": "layers.addRaster", "description": ""},
{"op": "layers.list", "description": "List layers"},
]
assert out["described"] == {"op": "layers.list", "description": "List layers"}
assert out["missing"] is None
def test_editor_control_invoke_and_run():
out = run_node(
"""
const calls = [];
const control = createEditorControl({
getState: () => ({ imgWidth: 640, imgHeight: 480 }),
handlers: {
'document.info': (params, ctx) => {
calls.push(['info', params, ctx.state]);
return ctx.state;
},
'history.undo': () => 'undone',
'boom': () => { throw new Error('nope'); },
},
});
const info = await control.invoke('document.info', { a: 1 });
const unknown = await control.invoke('does.not.exist');
const failed = await control.invoke('boom');
const run = await control.run([
{ op: 'document.info' },
{ op: 'history.undo' },
{ op: 'boom' },
{ op: 'history.undo' },
]);
console.log(JSON.stringify({ info, unknown, failed, run, calls }));
"""
)
assert out["info"] == {
"ok": True,
"op": "document.info",
"result": {"imgWidth": 640, "imgHeight": 480},
}
assert out["unknown"] == {
"ok": False,
"op": "does.not.exist",
"error": "Unknown editor op: does.not.exist",
}
assert out["failed"] == {"ok": False, "op": "boom", "error": "nope"}
# run() executes sequentially and stops at the first failure (so the
# trailing history.undo is never reached).
assert [r["op"] for r in out["run"]] == ["document.info", "history.undo", "boom"]
assert out["run"][-1]["ok"] is False
assert out["calls"][0][0] == "info"
assert out["calls"][0][1] == {"a": 1}
def test_editor_control_handler_entry_shapes():
out = run_node(
"""
const control = createEditorControl({
handlers: {
plain: () => 'plain-ok',
wrapped: { run: () => 'wrapped-ok', description: 'W' },
ignored: null,
},
});
console.log(JSON.stringify({
plain: await control.invoke('plain'),
wrapped: await control.invoke('wrapped'),
hasIgnored: control.list().some((x) => x.op === 'ignored'),
}));
"""
)
assert out["plain"] == {"ok": True, "op": "plain", "result": "plain-ok"}
assert out["wrapped"] == {"ok": True, "op": "wrapped", "result": "wrapped-ok"}
assert out["hasIgnored"] is False