import { describe, expect, it } from 'vitest'; import { OutlineError, looksIndented, parseOutline, toFlatLines, } from '@/services/outlineParser'; describe('lecture d une liste simple', () => { it('produit un noeud par ligne', () => { const result = parseOutline('VLAN\nVPN\nFirewall'); expect(result.count).toBe(3); expect(result.depth).toBe(1); expect(result.roots.map((node) => node.text)).toEqual(['VLAN', 'VPN', 'Firewall']); expect(result.roots.every((node) => node.children.length === 0)).toBe(true); }); it('ignore les lignes vides et les espaces superflus', () => { const result = parseOutline('VLAN\n\n \nVPN\n\n'); expect(result.roots.map((node) => node.text)).toEqual(['VLAN', 'VPN']); }); it('retire les puces et les numerotations', () => { const result = parseOutline('- VLAN\n* VPN\n1. Firewall\n2) Switch'); expect(result.roots.map((node) => node.text)).toEqual(['VLAN', 'VPN', 'Firewall', 'Switch']); }); it('refuse un texte sans ligne exploitable', () => { expect(() => parseOutline(' \n\n')).toThrow(OutlineError); }); it('applique la limite de noeuds', () => { const texte = Array.from({ length: 12 }, (_unused, index) => `N${index}`).join('\n'); expect(() => parseOutline(texte, 5)).toThrow(/limite/i); }); }); describe('lecture d une arborescence indentee', () => { const OUTLINE = [ 'Infrastructure', ' Reseau', ' VLAN', ' VPN', ' Firewall', ' Virtualisation', ' Proxmox', ' Docker', ' Services', ' GLPI', ' Grafana', ].join('\n'); it('reconstruit la hierarchie complete', () => { const result = parseOutline(OUTLINE); expect(result.count).toBe(11); expect(result.depth).toBe(3); expect(result.roots).toHaveLength(1); const racine = result.roots[0]; expect(racine?.text).toBe('Infrastructure'); expect(racine?.children.map((node) => node.text)).toEqual([ 'Reseau', 'Virtualisation', 'Services', ]); expect(racine?.children[0]?.children.map((node) => node.text)).toEqual([ 'VLAN', 'VPN', 'Firewall', ]); expect(racine?.children[2]?.children).toHaveLength(2); }); it('accepte les tabulations', () => { const result = parseOutline('Reseau\n\tVLAN\n\tVPN\n\t\tSite A'); expect(result.roots[0]?.children.map((node) => node.text)).toEqual(['VLAN', 'VPN']); expect(result.roots[0]?.children[1]?.children[0]?.text).toBe('Site A'); }); it('accepte plusieurs racines', () => { const result = parseOutline('Reseau\n VLAN\nServices\n GLPI'); expect(result.roots.map((node) => node.text)).toEqual(['Reseau', 'Services']); }); it('tolere un texte entierement indente', () => { const result = parseOutline(' Reseau\n VLAN'); expect(result.roots[0]?.text).toBe('Reseau'); expect(result.roots[0]?.children[0]?.text).toBe('VLAN'); }); it('refuse une indentation incoherente', () => { // "C" ne correspond a aucun niveau ouvert : ni 0, ni 4. expect(() => parseOutline('A\n B\n C')).toThrow(/incoherente/i); }); it('refuse une ligne moins indentee que la premiere', () => { expect(() => parseOutline(' A\nB')).toThrow(/moins indentee/i); }); it('refuse une arborescence trop profonde', () => { const profond = Array.from({ length: 25 }, (_unused, index) => `${' '.repeat(index * 2)}N${index}`, ).join('\n'); expect(() => parseOutline(profond)).toThrow(/profonde/i); }); it('signale la ligne fautive', () => { try { parseOutline('A\n B\n C'); expect.unreachable('une erreur etait attendue'); } catch (error) { expect(error).toBeInstanceOf(OutlineError); expect((error as OutlineError).line).toBe(3); } }); }); describe('detection du mode', () => { it('reconnait un texte indente', () => { expect(looksIndented('A\n B')).toBe(true); expect(looksIndented('A\n\tB')).toBe(true); expect(looksIndented('A\nB')).toBe(false); }); it('extrait les lignes utiles', () => { expect(toFlatLines('- A\n\n \n2. B\r\nC')).toEqual(['A', 'B', 'C']); }); });