import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; import React from 'react'; import { GraphNodeList } from './graph-node-list'; import type { GraphEdge, GraphNode } from './graph.types'; const NODES: GraphNode[] = [ { id: 'documento', label: 'Documento', type: 'Classe' }, { id: 'processo', label: 'Processo', type: 'Classe' }, { id: 'peticao', label: 'Petição inicial', type: 'Indivíduo' }, ]; const EDGES: GraphEdge[] = [ { source: 'peticao', target: 'documento', label: 'é um' }, { source: 'documento', target: 'processo', label: 'pertence a' }, ]; describe('GraphNodeList', () => { it('exposes the graph as a listbox', () => { render( {}} />); expect(screen.getByRole('listbox', { name: 'Graph nodes' })).toBeInTheDocument(); expect(screen.getAllByRole('option')).toHaveLength(3); }); it('puts relationships into words a screen reader can read', () => { render( {}} />); // The arrow on the canvas conveys nothing when read aloud. expect(screen.getByText('é um → Documento')).toBeInTheDocument(); expect(screen.getByText('Petição inicial → é um')).toBeInTheDocument(); }); it('shows the node type', () => { render( {}} />); expect(screen.getAllByText('Classe')).toHaveLength(2); }); it('marks the shared selection', () => { render( {}} />); // An option's accessible name deliberately includes its relationships, so // "Documento … → Processo" also contains the word; anchor to the label. expect(screen.getByRole('option', { name: /^Processo/ })).toHaveAttribute( 'aria-selected', 'true' ); }); it('reports a click', () => { const onSelect = vi.fn(); render(); fireEvent.click(screen.getByRole('option', { name: /^Processo/ })); expect(onSelect).toHaveBeenCalledWith('processo'); }); it('keeps the list to a single tab stop', () => { render( {}} />); const options = screen.getAllByRole('option'); expect(options.filter(o => o.getAttribute('tabindex') === '0')).toHaveLength(1); }); it('navigates with the arrow keys', () => { render( {}} />); fireEvent.keyDown(screen.getByRole('listbox'), { key: 'ArrowDown' }); expect(screen.getAllByRole('option')[1]).toHaveFocus(); }); it('selects with Enter and clears with Escape', () => { const onSelect = vi.fn(); render(); const listbox = screen.getByRole('listbox'); fireEvent.keyDown(listbox, { key: 'Enter' }); expect(onSelect).toHaveBeenLastCalledWith('documento'); fireEvent.keyDown(listbox, { key: 'Escape' }); expect(onSelect).toHaveBeenLastCalledWith(null); }); it('follows a selection made on the canvas', () => { const { rerender } = render( {}} /> ); rerender( {}} /> ); expect(screen.getAllByRole('option')[2]).toHaveAttribute('tabindex', '0'); }); it('can omit relationships', () => { render( {}} />); expect(screen.queryByText('é um → Documento')).not.toBeInTheDocument(); }); it('says so when there is nothing to list', () => { render( {}} />); expect(screen.getByText('No nodes to list')).toBeInTheDocument(); }); });