-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'PSL-US-7770-UnitTest' of https://github.com/microsoft/B…
…uild-your-own-copilot-Solution-Accelerator into PSL-US-7770-UnitTest
- Loading branch information
Showing
4 changed files
with
545 additions
and
1 deletion.
There are no files selected for viewing
223 changes: 223 additions & 0 deletions
223
ResearchAssistant/App/frontend/src/components/Answer/Answer.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
import React from 'react' | ||
import { render, fireEvent } from '@testing-library/react' | ||
import { Answer } from './Answer' | ||
import { type AskResponse, type Citation } from '../../api' | ||
import { debug } from 'console' | ||
|
||
// Mock cloneDeep directly in the test file | ||
jest.mock('lodash-es', () => ({ | ||
cloneDeep: jest.fn((value) => { | ||
return JSON.parse(JSON.stringify(value)) // Simple deep clone implementation | ||
}) | ||
})) | ||
jest.mock('remark-supersub', () => () => {}) | ||
jest.mock('remark-gfm', () => () => {}) | ||
jest.mock('rehype-raw', () => () => {}) | ||
|
||
const mockCitations = [ | ||
{ | ||
chunk_id: '0', | ||
content: 'Citation 1', | ||
filepath: 'path/to/doc1', | ||
id: '1', | ||
reindex_id: '1', // Updated to match the expected structure | ||
title: 'Title 1', | ||
url: 'http://example.com/doc1', | ||
metadata: null | ||
}, | ||
{ | ||
chunk_id: '1', | ||
content: 'Citation 2', | ||
filepath: 'path/to/doc2', | ||
id: '2', | ||
reindex_id: '2', // Updated to match the expected structure | ||
title: 'Title 2', | ||
url: 'http://example.com/doc2', | ||
metadata: null | ||
} | ||
] | ||
|
||
const mockAnswer: AskResponse = { | ||
answer: 'This is the answer with citations [doc1] and [doc2].', | ||
citations: mockCitations | ||
} | ||
|
||
type OnCitationClicked = (citedDocument: Citation) => void | ||
|
||
describe('Answer component', () => { | ||
let onCitationClicked: OnCitationClicked | ||
|
||
beforeEach(() => { | ||
onCitationClicked = jest.fn() | ||
}) | ||
|
||
test('toggles the citation accordion on chevron click', () => { | ||
const { getByLabelText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByLabelText(/Open references/i) // Changed to aria-label | ||
|
||
fireEvent.click(toggleButton) | ||
|
||
const citationFilename1 = getByLabelText(/path\/to\/doc1 - Part 1/i) | ||
const citationFilename2 = getByLabelText(/path\/to\/doc2 - Part 2/i) | ||
|
||
expect(citationFilename1).toBeInTheDocument() | ||
expect(citationFilename2).toBeInTheDocument() | ||
}) | ||
|
||
test('creates the citation filepath correctly', () => { | ||
const { getByLabelText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByLabelText(/Open references/i) // Changed to aria-label | ||
fireEvent.click(toggleButton) | ||
|
||
const citationFilename1 = getByLabelText(/path\/to\/doc1 - Part 1/i) | ||
const citationFilename2 = getByLabelText(/path\/to\/doc2 - Part 2/i) | ||
|
||
expect(citationFilename1).toBeInTheDocument() | ||
expect(citationFilename2).toBeInTheDocument() | ||
}) | ||
|
||
// Ensure to also test the initial state in another test | ||
test('initially renders with the accordion collapsed', () => { | ||
const { getByLabelText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByLabelText(/Open references/i) | ||
|
||
// Check the initial aria-expanded state | ||
expect(toggleButton).not.toHaveAttribute('aria-expanded') | ||
}) | ||
|
||
test('handles keyboard events to open the accordion and click citations', () => { | ||
const onCitationClicked = jest.fn() | ||
const { getByText, debug } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByText(/2 references/i) | ||
fireEvent.click(toggleButton) | ||
debug() | ||
|
||
const citationLink = getByText(/path\/to\/doc1/i) | ||
expect(citationLink).toBeInTheDocument() | ||
|
||
fireEvent.click(citationLink) | ||
|
||
// Adjusted expectation to match the structure including metadata | ||
expect(onCitationClicked).toHaveBeenCalledWith({ | ||
chunk_id: '0', | ||
content: 'Citation 1', | ||
filepath: 'path/to/doc1', | ||
id: '1', | ||
metadata: null, // Include this field | ||
reindex_id: '1', | ||
title: 'Title 1', | ||
url: 'http://example.com/doc1' | ||
}) | ||
}) | ||
|
||
test('handles keyboard events to click citations', () => { | ||
const { getByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByText(/2 references/i) | ||
fireEvent.click(toggleButton) | ||
|
||
const citationLink = getByText(/path\/to\/doc1/i) | ||
expect(citationLink).toBeInTheDocument() | ||
|
||
fireEvent.keyDown(citationLink, { key: 'Enter', code: 'Enter' }) | ||
expect(onCitationClicked).toHaveBeenCalledWith(mockCitations[0]) | ||
|
||
fireEvent.keyDown(citationLink, { key: ' ', code: 'Space' }) | ||
expect(onCitationClicked).toHaveBeenCalledTimes(2) // Now test's called again | ||
}) | ||
|
||
test('calls onCitationClicked when a citation is clicked', () => { | ||
const { getByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByText('2 references') | ||
fireEvent.click(toggleButton) | ||
|
||
const citationLink = getByText('path/to/doc1 - Part 1') | ||
fireEvent.click(citationLink) | ||
|
||
expect(onCitationClicked).toHaveBeenCalledWith(mockCitations[0]) | ||
}) | ||
|
||
test('renders the answer text correctly', () => { | ||
const { getByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
expect(getByText(/This is the answer with citations/i)).toBeInTheDocument() | ||
expect(getByText(/references/i)).toBeInTheDocument() | ||
}) | ||
|
||
test('displays correct number of citations', () => { | ||
const { getByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
expect(getByText('2 references')).toBeInTheDocument() | ||
}) | ||
|
||
test('toggles the citation accordion on click', () => { | ||
const { getByText, queryByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
const toggleButton = getByText('2 references') | ||
|
||
// Initially, citations should not be visible | ||
expect(queryByText('path/to/doc1 - Part 1')).not.toBeInTheDocument() | ||
expect(queryByText('path/to/doc2 - Part 2')).not.toBeInTheDocument() | ||
|
||
// Click to open the accordion | ||
fireEvent.click(toggleButton) | ||
|
||
// Now citations should be visible | ||
expect(getByText('path/to/doc1 - Part 1')).toBeInTheDocument() | ||
expect(getByText('path/to/doc2 - Part 2')).toBeInTheDocument() | ||
}) | ||
|
||
test('displays disclaimer text', () => { | ||
const { getByText } = render(<Answer answer={mockAnswer} onCitationClicked={onCitationClicked} />) | ||
|
||
expect(getByText(/AI-generated content may be incorrect/i)).toBeInTheDocument() | ||
}) | ||
test('creates citation filepath correctly without truncation', () => { | ||
const { getByLabelText, getByText } = render(<Answer answer={mockAnswer} onCitationClicked={jest.fn()} />) | ||
debug() | ||
const toggleButton = getByLabelText(/Open references/i) | ||
fireEvent.click(toggleButton) | ||
|
||
expect(getByText('path/to/doc1 - Part 1')).toBeInTheDocument() | ||
}) | ||
|
||
test('creates citation filepath correctly without truncation', () => { | ||
const { getByLabelText, getByText } = render(<Answer answer={mockAnswer} onCitationClicked={jest.fn()} />) | ||
debug() | ||
const toggleButton = getByLabelText(/Open references/i) | ||
fireEvent.click(toggleButton) | ||
|
||
// Check for the citations that should be rendered | ||
expect(getByText('path/to/doc1 - Part 1')).toBeInTheDocument() | ||
expect(getByText('path/to/doc2 - Part 2')).toBeInTheDocument() | ||
// Remove this if 'Citation 3' is not expected | ||
expect(getByText('2 references')).toBeInTheDocument() // Ensure this citation exists in the mock | ||
}) | ||
|
||
test('handles fallback case for citations without filepath or ids', () => { | ||
const { getByLabelText, getByText } = render(<Answer answer={mockAnswer} onCitationClicked={jest.fn()} />) | ||
debug() | ||
const toggleButton = getByLabelText(/Open references/i) | ||
fireEvent.click(toggleButton) | ||
debug() | ||
// This check is to ensure the fallback citation is rendered | ||
expect(getByText('2 references')).toBeInTheDocument() | ||
}) | ||
|
||
test('renders the citations even if some are invalid', () => { | ||
const { getByLabelText, getByText } = render(<Answer answer={mockAnswer} onCitationClicked={jest.fn()} />) | ||
|
||
const toggleButton = getByLabelText(/Open references/i) | ||
fireEvent.click(toggleButton) | ||
debug() | ||
// Check if 'Citation 3' appears in the document | ||
expect(getByText(/2 references/i)).toBeInTheDocument() // Use regex for flexibility | ||
expect(getByText('path/to/doc1 - Part 1')).toBeInTheDocument() | ||
expect(getByText('path/to/doc2 - Part 2')).toBeInTheDocument() | ||
}) | ||
}) |
2 changes: 1 addition & 1 deletion
2
ResearchAssistant/App/frontend/src/components/Answer/Answer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.