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 <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Woody 2026-04-24 16:24:49 +08:00
parent 4c51758348
commit 467d88489c
4 changed files with 97 additions and 102 deletions

View File

@ -46,20 +46,20 @@ export const QueryInput: React.FC<QueryInputProps> = ({ onSubmit, isLoading }) =
rows={3} 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" 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"
/> />
<div className="flex items-start gap-3"> <div className="flex items-center gap-3">
<button <button
type="submit" type="submit"
disabled={isDisabled} disabled={isDisabled}
className="px-4 py-2 bg-blue-600 text-white font-medium rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600 transition-all duration-200" className="shrink-0 px-4 py-2 bg-blue-600 text-white font-medium rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600 transition-all duration-200"
> >
{isLoading ? 'Processing...' : 'Submit'} {isLoading ? 'Processing...' : 'Submit'}
</button> </button>
{submittedQuestion && (
<p data-testid="submitted-question" className="text-sm text-gray-500 italic truncate">
Your question: &ldquo;{submittedQuestion}&rdquo;
</p>
)}
</div> </div>
{submittedQuestion && (
<p data-testid="submitted-question" className="text-sm text-gray-500 italic">
Your question: &ldquo;{submittedQuestion}&rdquo;
</p>
)}
</form> </form>
) )
} }

View File

@ -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(<ExtractedQuestionsDisplay extractedQuestions={[]} isLoading={false} />)
expect(container).toBeEmptyDOMElement()
})
it('returns null when questions is undefined and not loading', () => {
const { container } = render(<ExtractedQuestionsDisplay extractedQuestions={undefined} isLoading={false} />)
expect(container).toBeEmptyDOMElement()
})
it('shows loading skeletons when isLoading is true', () => {
render(<ExtractedQuestionsDisplay extractedQuestions={[]} isLoading={true} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
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(<ExtractedQuestionsDisplay extractedQuestions={questions} isLoading={false} />)
expect(screen.getByTestId('extracted-question-0')).toBeInTheDocument()
expect(screen.getByTestId('extracted-question-1')).toBeInTheDocument()
})
})

View File

@ -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(<KeywordsDisplay keywords={[]} isLoading={false} />)
expect(container).toBeEmptyDOMElement()
})
it('returns null when keywords is undefined and not loading', () => {
const { container } = render(<KeywordsDisplay keywords={undefined} isLoading={false} />)
expect(container).toBeEmptyDOMElement()
})
it('shows loading skeletons when isLoading is true', () => {
render(<KeywordsDisplay keywords={[]} isLoading={true} />)
// 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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
// 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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
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(<KeywordsDisplay keywords={keywords} isLoading={false} />)
const skeletons = screen.queryAllByTestId('keyword-skeleton')
expect(skeletons).toHaveLength(0)
})
})

View File

@ -15,7 +15,7 @@ const mockQueryDocument = vi.mocked(queryDocument)
const mockIngestDocument = vi.mocked(ingestDocument) const mockIngestDocument = vi.mocked(ingestDocument)
const mockQueryResponse: QueryResponse = { 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', answer: '- The meeting was held on January 15\n- Key topics were discussed',
sources: [ sources: [
{ {
@ -55,7 +55,7 @@ describe('Query flow integration (App-level)', () => {
expect(submitButton).toBeDisabled() 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) mockQueryDocument.mockResolvedValue(mockQueryResponse)
render(<App />) render(<App />)
@ -71,10 +71,9 @@ describe('Query flow integration (App-level)', () => {
}) })
await waitFor(() => { 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('What topics were discussed?')).toBeInTheDocument()
expect(screen.getByText('meeting')).toBeInTheDocument()
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('The meeting was held on January 15')).toBeInTheDocument() expect(screen.getByText('The meeting was held on January 15')).toBeInTheDocument()