From 467d88489cfea6dcfa8324b3b9b413cf996547c3 Mon Sep 17 00:00:00 2001 From: Woody Date: Fri, 24 Apr 2026 16:24:49 +0800 Subject: [PATCH] test(frontend): update tests for extracted questions and inline question display (sub-phase 2.2/2.3) Replace KeywordsDisplay test with ExtractedQuestionsDisplay test. Update e2e mock data for extracted_questions. Fix QueryInput to show submitted question inline with submit button. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- frontend/src/components/QueryInput.tsx | 14 +-- .../ExtractedQuestionsDisplay.test.tsx | 86 ++++++++++++++++++ .../test/components/KeywordsDisplay.test.tsx | 90 ------------------- frontend/src/test/e2e/query_flow.test.tsx | 9 +- 4 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 frontend/src/test/components/ExtractedQuestionsDisplay.test.tsx delete mode 100644 frontend/src/test/components/KeywordsDisplay.test.tsx diff --git a/frontend/src/components/QueryInput.tsx b/frontend/src/components/QueryInput.tsx index 7486b59..a2ba6c5 100644 --- a/frontend/src/components/QueryInput.tsx +++ b/frontend/src/components/QueryInput.tsx @@ -46,20 +46,20 @@ export const QueryInput: React.FC = ({ onSubmit, isLoading }) = rows={3} className="w-full rounded border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed" /> -
+
+ {submittedQuestion && ( +

+ Your question: “{submittedQuestion}” +

+ )}
- {submittedQuestion && ( -

- Your question: “{submittedQuestion}” -

- )} ) } diff --git a/frontend/src/test/components/ExtractedQuestionsDisplay.test.tsx b/frontend/src/test/components/ExtractedQuestionsDisplay.test.tsx new file mode 100644 index 0000000..34853db --- /dev/null +++ b/frontend/src/test/components/ExtractedQuestionsDisplay.test.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import { ExtractedQuestionsDisplay } from '../../components/ExtractedQuestionsDisplay' + +describe('ExtractedQuestionsDisplay', () => { + it('returns null when questions empty and not loading', () => { + const { container } = render() + expect(container).toBeEmptyDOMElement() + }) + + it('returns null when questions is undefined and not loading', () => { + const { container } = render() + expect(container).toBeEmptyDOMElement() + }) + + it('shows loading skeletons when isLoading is true', () => { + render() + + const skeletons = screen.getAllByTestId('extracted-question-skeleton') + expect(skeletons).toHaveLength(3) + + skeletons.forEach((skeleton) => { + expect(skeleton).toHaveClass('animate-pulse') + }) + }) + + it('renders questions as numbered list when provided', () => { + const questions = [ + 'What are the time extension provisions?', + 'What notice is required?', + 'How is extended time calculated?', + ] + render() + + expect(screen.getByText('What are the time extension provisions?')).toBeInTheDocument() + expect(screen.getByText('What notice is required?')).toBeInTheDocument() + expect(screen.getByText('How is extended time calculated?')).toBeInTheDocument() + + const items = screen.getAllByRole('listitem') + expect(items).toHaveLength(3) + }) + + it('shows "Extracted Questions:" label', () => { + const questions = ['What is the threshold?'] + render() + + const label = screen.getByText(/extracted questions:/i) + expect(label).toBeInTheDocument() + expect(label).toHaveClass('text-xs', 'text-gray-500', 'uppercase', 'tracking-wide') + }) + + it('renders questions in an ordered list', () => { + const questions = ['Question one?', 'Question two?', 'Question three?'] + render() + + const container = screen.getByTestId('extracted-questions-section') + expect(container).toBeInTheDocument() + + const list = screen.getByTestId('extracted-questions-container') + expect(list.tagName).toBe('OL') + }) + + it('has smooth transition classes on section', () => { + const questions = ['test'] + render() + + const section = screen.getByTestId('extracted-questions-section') + expect(section).toHaveClass('transition-all', 'duration-300', 'ease-in-out') + }) + + it('does not render loading state when questions are provided', () => { + const questions = ['test'] + render() + + const skeletons = screen.queryAllByTestId('extracted-question-skeleton') + expect(skeletons).toHaveLength(0) + }) + + it('renders each question with data-testid', () => { + const questions = ['First question?', 'Second question?'] + render() + + expect(screen.getByTestId('extracted-question-0')).toBeInTheDocument() + expect(screen.getByTestId('extracted-question-1')).toBeInTheDocument() + }) +}) diff --git a/frontend/src/test/components/KeywordsDisplay.test.tsx b/frontend/src/test/components/KeywordsDisplay.test.tsx deleted file mode 100644 index 7a32387..0000000 --- a/frontend/src/test/components/KeywordsDisplay.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import { KeywordsDisplay } from '../../components/KeywordsDisplay' - -describe('KeywordsDisplay', () => { - it('returns null when keywords empty and not loading', () => { - const { container } = render() - expect(container).toBeEmptyDOMElement() - }) - - it('returns null when keywords is undefined and not loading', () => { - const { container } = render() - expect(container).toBeEmptyDOMElement() - }) - - it('shows loading skeletons when isLoading is true', () => { - render() - - // Check for 3 animated placeholder pills - const skeletons = screen.getAllByTestId('keyword-skeleton') - expect(skeletons).toHaveLength(3) - - // Check that they have the animate-pulse class - skeletons.forEach((skeleton) => { - expect(skeleton).toHaveClass('animate-pulse') - }) - }) - - it('renders keyword chips when keywords provided', () => { - const keywords = ['RAG', 'retrieval', 'question answering'] - render() - - // Check that all keywords are rendered - expect(screen.getByText('RAG')).toBeInTheDocument() - expect(screen.getByText('retrieval')).toBeInTheDocument() - expect(screen.getByText('question answering')).toBeInTheDocument() - - // Check that chips have the correct styling - const chips = screen.getAllByRole('listitem') - chips.forEach((chip) => { - expect(chip).toHaveClass('inline-block', 'rounded-full', 'px-3', 'py-1', 'text-sm', 'font-medium', 'bg-blue-100', 'text-blue-800') - }) - }) - - it('shows "Extracted Keywords" label', () => { - const keywords = ['test'] - render() - - const label = screen.getByText(/extracted keywords:/i) - expect(label).toBeInTheDocument() - expect(label).toHaveClass('text-xs', 'text-gray-500', 'uppercase', 'tracking-wide') - }) - - it('renders chips in a flex container with flex-wrap', () => { - const keywords = ['keyword1', 'keyword2', 'keyword3'] - render() - - const container = screen.getByTestId('keywords-section') - expect(container).toBeInTheDocument() - - const innerContainer = screen.getByTestId('keywords-container') - expect(innerContainer).toHaveClass('flex', 'flex-wrap') - }) - - it('has smooth transition classes on keywords section', () => { - const keywords = ['test'] - render() - - const section = screen.getByTestId('keywords-section') - expect(section).toHaveClass('transition-all', 'duration-300', 'ease-in-out') - }) - - it('renders each keyword chip with mr-2 mb-2 spacing', () => { - const keywords = ['keyword1', 'keyword2'] - render() - - const chips = screen.getAllByRole('listitem') - chips.forEach((chip) => { - expect(chip).toHaveClass('mr-2', 'mb-2') - }) - }) - - it('does not render loading state when keywords are provided', () => { - const keywords = ['test'] - render() - - const skeletons = screen.queryAllByTestId('keyword-skeleton') - expect(skeletons).toHaveLength(0) - }) -}) diff --git a/frontend/src/test/e2e/query_flow.test.tsx b/frontend/src/test/e2e/query_flow.test.tsx index 08bf2f5..71cbd1e 100644 --- a/frontend/src/test/e2e/query_flow.test.tsx +++ b/frontend/src/test/e2e/query_flow.test.tsx @@ -15,7 +15,7 @@ const mockQueryDocument = vi.mocked(queryDocument) const mockIngestDocument = vi.mocked(ingestDocument) const mockQueryResponse: QueryResponse = { - keywords: ['legislative', 'council', 'meeting'], + extracted_questions: ['What happened at the legislative council meeting?', 'What topics were discussed?', 'When was the meeting held?'], answer: '- The meeting was held on January 15\n- Key topics were discussed', sources: [ { @@ -55,7 +55,7 @@ describe('Query flow integration (App-level)', () => { expect(submitButton).toBeDisabled() }) - it('full query flow: type question, submit, see keywords and answer', async () => { + it('full query flow: type question, submit, see questions and answer', async () => { mockQueryDocument.mockResolvedValue(mockQueryResponse) render() @@ -71,10 +71,9 @@ describe('Query flow integration (App-level)', () => { }) await waitFor(() => { - expect(screen.getByText('legislative')).toBeInTheDocument() + expect(screen.getByText('What happened at the legislative council meeting?')).toBeInTheDocument() }) - expect(screen.getByText('council')).toBeInTheDocument() - expect(screen.getByText('meeting')).toBeInTheDocument() + expect(screen.getByText('What topics were discussed?')).toBeInTheDocument() await waitFor(() => { expect(screen.getByText('The meeting was held on January 15')).toBeInTheDocument()