import { describe, expect, it } from 'vitest'; import { MAP_FORMAT_VERSION, type MindMap } from '@/types/map'; import { normalizeMapDocument, serializeMap } from '@/services/mapSerialization'; import { getOrderedChildren } from '@/utils/tree'; /** Carte v2 : pas de champ `order`, l'ordre est celui du tableau. */ const V2_MAP = { version: 2, id: 'v2-map', name: 'Reseau v2', createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z', nodes: [ { id: 'root', type: 'root', text: 'Reseau', position: { x: 0, y: 0 }, parentId: null }, { id: 'dns', type: 'standard', text: 'DNS', position: { x: 200, y: 0 }, parentId: 'root' }, { id: 'dhcp', type: 'standard', text: 'DHCP', position: { x: 200, y: 60 }, parentId: 'root' }, { id: 'vlan', type: 'standard', text: 'VLAN', position: { x: 200, y: 120 }, parentId: 'root' }, ], edges: [ { id: 'e-root-dns', source: 'root', target: 'dns' }, { id: 'e-root-dhcp', source: 'root', target: 'dhcp' }, { id: 'e-root-vlan', source: 'root', target: 'vlan' }, ], viewport: { x: 0, y: 0, zoom: 1 }, settings: { theme: 'default', layout: 'mindmap-right', branchStyle: 'bezier', showGrid: true }, }; const V1_MAP = { ...V2_MAP, version: 1, id: 'v1-map', nodes: V2_MAP.nodes.map((node) => node.id === 'root' ? { ...node, style: { width: 240, textAlign: 'center' } } : node, ), }; function texts(map: MindMap, parentId: string | null): string[] { return getOrderedChildren(map.nodes, parentId).map((node) => node.text); } describe('migration vers la v3', () => { it('materialise l ordre d une carte v2 depuis la position dans le tableau', () => { const map = normalizeMapDocument(V2_MAP); expect(map.version).toBe(MAP_FORMAT_VERSION); expect(texts(map, 'root')).toEqual(['DNS', 'DHCP', 'VLAN']); expect(map.nodes.map((node) => node.order)).toEqual([0, 0, 1, 2]); }); it('migre une carte v1 jusqu a la v3 sans perdre la mise en page', () => { const map = normalizeMapDocument(V1_MAP); expect(map.version).toBe(MAP_FORMAT_VERSION); expect(texts(map, 'root')).toEqual(['DNS', 'DHCP', 'VLAN']); expect(map.nodes[0]?.layout?.width).toBe(240); expect(map.nodes[0]?.style?.textAlign).toBe('center'); }); it('respecte un ordre explicite plutot que la position dans le tableau', () => { const desordonne = { ...V2_MAP, version: 3, nodes: [ V2_MAP.nodes[0], { ...V2_MAP.nodes[1], order: 2 }, { ...V2_MAP.nodes[2], order: 1 }, { ...V2_MAP.nodes[3], order: 0 }, ], }; expect(texts(normalizeMapDocument(desordonne), 'root')).toEqual(['VLAN', 'DHCP', 'DNS']); }); it('refuse une version plus recente', () => { expect(() => normalizeMapDocument({ ...V2_MAP, version: MAP_FORMAT_VERSION + 1 })).toThrow( /plus recente/i, ); }); }); describe('aller-retour de l ordre', () => { it('conserve un ordre reordonne apres export puis import', () => { const map = normalizeMapDocument(V2_MAP); const reordonne: MindMap = { ...map, nodes: map.nodes.map((node) => { if (node.id === 'vlan') return { ...node, order: 0 }; if (node.id === 'dns') return { ...node, order: 1 }; if (node.id === 'dhcp') return { ...node, order: 2 }; return node; }), }; const restaure = normalizeMapDocument(JSON.parse(serializeMap(reordonne))); expect(texts(restaure, 'root')).toEqual(['VLAN', 'DNS', 'DHCP']); }); /** VLAN en tete, comme le ferait "Placer en premier". */ function withVlanFirst(): MindMap { const map = normalizeMapDocument(V2_MAP); const ordres: Record = { vlan: 0, dns: 1, dhcp: 2 }; return { ...map, nodes: map.nodes.map((node) => ordres[node.id] === undefined ? node : { ...node, order: ordres[node.id] as number }, ), }; } it('ecrit les noeuds dans l ordre hierarchique', () => { const serialise = JSON.parse(serializeMap(withVlanFirst())) as MindMap; // Un lecteur anterieur a la v3, qui ignore `order`, retrouve le bon ordre. expect(serialise.nodes.map((node) => node.id)).toEqual(['root', 'vlan', 'dns', 'dhcp']); }); it('emet les liens dans l ordre de fratrie', () => { const serialise = JSON.parse(serializeMap(withVlanFirst())) as MindMap; expect(serialise.edges.map((edge) => edge.target)).toEqual(['vlan', 'dns', 'dhcp']); }); it('departage deux freres de meme rang par leur position dans le tableau', () => { const map = normalizeMapDocument(V2_MAP); const ambigu: MindMap = { ...map, nodes: map.nodes.map((node) => (node.id === 'vlan' ? { ...node, order: 0 } : node)), }; // `dns` et `vlan` ont tous deux le rang 0 : le tableau tranche, sans erreur. expect(texts(normalizeMapDocument(JSON.parse(serializeMap(ambigu))), 'root')).toEqual([ 'DNS', 'VLAN', 'DHCP', ]); }); it('conserve l ordre d une arborescence sur plusieurs niveaux', () => { const map = normalizeMapDocument({ version: 3, id: 'profond', nodes: [ { id: 'r', type: 'root', text: 'R', parentId: null, position: { x: 0, y: 0 } }, { id: 'a', text: 'A', parentId: 'r', order: 1, position: { x: 0, y: 0 } }, { id: 'b', text: 'B', parentId: 'r', order: 0, position: { x: 0, y: 0 } }, { id: 'a1', text: 'A1', parentId: 'a', order: 1, position: { x: 0, y: 0 } }, { id: 'a2', text: 'A2', parentId: 'a', order: 0, position: { x: 0, y: 0 } }, ], }); expect(texts(map, 'r')).toEqual(['B', 'A']); expect(texts(map, 'a')).toEqual(['A2', 'A1']); }); });