Skip to content

Commit

Permalink
Merge branch 'PSL-US-7770-UnitTest' of https://github.com/microsoft/B…
Browse files Browse the repository at this point in the history
  • Loading branch information
Kiran-Siluveru-Microsoft committed Oct 10, 2024
2 parents 400ea6d + 420ba70 commit d8bf812
Show file tree
Hide file tree
Showing 4 changed files with 545 additions and 1 deletion.
223 changes: 223 additions & 0 deletions ResearchAssistant/App/frontend/src/components/Answer/Answer.test.tsx
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()
})
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { useBoolean } from "@fluentui/react-hooks"
import { FontIcon, Stack, Text } from "@fluentui/react";

import React from "react";
import styles from "./Answer.module.css";

import { AskResponse, Citation } from "../../api";
Expand Down
Loading

0 comments on commit d8bf812

Please sign in to comment.