import { describe, it, beforeEach } from '@jest/globals'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../.././/./../../test-utils/test-utils'; import NormNode, { NormReduce, NormConnects, type NormNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode' import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores'; import type { Node } from '@xyflow/react'; import '@testing-library/jest-dom' describe('NormNode', () => { let user: ReturnType; beforeEach(() => { user = userEvent.setup(); }); describe('Rendering', () => { it('should render the norm node with default data', () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; renderWithProviders( ); expect(screen.getByPlaceholderText('Pepper should ...')).toBeInTheDocument(); }); it('should render with pre-populated norm text', () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Be respectful to humans', hasReduce: true, }, }; renderWithProviders( ); const input = screen.getByDisplayValue('Be respectful to humans'); expect(input).toBeInTheDocument(); }); it('should render with selected state', () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, critical: false }, }; renderWithProviders( ); const norm = screen.getByText("Norm :") expect(norm).toBeInTheDocument(); }); it('should render with dragging state', () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Dragged norm', hasReduce: true, }, }; renderWithProviders( ); const input = screen.getByDisplayValue('Dragged norm'); expect(input).toBeInTheDocument(); }); }); describe('User Interactions', () => { it('should update norm text when user types in the input field', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); await user.type(input, 'Be polite to guests{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'norm-1'); expect(updatedNode?.data.norm).toBe('Be polite to guests'); }); }); it('should handle clearing the norm text', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Initial norm text', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByDisplayValue('Initial norm text') as HTMLInputElement; // clearing the norm text is the same as just deleting all characters one by one // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/ for (let a = 0; a < 'Initial norm text'.length; a++){ await user.type(input, '{backspace}') } await user.type(input,'{enter}') await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'norm-1'); expect(updatedNode?.data.norm).toBe(''); }); }); it('should update norm text multiple times', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); await user.type(input, 'First norm{enter}'); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe('First norm'); }); // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/ for (let a = 0; a < 'First norm'.length; a++){ await user.type(input, '{backspace}') } await user.type(input, 'Second norm{enter}'); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe('Second norm'); }); }); it('should handle special characters in norm text', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); await user.type(input, "Don't harm & be nice!{enter}" ); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe("Don't harm & be nice!"); }); }); it('should handle long norm text', async () => { const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); await user.type(input, longText); await user.type(input, "{enter}") await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe(longText); }); }); }); describe('NormReduce Function', () => { it('should reduce a norm node to its essential data', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Safety Norm', droppable: true, norm: 'Never harm humans', hasReduce: true, }, }; const allNodes: Node[] = [normNode]; const result = NormReduce(normNode, allNodes); expect(result).toEqual({ id: 'norm-1', label: 'Safety Norm', norm: 'Never harm humans', }); }); it('should reduce multiple norm nodes independently', () => { const norm1: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Norm 1', droppable: true, norm: 'Be helpful', hasReduce: true, }, }; const norm2: Node = { id: 'norm-2', type: 'norm', position: { x: 100, y: 0 }, data: { label: 'Norm 2', droppable: true, norm: 'Be honest', hasReduce: true, }, }; const allNodes: Node[] = [norm1, norm2]; const result1 = NormReduce(norm1, allNodes); const result2 = NormReduce(norm2, allNodes); expect(result1.id).toBe('norm-1'); expect(result1.norm).toBe('Be helpful'); expect(result2.id).toBe('norm-2'); expect(result2.norm).toBe('Be honest'); }); it('should handle empty norm text', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Empty Norm', droppable: true, norm: '', hasReduce: true, }, }; const result = NormReduce(normNode, [normNode]); expect(result.norm).toBe(''); expect(result.id).toBe('norm-1'); }); it('should preserve node label in reduction', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Custom Label', droppable: false, norm: 'Test norm', hasReduce: false, }, }; const result = NormReduce(normNode, [normNode]); expect(result.label).toBe('Custom Label'); }); }); describe('NormConnects Function', () => { it('should handle connection without errors', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Test', hasReduce: true, }, }; const phaseNode: Node = { id: 'phase-1', type: 'phase', position: { x: 100, y: 0 }, data: { label: 'Phase 1', droppable: true, children: [], hasReduce: true, }, }; expect(() => { NormConnects(normNode, phaseNode, true); }).not.toThrow(); }); it('should handle connection when norm is target', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Test', hasReduce: true, }, }; const phaseNode: Node = { id: 'phase-1', type: 'phase', position: { x: 100, y: 0 }, data: { label: 'Phase 1', droppable: true, children: [], hasReduce: true, }, }; expect(() => { NormConnects(normNode, phaseNode, false); }).not.toThrow(); }); it('should handle self-connection', () => { const normNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'Test', hasReduce: true, }, }; expect(() => { NormConnects(normNode, normNode, true); }).not.toThrow(); }); }); describe('Integration with Store', () => { it('should properly update the store when editing norm text', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/ for (let a = 0; a < 20; a++){ await user.type(input, '{backspace}') } await user.type(input, 'New norm value{enter}'); await waitFor(() => { const state = useFlowStore.getState(); expect(state.nodes).toHaveLength(1); expect(state.nodes[0].id).toBe('norm-1'); expect(state.nodes[0].data.norm).toBe('New norm value'); }); }); it('should properly update the store when editing critical checkbox', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: '', hasReduce: true, critical: false, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const checkbox = screen.getByLabelText('Critical:'); await user.click(checkbox); await waitFor(() => { const state = useFlowStore.getState(); expect(state.nodes).toHaveLength(1); expect(state.nodes[0].id).toBe('norm-1'); expect(state.nodes[0].data.norm).toBe(''); expect(state.nodes[0].data.critical).toBe(true); }); }); it('should not affect other nodes when updating one norm node', async () => { const norm1: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Norm 1', droppable: true, norm: 'Original norm 1', hasReduce: true, }, }; const norm2: Node = { id: 'norm-2', type: 'norm', position: { x: 100, y: 0 }, data: { label: 'Norm 2', droppable: true, norm: 'Original norm 2', hasReduce: true, }, }; useFlowStore.setState({ nodes: [norm1, norm2], edges: [], }); renderWithProviders( ); const input = screen.getByDisplayValue('Original norm 1') as HTMLInputElement; // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/ for (let a = 0; a < 20; a++){ await user.type(input, '{backspace}') } await user.type(input, 'Updated norm 1{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNorm1 = state.nodes.find(n => n.id === 'norm-1'); const unchangedNorm2 = state.nodes.find(n => n.id === 'norm-2'); expect(updatedNorm1?.data.norm).toBe('Updated norm 1'); expect(unchangedNorm2?.data.norm).toBe('Original norm 2'); }); }); it('should maintain data consistency with multiple rapid updates', async () => { const mockNode: Node = { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, data: { label: 'Test Norm', droppable: true, norm: 'haa haa fuyaaah - link', hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('Pepper should ...'); await user.type(input, 'a'); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link'); }); await user.type(input, 'b'); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link'); }); await user.type(input, 'c'); await waitFor(() => { expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link'); }, { timeout: 3000 }); }); }); });