Templates (v7.71.x) : - registre unifié \ emplates\ (migrations 48-49) + TemplateService.instantiate unique (UI, API v2, agent, scheduler) - sélecteur (pilule page vide, menu •••, commande /template), gestionnaire /templates, menu New ▾, From template, base inline dans un document - 141 presets système (59 pages, 42 bases, 15 blocs, 25 lignes), titre auto depuis le template, variables title réservée - récurrences RRULE + scheduler dédupliqué, agent apply_template/list_templates, API /api/templates + /api/v2/fd-templates - correctifs : bouton Templates, centrage fenêtre, filtres CSP, flux de création, variable title - tests : tests/test_fd_templates.py (19) et e2e/templates_picker.spec.js (8) Inclut le travail déjà présent dans le working tree (vues Notion : view_query/view_aggregate/form_projection/geocoding, property_types, database_table, docs agents-skills) et ignore .playwright-mcp/.
104 lines
4.2 KiB
Python
104 lines
4.2 KiB
Python
"""FlowDeck v7.70.0 — Ouverture d'un projet Gitea : présentation + miroir local.
|
|
|
|
Régression couverte :
|
|
- ``GET /gitea-workspace?owner=&repo=`` crée le **workspace miroir local**
|
|
AVANT de calculer la sidebar : le premier affichage du projet ne montre plus
|
|
« No workspace open » (ou un ``localWsId: 0``) — équivalent local ouvert en
|
|
parallèle du dépôt distant ;
|
|
- le contexte projet est porté par l'URL : la sidebar résout le miroir même
|
|
sans cookie ``flowdeck_workspace`` (navigation directe / favori) ;
|
|
- ``GET /api/sidebar/workspace-tree`` rend l'arbre du **miroir** (et non plus le
|
|
placeholder « Remote workspace ») quand le cookie est ``gitea:owner:repo`` ;
|
|
- ``loadGiteaTree`` (base.html) cible ``#sidebar-gitea-items`` et n'écrase plus
|
|
l'arbre du workspace local de ``#sidebar-workspace-items``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from app.db import get_conn
|
|
|
|
|
|
def _mirror_id(ws_key: str) -> int | None:
|
|
with get_conn() as conn:
|
|
row = conn.execute(
|
|
"SELECT id FROM workspaces WHERE name=? AND settings_json LIKE ?",
|
|
(ws_key, "%gitea_repo%"),
|
|
).fetchone()
|
|
return row["id"] if row else None
|
|
|
|
|
|
def _mk_mirror_page(ws_id: int, title: str) -> int:
|
|
with get_conn() as conn:
|
|
cur = conn.execute(
|
|
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section) "
|
|
"VALUES ('', ?, ?, '', 'blocks', 'Private')",
|
|
(ws_id, title),
|
|
)
|
|
conn.commit()
|
|
return cur.lastrowid
|
|
|
|
|
|
def test_gitea_workspace_creates_and_shows_local_mirror(client):
|
|
"""Le premier GET crée le miroir et le sidebar pointe dessus (localWsId>0)."""
|
|
assert _mirror_id("acme/widget") is None
|
|
r = client.get("/gitea-workspace?owner=acme&repo=widget")
|
|
assert r.status_code == 200, r.text
|
|
ws_id = _mirror_id("acme/widget")
|
|
assert ws_id is not None, "le workspace miroir doit être créé"
|
|
# La sidebar expose l'ID du miroir (et non 0) dès le premier rendu.
|
|
m = re.search(r"localWsId:\s*(\d+)", r.text)
|
|
assert m, "localWsId introuvable dans la page"
|
|
assert int(m.group(1)) == ws_id, "la sidebar doit ouvrir l'équivalent local en parallèle"
|
|
assert "Open a workspace to see your files" not in r.text
|
|
assert "acme/widget" in r.text
|
|
|
|
|
|
def test_gitea_workspace_context_from_url_without_cookie(client):
|
|
"""Navigation directe (sans cookie) : le projet reste identifié par l'URL."""
|
|
r = client.get("/gitea-workspace?owner=acme&repo=gadget")
|
|
assert r.status_code == 200, r.text
|
|
ws_id = _mirror_id("acme/gadget")
|
|
assert ws_id is not None
|
|
m = re.search(r"localWsId:\s*(\d+)", r.text)
|
|
assert m and int(m.group(1)) == ws_id
|
|
# Pas de bandeau « aucune workspace ouverte » alors que le miroir existe.
|
|
assert "Open a workspace to see your files" not in r.text
|
|
|
|
|
|
def test_sidebar_workspace_tree_renders_gitea_mirror(client):
|
|
"""Le refresh sidebar ne remplace plus le miroir par « Remote workspace »."""
|
|
client.get("/gitea-workspace?owner=acme&repo=repo1")
|
|
ws_id = _mirror_id("acme/repo1")
|
|
assert ws_id is not None
|
|
_mk_mirror_page(ws_id, "Note miroir")
|
|
r = client.get(
|
|
"/api/sidebar/workspace-tree",
|
|
cookies={"flowdeck_workspace": "gitea:acme:repo1"},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
assert "Note miroir" in r.text
|
|
assert "Remote workspace" not in r.text
|
|
|
|
|
|
def test_sidebar_workspace_tree_gitea_without_mirror_is_empty_not_error(client):
|
|
r = client.get(
|
|
"/api/sidebar/workspace-tree",
|
|
cookies={"flowdeck_workspace": "gitea:ghost/none"},
|
|
)
|
|
assert r.status_code == 200
|
|
assert "Remote workspace" not in r.text
|
|
assert "No pages yet" in r.text
|
|
|
|
|
|
def test_loadgitea_tree_targets_repository_section():
|
|
"""Scan statique : loadGiteaTree ne doit pas cibler #sidebar-workspace-items."""
|
|
from pathlib import Path
|
|
|
|
html = (Path(__file__).resolve().parent.parent / "app" / "templates" / "base.html").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
fn = html.split("loadGiteaTree() {", 1)[1].split("newPageInWorkspace()", 1)[0]
|
|
assert "getElementById('sidebar-gitea-items')" in fn
|
|
assert "getElementById('sidebar-workspace-items')" not in fn
|