Files
odysseus/tests/test_editor_text_layers_js.py
T

159 lines
6.4 KiB
Python

import json
import subprocess
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MODULE = (ROOT / "static/js/editor/text-layer.js").as_uri()
def run_node(body: str):
script = textwrap.dedent(
f"""
import {{ normalizeTextData, rasterizeTextLayer, renderTextLayer }} from {json.dumps(MODULE)};
let id = 0;
function canvas(tag='canvas') {{
const value = {{tag, width:0, height:0, draws:[], fills:[], strokes:[]}};
const ctx = {{
font:'', textBaseline:'', textAlign:'left', fillStyle:'', strokeStyle:'',
lineWidth:0, lineJoin:'', imageSmoothingEnabled:false, imageSmoothingQuality:'low',
measureText(text) {{ return {{width:String(text).length * 10}}; }},
fillText(...args) {{ value.fills.push(args); }},
strokeText(...args) {{ value.strokes.push(args); }},
drawImage(...args) {{ value.draws.push(args.map(item => item?.tag || item)); }},
clearRect() {{}}, translate() {{}}, rotate() {{}}, scale() {{}},
}};
value.getContext = () => ctx;
return value;
}}
globalThis.document = {{createElement:() => canvas(`canvas-${{++id}}`)}};
{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_text_renderer_keeps_editable_metadata_and_builds_raster_cache():
result = run_node(
"""
const target = canvas('target');
const layer = {
canvas:target, ctx:target.getContext('2d'),
text:{
content:'Hello\\nWorld', fontFamily:'Georgia', fontSize:20,
fontWeight:'700', fontStyle:'italic', align:'center', color:'#ff0000',
strokeColor:'#000000', strokeWidth:2, lineHeight:1.5,
transform:{scaleX:2,scaleY:1.5,rotation:15,flipH:true,flipV:false},
},
};
renderTextLayer(layer);
console.log(JSON.stringify({
kind:layer.kind, content:layer.text.content, font:layer.text.fontFamily,
transform:layer.text.transform, size:[layer.canvas.width,layer.canvas.height],
finalDraw:layer.canvas.draws.at(-1),
}));
"""
)
assert result["kind"] == "text"
assert result["content"] == "Hello\nWorld"
assert result["font"] == "Georgia"
assert result["transform"] == {
"scaleX": 2, "scaleY": 1.5, "rotation": 15, "flipH": True, "flipV": False
}
assert result["size"][0] > 100
assert result["size"][1] > 80
assert result["finalDraw"][1:] == [0, 0]
def test_text_data_normalization_bounds_invalid_values():
result = run_node(
"""
console.log(JSON.stringify(normalizeTextData({
content:42, fontSize:-5, align:'diagonal', lineHeight:99,
transform:{scaleX:0,scaleY:'bad',rotation:'bad',flipV:1},
})));
"""
)
assert result["content"] == "42"
assert result["fontSize"] == 1
assert result["align"] == "left"
assert result["lineHeight"] == 5
assert result["letterSpacing"] == 0
assert result["frameWidth"] == 320
assert result["frameHeight"] == 0
assert result["verticalAlign"] == "top"
assert result["transform"]["scaleX"] == 0.01
assert result["transform"]["scaleY"] == 1
assert result["transform"]["rotation"] == 0
assert result["transform"]["flipV"] is True
def test_rasterize_text_layer_clears_retained_metadata_only_for_text():
result = run_node(
"""
const text = {kind:'text', text:{content:'Still editable'}};
const raster = {kind:'raster', text:null};
console.log(JSON.stringify({
changed:rasterizeTextLayer(text), text,
rasterChanged:rasterizeTextLayer(raster), raster,
}));
"""
)
assert result["changed"] is True
assert result["text"] == {"kind": "raster", "text": None}
assert result["rasterChanged"] is False
assert result["raster"] == {"kind": "raster", "text": None}
def test_text_tool_is_retained_across_editor_subsystems():
editor = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8")
toolbar = (ROOT / "static/js/editor/build/toolbar.js").read_text(encoding="utf-8")
controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8")
codec = (ROOT / "static/js/editor/document-codec.js").read_text(encoding="utf-8")
geometry = (ROOT / "static/js/editor/document-geometry.js").read_text(encoding="utf-8")
transform = (ROOT / "static/js/editor/tools/transform-session.js").read_text(encoding="utf-8")
panel = (ROOT / "static/js/editor/layer-panel.js").read_text(encoding="utf-8")
merge = (ROOT / "static/js/editor/wire-merge-buttons.js").read_text(encoding="utf-8")
assert "{ id: 'text', label: 'Text'" in toolbar
assert "key: 'T'" in toolbar
assert 'id="ge-text-section"' in controls
assert "if (state.tool === 'text') return _placeText(e);" in editor
assert "kind: l.kind || 'raster'" in editor
assert "text: _cloneDocumentValue(l.text, null)" in editor
assert "kind: layer.kind || 'raster'" in codec
assert "text: cloneDocumentValue(layer.text, null)" in codec
assert "scaleTextLayer(layer, scaleX, scaleY)" in geometry
assert "rotateTextLayer(layer, normalized)" in geometry
assert "renderTextLayer(layer)" in transform
assert 'id="ge-text-rasterize"' in controls
assert "layer.kind = 'raster'" in editor
assert "layer.text = null" in editor
assert "rasterizeTextLayer(layer);" in panel
assert "rasterizeTextLayer(layer);" in merge
assert "_rasterizeTextLayer(layer)" in editor
assert 'id="ge-text-letter-spacing"' in controls
assert 'id="ge-text-frame-width"' in controls
assert 'id="ge-text-frame-height"' in controls
assert 'id="ge-text-vertical-align"' in controls
assert 'id="ge-text-auto-width"' in controls
assert 'autoWidth: !!document.getElementById(\'ge-text-auto-width\')?.checked' in editor
assert "createTextEditOverlay" in editor
def test_text_auto_width_control_does_not_share_move_tool_selector():
controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8")
assert 'class="ge-text-auto-width-option"' in controls
assert 'class="ge-auto-select-option ge-text-auto-width-option"' not in controls