"""Overnight editor-polish batch contracts. Covers the UI/UX fixes applied in one pass: Brightness/Contrast presets, clearer selection-mode icons, marquee action-row icons, the crop-apply dismiss button, unified dock/layer icon sizing, the footer zoom glyph nudge, the histogram repaint hook, the bucket dodge/burn fixes. """ from pathlib import Path import re ROOT = Path(__file__).resolve().parents[1] def _read(rel): return (ROOT / rel).read_text(encoding="utf-8") CSS = _read("static/css/13-image-editor.css") LH = _read("static/js/editor/layer-helpers.js") OPTIONS = _read("static/js/editor/build/options-bar.js") CONTROLS = _read("static/js/editor/build/controls.js") EDITOR = _read("static/js/galleryEditor.js") PANELS = _read("static/js/editor/panels/panels.js") STROKE = _read("static/js/editor/stroke-pipeline.js") BUCKET = _read("static/js/editor/tools/bucket.js") SW = _read("static/sw.js") def test_brightness_contrast_has_extra_presets(): m = re.search(r"'brightness-contrast': \[([^\]]*)\]", LH) assert m, "brightness-contrast preset list missing" opts = m.group(1) for name in ("Brighten", "Darken", "Punch", "High Contrast", "Low Contrast", "Faded"): assert name in opts, name for case in ( "brightness-contrast:Brighten", "brightness-contrast:Darken", "brightness-contrast:Punch", "brightness-contrast:Low Contrast", "brightness-contrast:Faded", ): assert f"case '{case}':" in LH, case def test_intersect_mode_uses_overlap_icon(): assert "const INTERSECT_ICON =" in OPTIONS assert "['intersect', INTERSECT_ICON]" in OPTIONS icon = '' assert icon in OPTIONS # every wand/selection intersect button in the controls markup uses it assert CONTROLS.count(icon) >= 6 assert 'data-wand-mode="intersect"' in CONTROLS def test_marquee_action_row_has_svg_icons(): assert 'class="ge-btn ge-btn-sm" id="ge-marquee-clear"' in CONTROLS assert 'aria-label="Clear selection"' in CONTROLS for gid in ("ge-marquee-invert", "ge-marquee-delete", "ge-marquee-copy", "ge-marquee-mask"): assert f'id="{gid}"' in CONTROLS, gid def test_crop_apply_has_corner_dismiss(): assert 'class="ge-crop-close"' in EDITOR # The × clears the committed crop via the tool reset (cancel() is a no-op # once the gesture has ended). assert "_cropTool.reset()" in EDITOR assert ".ge-crop-close {" in CSS assert "border-radius: 50%;" in CSS[CSS.index(".ge-crop-close {"):CSS.index(".ge-crop-close {") + 320] def test_crop_dismiss_x_is_nudged_up(): # × glyph up 4px inside the round button; the button itself up 5px. assert 'class="ge-crop-close-x"' in EDITOR assert ".ge-crop-close-x { display: block; line-height: 1; transform: translateY(-2px); }" in CSS idx = CSS.index(".ge-crop-close {") assert "top: -13px;" in CSS[idx:idx + 120] def test_gradient_swatch_applies_the_gradient(): # Clicking a preset selects the gradient tool and paints it across the layer. assert "document.querySelector('[data-tool=\"gradient\"]')?.click();" in PANELS assert "applyGradient?.();" in PANELS assert "applyGradient: () => _applyFullGradient()," in EDITOR assert "function _applyFullGradient()" in EDITOR assert "_gradientTool.begin(from)" in EDITOR def test_dock_and_layer_icon_sizes_unified(): idx = CSS.index(".ge-layer-btn svg,") block = CSS[idx:idx + 360] assert ".ge-layer-group-toggle svg {" in block assert "width: 14px;" in block assert "height: 14px;" in block def test_adj_row_label_is_right_aligned(): idx = CSS.index(".ge-adj-row > label {") block = CSS[idx:idx + 160] assert "text-align: right;" in block def test_footer_zoom_glyph_nudged_down(): assert ".ge-footer-zoom .ge-zoom-glyph { left: 0; top: 4px; }" in CSS def test_histogram_picks_a_paintable_layer(): assert "const histogramLayer = ()" in PANELS assert "drawHistogram(histCanvas, layer)" in PANELS def test_dodge_and_burn_cap_per_dab_strength(): # Dodge/burn strength is now driven by the Intensity slider (default 25%, # scaled to 0.5 so even 100% builds gradually) instead of a fixed clamp. assert STROKE.count("const intensity = Number.isFinite(state.dodgeBurnIntensity)") >= 2 assert STROKE.count("alpha = Math.max(0.004, Math.min(0.6, alpha * intensity * 0.5));") >= 2 assert "operation = 'screen';" in STROKE assert "operation = 'multiply';" in STROKE def test_bucket_falls_back_to_a_paintable_layer(): assert "function paintableLayer()" in BUCKET assert "'Select a pixel layer to fill'" in BUCKET def test_sw_cache_name_matches(): assert re.search(r"CACHE_NAME = 'odysseus-v\d+-pr40-editor-precache'", SW)