from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def read(rel): return (ROOT / rel).read_text(encoding="utf-8") STATE = read("static/js/editor/state.js") POPUP = read("static/js/editor/build/transform-popup.js") SESSION = read("static/js/editor/tools/transform-session.js") MULTI = read("static/js/editor/multi-transform.js") GEO = read("static/js/editor/transform-frame-geometry.js") HANDLES = read("static/js/editor/tools/transform-handles.js") DRAG = read("static/js/editor/tools/transform-drag.js") def test_state_has_skew_fields(): assert "transformPendingSkewX: 0" in STATE assert "transformPendingSkewY: 0" in STATE def test_popup_exposes_skew_fields(): assert 'id="ge-transform-skew-x"' in POPUP assert 'id="ge-transform-skew-y"' in POPUP assert 'data-spin-for="ge-transform-skew-x"' in POPUP assert 'data-spin-for="ge-transform-skew-y"' in POPUP def test_session_threads_skew_through_transform(): assert "state.transformPendingSkewX" in SESSION assert "state.transformPendingSkewY" in SESSION assert "'#ge-transform-skew-x': state.transformPendingSkewX" in SESSION assert "skewX: state.transformPendingSkewX" in SESSION assert "state.transformPendingSkewX = target.skewX" in SESSION assert "Math.tan((geometry.skewX" in SESSION assert "Math.tan((geometry.skewY" in SESSION def test_multitransform_geometry_carries_skew(): assert "target.skewX || 0" in MULTI assert "target.skewY || 0" in MULTI assert "skewPadX" in MULTI assert "skewPadY" in MULTI assert "skewX," in MULTI assert "skewY," in MULTI def test_geometry_applies_local_shear(): assert "export function skewTransformFrame(frame, handle, startPoint, currentPoint, options = {}) {" in GEO assert "const shearX = Math.tan(skewX * Math.PI / 180);" in GEO assert "const shearY = Math.tan(skewY * Math.PI / 180);" in GEO assert "if (ySign) skewX = clamp(startSkewX" in GEO assert "if (xSign) skewY = clamp(startSkewY" in GEO def test_ctrl_drag_skews_the_grabbed_handle(): assert "import { resizeTransformFrame, skewTransformFrame, distortTransformFrame } from '../transform-frame-geometry.js';" in DRAG assert "state.transformStartSkewX = state.transformPendingSkewX || 0;" in DRAG assert "state.transformStartSkewY = state.transformPendingSkewY || 0;" in DRAG assert "if (e.ctrlKey || e.metaKey) {" in DRAG assert "skewTransformFrame({" in DRAG assert "state.transformPendingSkewX = skewed.skewX;" in DRAG assert "state.transformPendingSkewY = skewed.skewY;" in DRAG def test_handles_thread_skew_and_grow_the_overlay_past_the_image(): # transformFrame() must feed skew into the shared geometry... assert "skewX: state.transformPendingSkewX || 0," in HANDLES assert "skewY: state.transformPendingSkewY || 0," in HANDLES # ...and the overlay must be sized to the frame, not just the image. assert "export function syncOverlay(margin, extents = null) {" in HANDLES assert "const cover = syncOverlay(margin, frameExtents(geometry));" in HANDLES assert "function frameExtents(geometry) {" in HANDLES assert "ctx.translate(-cover.left, -cover.top);" in HANDLES assert "const MAX_BITMAP = 8192;" in HANDLES def test_transform_controls_live_in_the_header_strip(): css = read("static/css/13-image-editor.css") controls = read("static/js/editor/build/controls.js") assert 'class="ge-transform-section" id="ge-transform-section" style="display:none;"' in controls assert "const pop = document.getElementById('ge-transform-section');" in SESSION assert "pop.innerHTML = `