Files
odysseus/tests/test_editor_batch.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

105 lines
4.0 KiB
Python

import json
import re
import subprocess
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BATCH_MODULE = (ROOT / "static/js/editor/batch-runner.js").as_uri()
GALLERY = ROOT / "static/js/galleryEditor.js"
ACTIONS_PANEL = ROOT / "static/js/editor/actions-panel.js"
SW = ROOT / "static/sw.js"
def run_node(source: str):
result = subprocess.run(
["node", "--input-type=module", "-e", source],
cwd=ROOT,
text=True,
capture_output=True,
check=True,
)
return json.loads(result.stdout)
def test_batch_runner_processes_every_document_in_order():
source = textwrap.dedent(
f"""
import {{runBatchOverDocuments}} from {json.dumps(BATCH_MODULE)};
const seen = [];
const progress = [];
const docs = [{{id:'a',name:'A'}},{{id:'b',name:'B'}},{{id:'c',name:'C'}}];
const results = await runBatchOverDocuments({{
documents: docs,
runOne: async (doc, index) => {{ seen.push(doc.id); return index; }},
onProgress: (done, total, doc) => progress.push([done, total, doc.id]),
}});
console.log(JSON.stringify({{seen, progress, results}}));
"""
)
data = run_node(source)
assert data["seen"] == ["a", "b", "c"]
assert data["results"] == [
{"id": "a", "name": "A", "ok": True, "output": 0},
{"id": "b", "name": "B", "ok": True, "output": 1},
{"id": "c", "name": "C", "ok": True, "output": 2},
]
assert data["progress"] == [[1, 3, "a"], [2, 3, "b"], [3, 3, "c"]]
def test_batch_runner_isolates_a_failing_document_and_continues():
source = textwrap.dedent(
f"""
import {{runBatchOverDocuments}} from {json.dumps(BATCH_MODULE)};
const docs = [{{id:'a',name:'A'}},{{id:'b',name:'B'}},{{id:'c',name:'C'}}];
const results = await runBatchOverDocuments({{
documents: docs,
runOne: async (doc) => {{ if (doc.id === 'b') throw new Error('boom'); return doc.id; }},
}});
console.log(JSON.stringify(results));
"""
)
results = run_node(source)
assert len(results) == 3
assert results[0]["ok"] is True and results[0]["output"] == "a"
assert results[1]["ok"] is False and "boom" in results[1]["error"]
assert results[2]["ok"] is True and results[2]["output"] == "c"
def test_batch_runner_tolerates_an_empty_document_list():
source = textwrap.dedent(
f"""
import {{runBatchOverDocuments}} from {json.dumps(BATCH_MODULE)};
const results = await runBatchOverDocuments({{ documents: [], runOne: async () => 1 }});
console.log(JSON.stringify(results));
"""
)
assert run_node(source) == []
def test_gallery_editor_exposes_the_batch_documents_op():
src = GALLERY.read_text()
assert "'actions.batchDocuments':" in src
assert "import { runBatchOverDocuments } from './editor/batch-runner.js';" in src
assert "import { createActionsPanel, ACTION_COMMANDS } from './editor/actions-panel.js';" in src
assert "ACTION_COMMANDS.map(([id, , sel]) => [id, sel])" in src
assert "runBatchOverDocuments({ documents: _openDocuments.map((d) => ({ id: d.id, name: d.name }))" in src
assert "await invoke('document.switch', { id: doc.id });" in src
assert "JSON.parse(localStorage.getItem('ge-actions') || '{}')[name]" in src
def test_actions_panel_has_a_batch_docs_button():
src = ACTIONS_PANEL.read_text()
assert "const batchDocsButton = el('button', 'ge-btn ge-btn-sm', 'Batch docs');" in src
assert "batchRow.append(batchSelect, batchButton, batchDocsButton);" in src
assert "window.geEditorControl?.invoke?.('actions.batchDocuments', { name })" in src
assert "batchDocsButton.disabled = true;" in src
assert "batchDocsButton.disabled = false;" in src
def test_service_worker_precaches_the_batch_runner():
src = SW.read_text()
assert re.search(r"odysseus-v\d+-pr40-editor-precache", src)
assert "'/static/js/editor/batch-runner.js'," in src