"""7.62.0 — menu contextuel au highlight de texte (toolbar façon Notion).""" import json import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parent.parent SRC = (ROOT / "static" / "js" / "page_editor_scripts.js").read_text(encoding="utf-8") TPL = (ROOT / "app" / "templates" / "_page_editor_content.html").read_text(encoding="utf-8") CSS = (ROOT / "static" / "css" / "app.css").read_text(encoding="utf-8") APPJS = (ROOT / "static" / "js" / "app.js").read_text(encoding="utf-8") def _slice(src: str, start: str, end: str) -> str: i = src.index(start) return src[i:src.index(end, i)] # ── Markup : les 4 lignes + Skills + pied, dans l'ordre de la description ─── def test_toolbar_markup_matches_spec(): chunk = _slice(TPL, 'class="format-toolbar"', "Copy Link Toast") order = [ "fmtStyle($event)", # ligne 1 : Normal Text › "fmtApply('bold')", "fmtApply('italic')", "fmtApply('underline')", "fmtApply('strikeThrough')", "fmtCode()", # ligne 2 : A B I U T Tx "fmtLink()", "fmtHighlight()", "fmtCodeBlock()", "fmtEquation()", "fmtMore($event)", # ligne 3 : 🔗 S √x … "fmtComment()", "fmtAskAI()", # ligne 4 : 💬 ✨ "ft-group", "Improve writing", "Proofread", "Explain", "Reformat", "Edit with AI", "Alt+⇧+E", # pied de page ] positions = [chunk.index(o) for o in order] assert positions == sorted(positions), positions # ligne 1 dynamique (type du bloc) + chevron de sous-menu assert "fmtStyleName()" in chunk and "ft-caret" in chunk # info-bulle HTML (exemple en italique + description) assert "data-tooltip-html=" in chunk # icônes réellement définies dans le jeu d'icônes assert 'fd_icon("message-square",15)' in chunk assert 'fd_icon("sparkles",15)' in chunk and 'fd_icon("link",15)' in chunk # ── Actions : câblées vers de vraies méthodes, sous-menus réutilisés ──────── def test_actions_are_wired_to_real_methods(): for m in ("fmtStyle(ev)", "fmtColor(ev)", "fmtApply(f)", "fmtCode()", "fmtHighlight()", "fmtCodeBlock()", "fmtLink()", "fmtEquation()", "fmtMore(ev)", "fmtComment()", "fmtAskAI()", "fmtSkill(tpl)", "fmtEditAI()", "askAIBlock(idx)"): assert m in SRC, m # pas de doublon de menu : on rouvre les sous-menus/menus déjà existants assert "_blockTurnMenuOpen(p.x,p.y,bid)" in SRC assert "_blockColorMenuOpen(p.x,p.y,bid)" in SRC assert "blockMenuOpen(" in SRC # sous-menu ouvert depuis la toolbar : fermeture au survol sortant assert "function _subHoverClose()" in SRC # Ctrl+J et Alt+⇧+E ouvrent l'IA du BLOC (l'ancien askAI(idx) était écrasé # par askAI() défini plus bas dans le même objet) assert "E.askAIBlock(i1)" in SRC assert "k === 'e'" in SRC assert "askAI(idx)" not in SRC # ── Persistance du formatage : sync() relit le DOM formaté en markdown ───── def test_formatting_survives_sync(): assert "function gtMd(" in SRC assert "gtMd(el)" in SRC and "gtMd(che)" in SRC # sync() + enfants toggle/columns for tok in ("'**'+inner+'**'", "'*'+inner+'*'", "'__'+inner+'__'", "'~~'+inner+'~~'", "'`'+inner+'`'", "'=='+inner+'=='"): assert tok in SRC, tok # mdEsc rend les nouveaux marqueurs (souligné, surlignage, équation inline) for re_ in ("/__([^_]+)__/g", "/==([^=]+)==/g", "/\\$\\$([^$]+)\\$\\$/g"): assert re_ in SRC, re_ # les chips wiki gardent leur token brut assert "fd-wiki-chip" in SRC # ── Placement & fermeture ────────────────────────────────────────────────── def test_menu_position_and_close(): assert "r.bottom+8" in SRC # sous la sélection (bas de l'écran : au-dessus) assert "d.fmt.open=false;d._sel=null" in SRC # clic ailleurs → fermeture assert "closest('.format-toolbar')" in SRC # clic dans la toolbar ≠ clic ailleurs assert "@mousedown.prevent" in TPL # la sélection survit au clic # ── Styles ──────────────────────────────────────────────────────────────── def test_menu_styles(): for sel in (".format-toolbar .ft-row {", ".format-toolbar .ft-item {", ".format-toolbar .ft-style {", ".format-toolbar .ft-group {", ".format-toolbar .ft-sep {", ".format-toolbar .ft-foot {", ".tooltip.tooltip-light {"): assert sel in CSS, sel # l'info-bulle claire s'applique aux éléments du menu de sélection assert "data-tooltip-html" in APPJS and "tooltip-light" in APPJS # ── Comportement réel : gtMd (DOM → source markdown) + mdEsc, sous node ──── def test_gt_md_and_md_esc_roundtrip_for_real(): node = shutil.which("node") if not node: pytest.skip("node absent : test comportemental ignoré") gtmd = _slice(SRC, "function gtMd(", " // Enveloppe/désenveloppe") mdesc = _slice(SRC, "function mdEsc(", " // v5.11.0: labels of [[fdpage:ID]]") script = f""" var window = {{}}; // gtMd fait window.gtMd=… function gtTok(e){{ return e.__raw || ''; }} function fdDateLabel(iso){{ return iso; }} {gtmd} {mdesc} // DOM minimal (gtMd ne lit que childNodes / tagName / classList / attributs) function T(s){{ return {{nodeType:3, nodeValue:s}}; }} function E(tag, attrs, kids){{ attrs = attrs || {{}}; const cls = String(attrs['class'] || '').split(/\\s+/); return {{nodeType:1, tagName:tag, childNodes:kids || [], classList:{{contains:c=>cls.indexOf(c) >= 0}}, getAttribute:k=>(k in attrs ? attrs[k] : null), hasAttribute:k=>(k in attrs)}}; }} // querySelector truthy → chemin « formaté » de gtMd (gtTok jamais appelé) const root = E('DIV', {{'class':''}}, [ E('B', {{}}, [T('gras')]), E('EM', {{}}, [T('italique')]), E('U', {{}}, [T('souligne')]), E('S', {{}}, [T('barre')]), E('CODE', {{}}, [T('code')]), E('MARK', {{}}, [T('surligne')]), E('A', {{'href':'https://example.com/x'}}, [T('lien')]), E('SPAN', {{'class':'fd-wiki-chip', 'data-token':'[[fdpage:42]]'}}), E('SPAN', {{'class':'fd-math-inline', 'data-tex':'x^2'}}), T(' fin') ]); root.querySelector = () => ({{}}); // force le chemin formaté const md = gtMd(root); const html = mdEsc(md); console.log(JSON.stringify({{md:md, html:html}})); """ out = subprocess.run([node, "-e", script], capture_output=True, text=True, timeout=30, encoding="utf-8") assert out.returncode == 0, out.stderr data = json.loads(out.stdout) assert data["md"] == ("**gras***italique*__souligne__~~barre~~`code`==surligne==" "[lien](https://example.com/x)[[fdpage:42]]$$x^2$$ fin"), data["md"] for token in ("gras", "souligne", "surligne", "code", 'href="https://example.com/x"', 'class="fd-wiki-chip"', 'data-tex="x^2"'): assert token in data["html"], token