Files
flowdeck/tests/test_v768_columns_clipboard.py
T
bruno 3d3b46b58b
FlowDeck CI / lint (push) Successful in 1m34s
FlowDeck CI / test (push) Failing after 17m31s
FlowDeck CI / docker (push) Skipped
fix: sélection multi-blocs fonctionnelle (Ctrl+C/X, drag souris, clic droit) + identité visuelle (v7.69.0)
Trois bugs d'éditeur, chacun avec sa cause racine prouvée par trace
d'événements — le mécanisme Clipboard livré en v7.68 était inutilisable.

1) Ctrl+C / Ctrl+X multi-blocs ne faisaient RIEN.
   Cause : le navigateur ne sait pas sérialiser une sélection qui traverse
   deux `contenteditable` (chaque bloc en a un). `getSelection().toString()`
   renvoie `""` et Chromium n'émet AUCUN événement `copy` — les handlers
   `ct.addEventListener('copy')` n'étaient donc jamais atteints.
   → le raccourci est intercepté au keydown (capture sur document, garde
   `closest('#_blocksCt')` pour ne pas voler Ctrl+C ailleurs) et le markdown
   est écrit dans le presse-papier par nos soins. Sélection mono-bloc =
   comportement natif inchangé.

2) Glisser la souris d'un bloc à l'autre ne sélectionnait qu'un bloc.
   Cause : `mouseup` (on reconstruit la plage) → `click` → `focusin` → le
   navigateur efface la sélection. La plage était reconstruite puis détruite
   dans la foulée.
   → la plage reconstruite est mise en attente (`_mdSelRange`) et restaurée
   au tour de boucle suivant si le focus l'a effectivement effacée. Chaque
   nouveau geste (mousedown) remet l'attente à zéro, donc un clic pour
   placer le curseur reste normal.

3) Le clic droit effaçait le surlignage.
   Cause : `.bm-search` prenait le focus 10 ms après l'ouverture du menu.
   → le menu ne vole plus le focus quand un texte est sélectionné. Les
   actions Clipboard utilisaient déjà la sélection figée à l'ouverture
   (`d._clipSt`) ; le `preventDefault` du clic droit est remonté sur
   `mousedown` (sur `contextmenu` il arrive trop tard).

Identité visuelle (bannière + logo du projet ajoutés à la racine) :
- assets optimisés servis : `static/img/banner.webp` 22,5 Ko (l'original
  fait 1,6 Mo) + JPEG de secours, `logo.webp` 5,3 Ko + `logo-512.jpg` ;
  originaux conservés dans `assets/` pour régénération.
- icônes PWA régénérées à partir du vrai logo (72 → 512 px, marge
  maskable sur 192/512) + apple-touch-icon.
- landing : logo dans la barre de navigation, bannière en <picture>
  (WebP + JPEG) avec dimensions déclarées (pas de décalage de mise en page).
- `og:image`, `twitter:card` et `meta description` ajoutés sur la landing
  ET dans base.html — la landing a son propre <head>.

Tests : `tests/test_v769_selection_persistence.py` (19 statiques) +
`e2e/v769_selection_persistence.spec.js` (5, vrais gestes : drag souris,
Ctrl+C/Ctrl+X au clavier, clic droit). `e2e/v768_columns_clipboard.spec.js`
corrigé : les gestes simulés (`execCommand`, plage fabriquée en evaluate)
sont remplacés par de vrais gestes, et le token statique obsolète
`si>=ei)return null;` aligné sur la garde `isCollapsed` réelle.

pytest 1413 passed / 0 failed · ruff OK · OpenAPI 526 chemins / 7.69.0
2026-10-08 17:27:14 -04:00

67 lines
3.2 KiB
Python

"""7.68.0 — colonnes/math/TOC rendus + copier-couper-coller multi-blocs."""
from pathlib import Path
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 _fn(name: str, span: int = 1500) -> str:
i = SRC.index(name + "(")
return SRC[i:i + span]
def test_renderers_are_actually_dispatched():
# les trois renderers existaient mais n'étaient JAMAIS appelés →
# colonnes (slash + Turn into), équation et TOC rendaient un paragraphe vide
dispatch = _fn("renderBlock")
for call in ("renderColumnsBlock(b)", "renderMathBlock(b)", "renderTOC(b)"):
assert call in dispatch, call
assert SRC.count("function " + call.split("(")[0] + "(") == 1
# la suppression d'une colonne n'est plus un bouton invisible
assert ".block-column-wrapper:hover .block-actions {" in CSS
def test_gfm_tables_without_leading_pipe():
# l'IA rend souvent « A | B » / « --- | --- » — le pipe de tête est optionnel
assert "s.indexOf('|')>=0&&_tblSplitLine(s).length>=2" in SRC
# une ligne de séparation doit rester ≥2 cellules (sinon « texte » + « --- »)
assert "if(cells.length<2)return false;return cells.every" in SRC
# md2b ET paste2b passent par les mêmes helpers (alignés)
assert SRC.count("_tblIsSep(lines[i+1])") == 2
def test_clipboard_multi_block_selection():
# état : sélection vivante traversant ≥2 blocs ; sinon null (natif)
for tok in ("_clipState(){", "!s||!s.rangeCount||s.isCollapsed)return null;",
"_srcOffset(sblk,r.startContainer,r.startOffset)",
"_srcOffset(eblk,r.endContainer,r.endOffset)"):
assert tok in SRC, tok
# markdown : début partiel → blocs entiers → fin partielle
assert "_clipMarkdown(st){" in SRC
assert "blocksToMarkdown(b)" in SRC
# coupure : premier/trois restent (têtes/queues), milieu supprimé
assert "this.blocks.slice(0,st.si+1).concat([last],this.blocks.slice(st.ei+1))" in SRC
# copy/cut restent branchés (menu contextuel, execCommand)…
assert "ct.addEventListener('copy'," in SRC
assert "ct.addEventListener('cut'," in SRC
assert "ev.clipboardData.setData('text/plain',md)" in SRC
# …mais le VRAI raccourci clavier est intercepté au keydown : le navigateur
# n'émet aucun événement copy pour une sélection à travers deux
# contenteditable (v7.69).
assert "document.addEventListener('keydown'," in SRC
assert "if(k!=='c'&&k!=='x')return;" in SRC
# menu clic droit : groupe Clipboard Cut/Copy/Paste
assert "'<div class=\"bm-group\">Clipboard</div>'" in SRC
for act in ("clip-cut", "clip-copy", "clip-paste"):
assert f"data-bm-act=\"{act}\"" in SRC or f"'{act}'" in SRC, act
# « Copy link to block » : l'act du menu doit être celle du handler
assert "_blockMenuItem('copy-link'," in SRC
assert "act==='copy-link'" in SRC
def test_paste_single_structured_block_is_inserted():
# un seul bloc structuré collé (table, code, titre) ne retombe plus en texte brut
assert "if(parsed.length===1&&parsed[0].type==='paragraph')return fallback();" in SRC