feat: menu + — Design System – Canevas — phase 3/8 (v7.53.0)
- Section « Design System – Canevas » : liste des canevas (intégrés +
personnels, GET /board/api/page-templates) puis page de détail avec
« Créer une page à partir du canevas », « Insérer dans le document ouvert »
(désactivé sans document ouvert) et « Enregistrer le document ouvert comme
canevas ».
- GET /board/api/page-templates/{id}/blocks (nouvelle route) : blocs d'un
canevas — intégré via template_id=0&key=, sinon id ; 404 clé/id inconnu.
Insertion côté front via ensureBlockIds() global + concat dans E.blocks
+ autoSave/render.
- Canevas « Design System » ajouté aux builtins (callout tokens, TOC, toggles
composants, grille, checklist revue UI, citation).
- Fix : le front du panneau appelait /api/page-templates (404 silencieux) —
le router est sous prefix=/board → /board/api/page-templates.
- Piège : pour /use la clé builtin se passe DANS LE BODY (body.key), pas en
query string — test garde-fou ajouté.
- Tests : tests/test_v753_canvases.py (5) + test_v751 adapté. Suite complète
1285 verts (-n auto), ruff 0, eslint 0 problème. OpenAPI 516 chemins.
This commit is contained in:
@@ -104,8 +104,8 @@ def test_plus_menu_sections_and_disabled_states():
|
||||
for key in ("'files'", "'skills'", "'connectors'", "'canvases'", "'plugins'", "'memory'"):
|
||||
assert f"key:{key}" in root, key
|
||||
assert "action:'files'" in root and "action:'skills'" in root
|
||||
# phases 1 + 2 livrées → il reste 4 sections « bientôt »
|
||||
assert root.count("disabled:true") == 4
|
||||
# phases 1-3 livrées → il reste 3 sections « bientôt » (connecteurs, plugins, mémoire)
|
||||
assert root.count("disabled:true") == 3
|
||||
|
||||
files = src.split("var FD_PLUS_FILES = [", 1)[1].split("];", 1)[0]
|
||||
assert "action:'search'" in files and "action:'browse'" in files
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"""v7.53.0 — Menu + : Design System – Canevas (créer / insérer / enregistrer)."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from app.db import get_conn
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
JS_PATH = ROOT / "static" / "js" / "agent_panel_2.js"
|
||||
|
||||
|
||||
def test_canvases_list_includes_design_system(client):
|
||||
tpl = {t["key"]: t for t in client.get("/board/api/page-templates").json()["templates"] if t.get("builtin")}
|
||||
assert "design_system" in tpl
|
||||
assert tpl["design_system"]["name"] == "Design System"
|
||||
assert tpl["design_system"]["icon"] == "🎨"
|
||||
|
||||
|
||||
def test_blocks_route_builtin(client):
|
||||
r = client.get("/board/api/page-templates/0/blocks", params={"key": "design_system"})
|
||||
assert r.status_code == 200, r.text
|
||||
blocks = r.json()["blocks"]
|
||||
assert isinstance(blocks, list) and blocks
|
||||
assert blocks[0]["type"] == "heading_1" and blocks[0]["content"] == "Design System"
|
||||
# les builtins sont livrés sans id : le front appelle ensureBlockIds()
|
||||
assert "id" not in blocks[0]
|
||||
|
||||
assert client.get("/board/api/page-templates/0/blocks", params={"key": "nope"}).status_code == 404
|
||||
empty = client.get("/board/api/page-templates/0/blocks", params={"key": "empty"})
|
||||
assert empty.status_code == 200 and empty.json()["blocks"]
|
||||
|
||||
|
||||
def test_blocks_route_user_template(client):
|
||||
blocks = [{"type": "heading_1", "content": "Mon canevas"},
|
||||
{"type": "paragraph", "content": "Contenu."}]
|
||||
created = client.post("/board/api/page-templates", json={"name": "Mon canevas", "blocks": blocks})
|
||||
assert created.status_code == 200, created.text
|
||||
tid = created.json()["id"]
|
||||
|
||||
got = client.get(f"/board/api/page-templates/{tid}/blocks")
|
||||
assert got.status_code == 200
|
||||
assert [b["content"] for b in got.json()["blocks"]] == ["Mon canevas", "Contenu."]
|
||||
|
||||
assert client.get("/board/api/page-templates/999999/blocks").status_code == 404
|
||||
|
||||
|
||||
def test_use_design_system_creates_page(client):
|
||||
r = client.post("/board/api/page-templates/0/use", json={"key": "design_system"})
|
||||
assert r.status_code == 200, r.text
|
||||
page_id = r.json()["id"]
|
||||
with get_conn() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT content, content_format FROM pages WHERE id=?", (page_id,)
|
||||
).fetchone()
|
||||
assert row["content_format"] == "blocks"
|
||||
blocks = json.loads(row["content"])
|
||||
texts = [b.get("content", "") for b in blocks]
|
||||
assert "Design System" in texts
|
||||
# les ids sont assignés côté serveur (_ensure_block_ids)
|
||||
assert all(b.get("id") for b in blocks)
|
||||
|
||||
|
||||
def test_canvases_menu_wired(client):
|
||||
src = JS_PATH.read_text(encoding="utf-8")
|
||||
root = src.split("var FD_PLUS_MENU = [", 1)[1].split("];", 1)[0]
|
||||
assert "action:'canvases'" in root
|
||||
# phases 1-3 livrées → 3 sections « bientôt » restantes (connecteurs, plugins, mémoire)
|
||||
assert root.count("disabled:true") == 3
|
||||
for action in ("'canvas'", "'canvas-create'", "'canvas-insert'", "'canvas-save'"):
|
||||
assert action in src, action
|
||||
# piège /use : la clé builtin part DANS LE BODY, pas en query string
|
||||
assert "body: JSON.stringify(c.builtin ? {key: c.key} : {})" in src
|
||||
assert "ensureBlockIds" in src
|
||||
Reference in New Issue
Block a user