legco_ai_assistant/frontend/src/test/utils/citationParser.test.ts

108 lines
3.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { processCitations } from '../../utils/citationParser'
import type { SourceMetadata } from '../../types'
const mockSources: SourceMetadata[] = [
{
filename: 'NEC4 ACC.pdf',
upload_date: '2024-01-15',
content_summary: 'Summary',
chunk_index: 0,
page_number: 3,
chunk_file_path: 'chunk_0.pdf',
},
{
filename: 'meeting_notes.docx',
upload_date: '2024-01-16',
content_summary: 'Minutes',
chunk_index: 1,
page_number: null,
chunk_file_path: 'chunk_1.pdf',
},
{
filename: 'report.pdf',
upload_date: '2024-01-17',
content_summary: 'Report',
chunk_index: 2,
page_number: 5,
chunk_file_path: 'chunk_2.pdf',
},
]
describe('processCitations', () => {
it('returns original text when no sources provided', () => {
const text = 'This has [NEC4 ACC.pdf, page 3] citation.'
expect(processCitations(text, [])).toBe(text)
})
it('replaces matched citation with markdown link', () => {
const text = 'Clause info [NEC4 ACC.pdf, page 3] is important.'
const result = processCitations(text, mockSources)
expect(result).toContain('](')
expect(result).toContain('/pdf-viewer')
expect(result).toMatch(/\[NEC4 ACC\.pdf, page 3\]\([^)]+\)/)
})
it('handles filename-only citation for DOCX (no page)', () => {
const text = 'Notes [meeting_notes.docx] from meeting.'
const result = processCitations(text, mockSources)
expect(result).toContain('/pdf-viewer')
expect(result).toContain('meeting_notes.docx')
})
it('leaves unmatched citations as plain text', () => {
const text = 'Unknown source [unknown_file.pdf, page 10] here.'
const result = processCitations(text, mockSources)
expect(result).toBe(text)
})
it('handles multiple citations in same text', () => {
const text = 'A [NEC4 ACC.pdf, page 3] and B [report.pdf, page 5].'
const result = processCitations(text, mockSources)
const linkCount = (result.match(/\[.+?\]\(/g) || []).length
expect(linkCount).toBe(2)
})
it('does not break existing markdown links', () => {
const text = 'See [label](http://example.com) and [NEC4 ACC.pdf, page 3].'
const result = processCitations(text, mockSources)
expect(result).toContain('[label](http://example.com)')
expect(result).toContain('/pdf-viewer')
})
it('does not break markdown images', () => {
const text = '![diagram](http://example.com/img.png) and [NEC4 ACC.pdf, page 3].'
const result = processCitations(text, mockSources)
expect(result).toContain('![diagram]')
expect(result).toContain('/pdf-viewer')
})
it('matches case-insensitively', () => {
const text = 'Cite [nec4 acc.pdf, page 3] lowercase.'
const result = processCitations(text, mockSources)
expect(result).toContain('/pdf-viewer')
})
it('leaves plain bracket text without matching source', () => {
const text = 'Some [plain bracket text] without source.'
const result = processCitations(text, mockSources)
expect(result).toBe(text)
})
it('skips sources without chunk_file_path', () => {
const sourcesWithoutPath = [
{
filename: 'no_path.pdf',
upload_date: '2024-01-18',
content_summary: 'Summary',
chunk_index: 0,
page_number: 1,
chunk_file_path: null,
},
]
const text = 'Source [no_path.pdf, page 1] missing path.'
const result = processCitations(text, sourcesWithoutPath)
expect(result).toBe(text)
})
})