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:
parent
4c51758348
commit
467d88489c
|
|
@ -46,20 +46,20 @@ export const QueryInput: React.FC<QueryInputProps> = ({ 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"
|
||||
/>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
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'}
|
||||
</button>
|
||||
</div>
|
||||
{submittedQuestion && (
|
||||
<p data-testid="submitted-question" className="text-sm text-gray-500 italic">
|
||||
<p data-testid="submitted-question" className="text-sm text-gray-500 italic truncate">
|
||||
Your question: “{submittedQuestion}”
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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(<App />)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue