import re from pathlib import Path REPO = Path(__file__).resolve().parents[1] def _read(rel): return (REPO / rel).read_text(encoding="utf-8") def test_self_hosted_font_faces_are_declared(): css = _read("static/css/00-tokens.css") for family in ["Inter", "Roboto", "Playfair Display", "Lobster", "Bebas Neue", "Roboto Mono", "Space Grotesk", "Fraunces", "Syne", "Bungee", "Instrument Serif"]: assert f"font-family: '{family}'" in css, family for fname in [ "Inter-Regular.woff2", "Roboto-Regular.woff2", "PlayfairDisplay-Regular.woff2", "Lobster-Regular.woff2", "BebasNeue-Regular.woff2", "RobotoMono-Regular.woff2", "SpaceGrotesk-Regular.woff2", "Fraunces-Regular.woff2", "Syne-Regular.woff2", "Bungee-Regular.woff2", "InstrumentSerif-Regular.woff2", ]: assert f"/static/fonts/{fname}" in css, fname def test_font_files_exist_on_disk(): for fname in [ "Roboto-Regular.woff2", "Roboto-Bold.woff2", "PlayfairDisplay-Regular.woff2", "PlayfairDisplay-Bold.woff2", "Lobster-Regular.woff2", "BebasNeue-Regular.woff2", "RobotoMono-Regular.woff2", "RobotoMono-Bold.woff2", "SpaceGrotesk-Regular.woff2", "SpaceGrotesk-Bold.woff2", "Fraunces-Regular.woff2", "Fraunces-Bold.woff2", "Syne-Regular.woff2", "Syne-Bold.woff2", "Bungee-Regular.woff2", "InstrumentSerif-Regular.woff2", ]: assert (REPO / "static" / "fonts" / fname).is_file(), fname def test_text_layer_exposes_font_helpers(): js = _read("static/js/editor/text-layer.js") assert "export const EDITOR_TEXT_FONTS" in js assert "'Playfair Display'" in js and "'Bebas Neue'" in js and "'Roboto Mono'" in js assert "export function loadEditorFonts()" in js assert "export function availableEditorFonts()" in js assert "document.fonts.load(" in js assert "document.fonts.check(" in js def test_options_bar_font_select_lists_webfonts(): js = _read("static/js/editor/build/controls.js") for family in ["Inter", "Roboto", "Playfair Display", "Lobster", "Bebas Neue", "Roboto Mono", "Space Grotesk", "Fraunces", "Syne", "Bungee", "Instrument Serif"]: assert family in js, family def test_wire_text_controls_loads_and_prunes_fonts(): js = _read("static/js/galleryEditor.js") assert "_loadEditorFonts" in js assert "_availableEditorFonts" in js assert "Array.from(_fontSel.options).forEach" in js def test_sw_version_is_current(): sw = _read("static/sw.js") m = re.search(r"CACHE_NAME = '([^']+)'", sw) assert m and re.fullmatch(r"odysseus-v\d+-pr40-editor-precache", m.group(1)), m def test_text_box_width_survives_the_position_loop(): js = _read("static/js/editor/text-edit-overlay.js") # The per-frame position() used to rewrite the width unconditionally, which # stomped the native resize handle before the ResizeObserver saw it. assert "const targetW = Math.max(80, text.frameWidth * scaleX);" in js assert "if (!resizing && Math.abs(targetW - appliedWidth) > 0.5) {" in js assert "input.style.minWidth = '40px';" in js assert "let appliedWidth = 0;" in js assert "appliedWidth = 0;" in js def test_text_box_has_a_visible_corner_resize_handle(): js = _read("static/js/editor/text-edit-overlay.js") css = _read("static/css/13-image-editor.css") # The native textarea grip is invisible and tiny in this theme, so the # editor exposes its own 14px grab square that drives the frame width. assert "handle.className = 'ge-direct-text-handle';" in js assert "handle.addEventListener('pointerdown'" in js assert "handle.addEventListener('pointermove'" in js assert "input.style.width = `${w}px`;" in js assert "onInput?.(layer, input.value, input, w / Math.max(0.01, dragStart.scale));" in js assert "handle.innerHTML = '