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}
|
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>
|
||||||
</div>
|
|
||||||
{submittedQuestion && (
|
{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}”
|
Your question: “{submittedQuestion}”
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</form>
|
</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 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()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue