Construire une grande carte demande beaucoup moins de gestes. Ordre des freres (format v3) `MindMapNode.order` devient la position parmi les freres. `parentId` reste la seule source de verite de la relation hierarchique : une source par preoccupation, jamais deux versions contradictoires. L'ordre est renumerote a chaque modification structurelle dans `withEdges`, unique point de passage. Le format passe en v3 : un lecteur v2 ignorerait `order` et reordonnerait silencieusement la carte. Migration v2 -> v3 depuis la position dans le tableau, v1 -> v2 -> v3 s'enchainent, et les noeuds sont ecrits en parcours de profondeur pour rester corrects meme pour un lecteur anterieur. Creation rapide - Ajouter plusieurs enfants : une ligne = un noeud, avec apercu avant creation. - Collage d'une liste multi-ligne sur un noeud : le presse-papiers interne reste prioritaire, l'edition de texte n'est jamais interceptee. - Import d'une arborescence indentee : espaces ou tabulations, puces retirees, indentation incoherente signalee avec le numero de ligne plutot que devinee. - Les nouveaux noeuds sont repartis autour du parent au lieu de se superposer. Hierarchie Monter, descendre, placer en premier ou en dernier, remonter d'un niveau, "Deplacer vers..." avec recherche, suppression d'un noeud en conservant ses enfants a la place qu'il occupait. Le re-parentage passe toujours par `reparentNode` : la detection de cycle n'est ecrite qu'une fois. Branches Selectionner, dupliquer, copier, coller comme enfant, deplacer avec ses descendants (reglage, ou Alt pour l'inverser le temps d'un glisser), replier et developper une branche, tout replier, tout developper, replier au-dela d'un niveau. Clavier Maj+Tab remonte d'un niveau, Maj+Entree ouvre la creation multiple, Alt+Haut et Alt+Bas reordonnent. Tab, Entree et F2 gardent leur comportement. Historique Chaque operation logique reste une seule entree : creation multiple, import d'outline, reordonnancement, deplacement de branche, duplication, collage, suppression, tout replier. Tests : 172 vitest (103 -> 172) et 36 pytest (31 -> 36). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
"""Le backend doit accepter et restituer l'ordre des freres (format v3)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from tests.conftest import build_map
|
|
|
|
|
|
def build_v3_map() -> dict:
|
|
payload = build_map(node_count=1, name="Reseau")
|
|
payload["version"] = 3
|
|
payload["nodes"] = [
|
|
{
|
|
"id": "root",
|
|
"type": "root",
|
|
"text": "Reseau",
|
|
"position": {"x": 0, "y": 0},
|
|
"parentId": None,
|
|
"order": 0,
|
|
},
|
|
{
|
|
"id": "vlan",
|
|
"type": "standard",
|
|
"text": "VLAN",
|
|
"position": {"x": 200, "y": 0},
|
|
"parentId": "root",
|
|
"order": 0,
|
|
},
|
|
{
|
|
"id": "vpn",
|
|
"type": "standard",
|
|
"text": "VPN",
|
|
"position": {"x": 200, "y": 60},
|
|
"parentId": "root",
|
|
"order": 1,
|
|
},
|
|
{
|
|
"id": "dns",
|
|
"type": "standard",
|
|
"text": "DNS",
|
|
"position": {"x": 200, "y": 120},
|
|
"parentId": "root",
|
|
"order": 2,
|
|
},
|
|
]
|
|
payload["edges"] = [
|
|
{"id": "e-root-vlan", "source": "root", "target": "vlan"},
|
|
{"id": "e-root-vpn", "source": "root", "target": "vpn"},
|
|
{"id": "e-root-dns", "source": "root", "target": "dns"},
|
|
]
|
|
payload["settings"]["dragMovesBranch"] = True
|
|
return payload
|
|
|
|
|
|
def test_share_accepts_v3_map(client: TestClient) -> None:
|
|
response = client.post("/api/shares", json={"title": "v3", "map": build_v3_map()})
|
|
assert response.status_code == 201, response.text
|
|
|
|
|
|
def test_child_order_is_returned_unchanged(client: TestClient) -> None:
|
|
created = client.post("/api/shares", json={"title": "v3", "map": build_v3_map()})
|
|
token = created.json()["share_token"]
|
|
|
|
public = client.get("/api/shares/" + token).json()
|
|
assert public["map_version"] == 3
|
|
|
|
orders = {node["id"]: node.get("order") for node in public["map"]["nodes"]}
|
|
assert orders == {"root": 0, "vlan": 0, "vpn": 1, "dns": 2}
|
|
assert public["map"]["settings"]["dragMovesBranch"] is True
|
|
|
|
|
|
def test_reordered_update_is_visible_publicly(client: TestClient) -> None:
|
|
created = client.post("/api/shares", json={"title": "v3", "map": build_v3_map()})
|
|
body = created.json()
|
|
|
|
reordered = build_v3_map()
|
|
for node in reordered["nodes"]:
|
|
if node["id"] == "dns":
|
|
node["order"] = 0
|
|
elif node["id"] == "vlan":
|
|
node["order"] = 2
|
|
|
|
response = client.put(
|
|
"/api/shares/" + body["share_token"],
|
|
json={"title": "v3", "map": reordered},
|
|
headers={"X-Management-Token": body["management_token"]},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
public = client.get("/api/shares/" + body["share_token"]).json()
|
|
orders = {node["id"]: node.get("order") for node in public["map"]["nodes"]}
|
|
assert orders["dns"] == 0
|
|
assert orders["vlan"] == 2
|
|
|
|
|
|
def test_older_versions_are_still_accepted(client: TestClient) -> None:
|
|
for version in (1, 2):
|
|
payload = build_map()
|
|
payload["version"] = version
|
|
response = client.post("/api/shares", json={"title": f"v{version}", "map": payload})
|
|
assert response.status_code == 201, response.text
|
|
|
|
|
|
def test_invalid_order_type_is_rejected_or_ignored(client: TestClient) -> None:
|
|
"""Un ordre non numerique ne doit jamais faire tomber le serveur."""
|
|
payload = build_v3_map()
|
|
payload["nodes"][1]["order"] = "premier"
|
|
|
|
response = client.post("/api/shares", json={"title": "x", "map": payload})
|
|
assert response.status_code in (201, 422)
|