mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-13 03:32:21 +02:00
202 lines
8.5 KiB
Python
202 lines
8.5 KiB
Python
import json
|
|
import subprocess
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
GEOMETRY_MODULE = (ROOT / "static/js/editor/layer-geometry.js").as_uri()
|
|
MOVE_MODULE = (ROOT / "static/js/editor/tools/move.js").as_uri()
|
|
STATE_MODULE = (ROOT / "static/js/editor/state.js").as_uri()
|
|
|
|
|
|
def run_node(body: str):
|
|
script = textwrap.dedent(
|
|
f"""
|
|
import {{
|
|
createLayerGeometryController, normalizeLayerCoordinate,
|
|
readLayerGeometry, setLayerPosition,
|
|
}} from {json.dumps(GEOMETRY_MODULE)};
|
|
import {{ createMoveTool }} from {json.dumps(MOVE_MODULE)};
|
|
import {{ state }} from {json.dumps(STATE_MODULE)};
|
|
globalThis.document = {{activeElement:null}};
|
|
{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_geometry_normalizes_coordinates_and_reports_layer_bounds():
|
|
result = run_node(
|
|
"""
|
|
const layer = {id:'photo',canvas:{width:640,height:480}};
|
|
const editor = {layerOffsets:new Map([['photo',{x:12.6,y:-7.4}]])};
|
|
const geometry = readLayerGeometry(editor,layer);
|
|
const moved = setLayerPosition(editor,layer,'42.8','bad');
|
|
console.log(JSON.stringify({
|
|
geometry,moved,
|
|
high:normalizeLayerCoordinate(99999999),
|
|
low:normalizeLayerCoordinate(-99999999),
|
|
}));
|
|
"""
|
|
)
|
|
assert result == {
|
|
"geometry": {"x": 13, "y": -7, "width": 640, "height": 480},
|
|
"moved": {"x": 43, "y": -7},
|
|
"high": 1000000,
|
|
"low": -1000000,
|
|
}
|
|
|
|
|
|
def test_nudges_coalesce_history_and_transform_position_tracks_without_extra_snapshot():
|
|
result = run_node(
|
|
"""
|
|
const layer = {id:'photo',name:'Photo',canvas:{width:100,height:80},locked:false};
|
|
Object.assign(state,{
|
|
layers:[layer],activeLayerId:layer.id,
|
|
layerOffsets:new Map([[layer.id,{x:0,y:0}]]),
|
|
transformActive:false,transformLayer:null,transformOrigOffset:null,
|
|
});
|
|
const labels=[];
|
|
const controller=createLayerGeometryController({
|
|
activeLayer:()=>layer,saveState:label=>labels.push(label),composite:()=>{},
|
|
});
|
|
controller.nudge(1,0);
|
|
controller.nudge(1,0);
|
|
controller.endNudge();
|
|
controller.nudge(0,10);
|
|
controller.endNudge();
|
|
state.transformActive=true;
|
|
state.transformLayer=layer;
|
|
state.transformOrigOffset={x:2,y:10};
|
|
controller.nudge(5,-3);
|
|
console.log(JSON.stringify({
|
|
labels,offset:state.layerOffsets.get(layer.id),orig:state.transformOrigOffset,
|
|
}));
|
|
"""
|
|
)
|
|
assert result == {
|
|
"labels": ['Nudge "Photo"', 'Nudge "Photo"'],
|
|
"offset": {"x": 7, "y": 7},
|
|
"orig": {"x": 7, "y": 7},
|
|
}
|
|
|
|
|
|
def test_move_drag_can_return_to_origin_and_transform_move_reuses_session_history():
|
|
result = run_node(
|
|
"""
|
|
const layer={id:'photo',name:'Photo',canvas:{width:50,height:40},visible:true};
|
|
const canvas={width:200,height:200,getBoundingClientRect:()=>({left:0,top:0,width:200,height:200})};
|
|
Object.assign(state,{
|
|
mainCanvas:canvas,imgWidth:200,imgHeight:200,zoom:1,
|
|
layers:[layer],activeLayerId:layer.id,layerOffsets:new Map([[layer.id,{x:0,y:0}]]),
|
|
moving:false,transformActive:false,
|
|
});
|
|
const labels=[];
|
|
const positions=[];
|
|
const tool=createMoveTool({
|
|
activeLayer:()=>layer,saveState:label=>labels.push(label),composite:()=>{},
|
|
onPositionChange:(_layer,_before,next)=>positions.push(next),
|
|
});
|
|
const event=(x,y)=>({clientX:x,clientY:y,preventDefault(){},ctrlKey:false,metaKey:false});
|
|
tool.begin(event(10,10));
|
|
tool.drag(event(20,10));
|
|
tool.drag(event(10,10));
|
|
tool.end();
|
|
state.transformActive=true;
|
|
tool.begin(event(10,10));
|
|
tool.drag(event(15,10));
|
|
tool.end();
|
|
console.log(JSON.stringify({labels,positions,offset:state.layerOffsets.get(layer.id)}));
|
|
"""
|
|
)
|
|
assert result["labels"] == ['Move "Photo"']
|
|
assert result["positions"][:2] == [{"x": 10, "y": 0}, {"x": 0, "y": 0}]
|
|
assert result["offset"] == {"x": 5, "y": 0}
|
|
|
|
|
|
def test_position_lock_blocks_drag_and_nudge_without_blocking_other_layer_locks():
|
|
result = run_node(
|
|
"""
|
|
const layer={id:'photo',name:'Photo',canvas:{width:50,height:40},visible:true,locks:{position:true}};
|
|
const canvas={width:200,height:200,getBoundingClientRect:()=>({left:0,top:0,width:200,height:200})};
|
|
Object.assign(state,{
|
|
mainCanvas:canvas,imgWidth:200,imgHeight:200,zoom:1,
|
|
layers:[layer],layerGroups:[],selectedLayerIds:[layer.id],activeLayerId:layer.id,
|
|
layerOffsets:new Map([[layer.id,{x:0,y:0}]]),moving:false,transformActive:false,
|
|
});
|
|
const labels=[];
|
|
const controller=createLayerGeometryController({activeLayer:()=>layer,saveState:label=>labels.push(label),composite:()=>{}});
|
|
const tool=createMoveTool({activeLayer:()=>layer,saveState:label=>labels.push(label),composite:()=>{}});
|
|
const event=(x,y)=>({clientX:x,clientY:y,preventDefault(){},ctrlKey:false,metaKey:false});
|
|
controller.nudge(5,0);
|
|
tool.begin(event(10,10));
|
|
tool.drag(event(30,30));
|
|
tool.end();
|
|
const blocked={offset:state.layerOffsets.get(layer.id),labels:[...labels]};
|
|
layer.locks={pixels:true,transparency:true,position:false};
|
|
controller.nudge(5,0);
|
|
console.log(JSON.stringify({blocked,allowed:state.layerOffsets.get(layer.id),labels}));
|
|
"""
|
|
)
|
|
assert result["blocked"] == {"offset": {"x": 0, "y": 0}, "labels": []}
|
|
assert result["allowed"] == {"x": 5, "y": 0}
|
|
assert result["labels"] == ['Nudge "Photo"']
|
|
|
|
|
|
def test_unlinked_mask_moves_independently_and_stays_fixed_when_parent_moves():
|
|
result = run_node(
|
|
"""
|
|
const mask={id:'mask',name:'Layer Mask',mode:'layer',linked:false,offset:{x:4,y:6}};
|
|
const layer={id:'photo',name:'Photo',canvas:{width:50,height:40},visible:true,masks:[mask],activeMaskId:mask.id};
|
|
const canvas={width:200,height:200,getBoundingClientRect:()=>({left:0,top:0,width:200,height:200})};
|
|
Object.assign(state,{
|
|
mainCanvas:canvas,imgWidth:200,imgHeight:200,zoom:1,
|
|
layers:[layer],layerGroups:[],selectedLayerIds:[layer.id],activeLayerId:layer.id,
|
|
layerOffsets:new Map([[layer.id,{x:20,y:30}]]),moving:false,transformActive:false,
|
|
snapEnabled:false,snapToGrid:false,guides:{vertical:[],horizontal:[]},
|
|
});
|
|
const labels=[];
|
|
const tool=createMoveTool({activeLayer:()=>layer,saveState:label=>labels.push(label),composite:()=>{}});
|
|
const event=(x,y)=>({clientX:x,clientY:y,preventDefault(){},ctrlKey:false,metaKey:false});
|
|
tool.begin(event(10,10));
|
|
tool.drag(event(17,14));
|
|
tool.end();
|
|
const independent={layer:state.layerOffsets.get(layer.id),mask:{...mask.offset}};
|
|
layer.activeMaskId=null;
|
|
tool.begin(event(10,10));
|
|
tool.drag(event(22,15));
|
|
tool.end();
|
|
console.log(JSON.stringify({labels,independent,layer:state.layerOffsets.get(layer.id),mask:mask.offset,
|
|
documentMask:{x:state.layerOffsets.get(layer.id).x+mask.offset.x,y:state.layerOffsets.get(layer.id).y+mask.offset.y}}));
|
|
"""
|
|
)
|
|
assert result == {
|
|
"labels": ['Move mask "Layer Mask"', 'Move "Photo"'],
|
|
"independent": {"layer": {"x": 20, "y": 30}, "mask": {"x": 11, "y": 10}},
|
|
"layer": {"x": 32, "y": 35},
|
|
"mask": {"x": -1, "y": 5},
|
|
"documentMask": {"x": 31, "y": 40},
|
|
}
|
|
|
|
|
|
def test_geometry_controls_and_arrow_nudging_are_wired_offline():
|
|
controls = (ROOT / "static/js/editor/build/controls.js").read_text(encoding="utf-8")
|
|
keyboard = (ROOT / "static/js/editor/keyboard-shortcuts.js").read_text(encoding="utf-8")
|
|
editor = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8")
|
|
service_worker = (ROOT / "static/sw.js").read_text(encoding="utf-8")
|
|
|
|
for field_id in ("ge-layer-x", "ge-layer-y", "ge-layer-width", "ge-layer-height"):
|
|
assert f'id="{field_id}"' in controls
|
|
assert "e.key.startsWith('Arrow')" in keyboard
|
|
assert "nudgeActiveLayer?.(...delta)" in keyboard
|
|
assert "_layerGeometry.trackExternalMove" in editor
|
|
assert "/static/js/editor/layer-geometry.js" in service_worker
|