// BasicBeliefNode.test.tsx 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 BasicBeliefNode, { type BasicBeliefNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx'; import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores'; import type { Node } from '@xyflow/react'; import '@testing-library/jest-dom'; describe('BasicBeliefNode', () => { let user: ReturnType; beforeEach(() => { user = userEvent.setup(); }); describe('Rendering', () => { it('should render the basic belief node with keyword type by default', () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'help', value: 'help', label: 'Keyword said:' }, hasReduce: true, }, }; renderWithProviders( ); expect(screen.getByText('Belief:')).toBeInTheDocument(); expect(screen.getByDisplayValue('Keyword said:')).toBeInTheDocument(); expect(screen.getByDisplayValue('help')).toBeInTheDocument(); }); it('should render with semantic belief type', () => { const mockNode: Node = { id: 'belief-2', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' }, hasReduce: true, }, }; renderWithProviders( ); expect(screen.getByDisplayValue('Detected with LLM:')).toBeInTheDocument(); expect(screen.getByDisplayValue('test value')).toBeInTheDocument(); }); it('should render with object belief type', () => { const mockNode: Node = { id: 'belief-3', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'object', id: 'obj1', value: 'cup', label: 'Object found:' }, hasReduce: true, }, }; renderWithProviders( ); expect(screen.getByDisplayValue('Object found:')).toBeInTheDocument(); expect(screen.getByDisplayValue('cup')).toBeInTheDocument(); }); it('should render with emotion belief type and select dropdown', () => { const mockNode: Node = { id: 'belief-4', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'emotion', id: 'em1', value: 'happy', label: 'Emotion recognised:' }, hasReduce: true, }, }; renderWithProviders( ); expect(screen.getByDisplayValue('Emotion recognised:')).toBeInTheDocument(); // For emotion type, we should check that the select has the correct value selected const selectElement = screen.getByDisplayValue('Happy'); expect(selectElement).toBeInTheDocument(); expect((selectElement as HTMLSelectElement).value).toBe('happy'); }); it('should render emotion dropdown with all emotion options', () => { const mockNode: Node = { id: 'belief-5', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'emotion', id: 'em1', value: 'happy', label: 'Emotion recognised:' }, hasReduce: true, }, }; renderWithProviders( ); const selectElement = screen.getByDisplayValue('Happy'); expect(selectElement).toBeInTheDocument(); // Check that all emotion options are present expect(screen.getByText('Happy')).toBeInTheDocument(); expect(screen.getByText('Angry')).toBeInTheDocument(); expect(screen.getByText('Sad')).toBeInTheDocument(); expect(screen.getByText('Cheerful')).toBeInTheDocument(); }); it('should render without wrapping quotes for object type', () => { const mockNode: Node = { id: 'belief-6', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'object', id: 'obj1', value: 'chair', label: 'Object found:' }, hasReduce: true, }, }; renderWithProviders( ); // Object type should not have wrapping quotes const inputs = screen.getAllByDisplayValue('chair'); expect(inputs.length).toBe(1); // Only the text input, no extra quote elements }); }); describe('User Interactions', () => { it('should update belief type when select is changed', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: 'hello', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const select = screen.getByDisplayValue('Keyword said:'); await user.selectOptions(select, 'semantic'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.type).toBe('semantic'); // Note: The component doesn't update the label when changing type // So we can't test for label change }); }); it('should update text value when typing for keyword type', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: '', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('keyword...'); await user.type(input, 'help me{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.value).toBe('help me'); }); }); it('should update text value when typing for semantic type', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByDisplayValue('test value') as HTMLInputElement; // Clear the input for (let i = 0; i < 'test value'.length; i++) { await user.type(input, '{backspace}'); } await user.type(input, 'new semantic value{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.value).toBe('new semantic value'); }); }); it('should update emotion value when selecting from dropdown', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'emotion', id: 'em1', value: 'happy', label: 'Emotion recognised:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const select = screen.getByDisplayValue('Happy'); await user.selectOptions(select, 'sad'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.value).toBe('sad'); }); }); it('should preserve value when switching between text-based belief types', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: 'test value', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); // Switch from keyword to semantic const typeSelect = screen.getByDisplayValue('Keyword said:'); await user.selectOptions(typeSelect, 'semantic'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.type).toBe('semantic'); expect(updatedNode?.data.belief.value).toBe('test value'); // Value should be preserved }); }); it('should automatically choose the first option when switching to emotion type, and carry on to the text values', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: 'some text', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); // Switch from keyword to emotion const typeSelect = screen.getByDisplayValue('Keyword said:'); await user.selectOptions(typeSelect, 'emotion'); await waitFor(() => { const state = useFlowStore.getState(); const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node; expect(updatedNode?.data.belief.type).toBe('emotion'); // The component doesn't reset the value when changing types // So it keeps the old value even though it doesn't make sense for emotion type expect(updatedNode?.data.belief.value).toBe('Happy'); }); }); }); // ... rest of the tests remain the same, just fixing the Integration with Store section ... describe('Integration with Store', () => { it('should properly update the store when changing belief value', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: '', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByPlaceholderText('keyword...'); await user.type(input, 'emergency{enter}'); await waitFor(() => { const state = useFlowStore.getState(); expect(state.nodes).toHaveLength(1); expect(state.nodes[0].id).toBe('belief-1'); const beliefData = state.nodes[0].data as BasicBeliefNodeData; expect(beliefData.belief.value).toBe('emergency'); expect(beliefData.belief.type).toBe('keyword'); }); }); it('should properly update the store when changing belief type', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'keyword', id: 'kw1', value: 'test', label: 'Keyword said:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const select = screen.getByDisplayValue('Keyword said:'); await user.selectOptions(select, 'object'); await waitFor(() => { const state = useFlowStore.getState(); const beliefData = state.nodes[0].data as BasicBeliefNodeData; expect(beliefData.belief.type).toBe('object'); // Note: The component doesn't update the label when changing type expect(beliefData.belief.value).toBe('test'); // Value should be preserved }); }); it('should not affect other nodes when updating one belief node', async () => { const belief1: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief 1', droppable: true, belief: { type: 'keyword', id: 'kw1', value: 'hello', label: 'Keyword said:' }, hasReduce: true, }, }; const belief2: Node = { id: 'belief-2', type: 'basic_belief', position: { x: 100, y: 0 }, data: { label: 'Belief 2', droppable: true, belief: { type: 'object', id: 'obj1', value: 'chair', label: 'Object found:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [belief1, belief2], edges: [], }); renderWithProviders( ); const input = screen.getByDisplayValue('hello') as HTMLInputElement; // Clear the input for (let i = 0; i < 'hello'.length; i++) { await user.type(input, '{backspace}'); } await user.type(input, 'goodbye{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const updatedBelief1 = state.nodes.find(n => n.id === 'belief-1') as Node; const unchangedBelief2 = state.nodes.find(n => n.id === 'belief-2') as Node; expect(updatedBelief1.data.belief.value).toBe('goodbye'); expect(unchangedBelief2.data.belief.value).toBe('chair'); expect(unchangedBelief2.data.belief.type).toBe('object'); }); }); it('should handle multiple rapid updates to belief value', async () => { const mockNode: Node = { id: 'belief-1', type: 'basic_belief', position: { x: 0, y: 0 }, data: { label: 'Belief', droppable: true, belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' }, hasReduce: true, }, }; useFlowStore.setState({ nodes: [mockNode], edges: [], }); renderWithProviders( ); const input = screen.getByDisplayValue('test value') as HTMLInputElement; await user.type(input, '1'); await waitFor(() => { const state = useFlowStore.getState(); const nodeData = state.nodes[0].data as BasicBeliefNodeData; expect(nodeData.belief.value).toBe('test value'); }); await user.type(input, '2'); await waitFor(() => { const state = useFlowStore.getState(); const nodeData = state.nodes[0].data as BasicBeliefNodeData; expect(nodeData.belief.value).toBe('test value'); }); await user.type(input, '{enter}'); await waitFor(() => { const state = useFlowStore.getState(); const nodeData = state.nodes[0].data as BasicBeliefNodeData; expect(nodeData.belief.value).toBe('test value12'); }); }); }); });