mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-17 04:12:24 +02:00
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>
105 lines
4.0 KiB
Python
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
|