"""7.61.0 — menu contextuel de bloc refondu façon Notion (actions + raccourcis).""" 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") CSS = (ROOT / "static" / "css" / "app.css").read_text(encoding="utf-8") def _slice(start: str, end: str) -> str: i = SRC.index(start) j = SRC.index(end, i) return SRC[i:j].rstrip() # ── Panneau de gauche ─────────────────────────────────────────────────────── def test_left_panel_matches_spec(): # recherche, groupe « Text », les 10 actions dans l'ordre, pied métadonnées assert 'placeholder="Search actions..."' in SRC assert "'
Text
'" in SRC order = ["'↩','Turn into'", "'🎨','Color'", "'🔗','Copy link to block'", "'▣','Duplicate'", "'↪','Move to…'", "'✕','Delete'", "'💬','Comment'", "'✎','Suggest edits'", "'✨','Ask AI'", "'📚','Skills'"] chunk = SRC[src_index("d.innerHTML=' int: return SRC.index(needle) def test_actions_are_all_dispatched(): dispatch = _slice("function _blockMenuClick(", "\n function _blockMenuItem(") for act in ("submenu", "duplicate", "copy-link", "move-to", "delete", "comment", "suggest", "ai", "skill", "color", "turn-type"): assert f"act==='{act}'" in dispatch, act # méthodes réelles côté éditeur # v7.62 : « askAI(idx) » renommé askAIBlock(idx) — « askAI() » l'écrasait for m in ("commentBlock(idx)", "askAIBlock(idx)", "suggestEdit(idx)", "runSkill(idx,tpl)", "applyLastColor(idx)", "copyBlockLink(bid)", "deleteSelected(idx)", "duplicateBlock("): assert m in SRC, m # menu : pas d'entrée « copier/couper/coller les blocs » (raccourcis conservés) assert "act==='copy-blocks'" not in dispatch # ── Sous-menus ────────────────────────────────────────────────────────────── def test_turn_into_submenu(): # v7.63 : le générateur d'items a été extrait (_blockTurnItems) pour être # réutilisé par la feuille mobile — le test vise le générateur. turn = _slice("function _blockTurnItems(", "\n function _blockColorItems(") assert "_BLOCK_TURN_MENU" in turn and "check:on" in turn # ✓ du type actuel assert "'✓'" in SRC for name in ("Text", "Heading 1", "Heading 4", "Bulleted list", "Numbered list", "To-do list", "Toggle list", "Code", "Quote", "Callout", "Block equation", "Synced block", "2 columns", "5 columns"): assert f"'{name}'" in SRC, name assert "Create '+t.cols+' columns of blocks" in SRC # info-bulle assert "data-cols" in SRC and "cols>=2" in SRC # 2..5 colonnes # Page / Page in / Toggle heading : types inexistants dans FlowDeck → absents assert "'Page in'" not in SRC and "'Toggle heading 1'" not in SRC def test_color_submenu(): color = _slice("function _blockColorItems(", "\n function _blockSkillsMenuOpen(") for group in ("Last used", "Text color", "Background color"): assert f">{group}" in color, group names = ["'Default'", "'Gray'", "'Brown'", "'Orange'", "'Yellow'", "'Green'", "'Blue'", "'Purple'", "'Pink'", "'Red'"] arr = _slice("const _BLOCK_COLOR_NAMES=", "];") for n in names: assert n in arr, n assert "bm-dot" in color # carré de couleur (fond) assert "_BM_SHORTCUTS.color" in color # raccourci « Last used » assert "fdLastBlockColor" in SRC # mémoire de la dernière couleur def test_skills_submenu_uses_real_skills(): skills = _slice("function _blockSkillsMenuOpen(", "\n function _rtMac(") assert "fetch('/api/agent/skills'" in skills assert "act==='skill'" in SRC or "'skill'" in skills assert "prompt_template" in skills assert "_aiWritingRun('write',idx" in SRC # exécution réelle via l'IA # ── Survol / fermeture ────────────────────────────────────────────────────── def test_hover_submenus_and_close(): for token in ("_blockSubOpen(kind,bid,anchor)", "data-sub", "closest('[data-sub]')", "mouseleave", "_blockSubCloseLater", "function _blockMenuClose()", "_blockDocDown"): assert token in SRC, token assert "si.focus()" in SRC # recherche focalisée à l'ouverture # ── Raccourcis ────────────────────────────────────────────────────────────── def test_shortcuts_implemented(): keys = _slice("// ── v7.61 : raccourcis du menu contextuel de bloc ──", "document.addEventListener('keydown', function (e) {\n if (!(e.ctrlKey || e.metaKey)") combos = { "k === 'j'": "Ctrl+J (Ask AI)", "k === 'h'": "Ctrl+⇧+H (dernière couleur)", "k === 'm'": "Ctrl+⇧+M (commenter)", "k === 'x'": "Ctrl+⇧+Alt+X (suggest edits)", "k === 'l'": "Alt+⇧+L (copier le lien)", "k === 'p'": "Ctrl+⇧+P (déplacer vers…)", "k === 'd'": "Ctrl+D (dupliquer)", "k === 'delete'": "Del (supprimer, menu ouvert uniquement)", "k === 'escape'": "Échap (fermer)", } for token, label in combos.items(): assert token in keys, label # Del n'agit jamais pendant la saisie assert "tgt.isContentEditable" in keys # le libellé affiché dans le menu correspond aux combinaisons réelles shortcuts = _slice("const _BM_SHORTCUTS=", ";") for hint in ("Alt+⇧+L", "Ctrl+D", "Ctrl+⇧+P", "Del", "Ctrl+⇧+M", "Ctrl+⇧+Alt+X", "Ctrl+J", "Ctrl+⇧+H"): assert hint in shortcuts, hint # ── Comportement réel : conversion en colonnes (node) ─────────────────────── def test_turn_into_columns_creates_children_for_real(): node = shutil.which("node") if not node: pytest.skip("node absent : test comportemental ignoré") types = _slice("const _BLOCK_TURN_TYPES=[", "];") method = _slice("turnInto(idx,type", "setBlockColor(idx,key,val){") script = f""" function _rtSync(){{}} function _selClear(){{}} var _n=0; function genId(){{return 'g'+(++_n);}} {types}]; const E = {{ blocks:[{{id:'b1',type:'paragraph',content:'Titre'}}], _blockTargets(i){{return [i];}}, sync(){{}}, pushHistory(){{}}, dirty:false, autoSave(){{}}, render(){{}}, getEl(){{return null;}}, _pickSyncedBlock(){{}}, {method} }}; E.turnInto(0,'columns',3); console.log(JSON.stringify({{type:E.blocks[0].type, kids:E.blocks[0].children.length}})); """ out = subprocess.run([node, "-e", script], capture_output=True, text=True, timeout=30, encoding="utf-8") assert out.returncode == 0, out.stderr assert json.loads(out.stdout) == {"type": "columns", "kids": 3} # ── Styles ────────────────────────────────────────────────────────────────── def test_menu_styles(): for sel in (".block-menu .bm-group {", ".block-menu .bm-foot {", ".block-menu .bm-check {", ".block-menu .bm-dot {", ".block-menu-sub { min-width: 230px; max-height: 60vh;"): assert sel in CSS, sel