167 lines
6.3 KiB
TypeScript
167 lines
6.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
||||
|
|
import {
|
|||
|
|
extractAllFilms,
|
|||
|
|
extractAllSongs,
|
|||
|
|
extractAllBooks,
|
|||
|
|
extractAllTVSeries,
|
|||
|
|
extractAllPlaces,
|
|||
|
|
extractAllImages,
|
|||
|
|
extractMagazineSections,
|
|||
|
|
stripContentTags,
|
|||
|
|
} from '../contentExtraction'
|
|||
|
|
|
|||
|
|
// ─── Edge cases ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
describe('contentExtraction edge cases', () => {
|
|||
|
|
it('handles empty string', () => {
|
|||
|
|
expect(extractAllFilms('')).toEqual([])
|
|||
|
|
expect(extractAllSongs('', '')).toEqual([])
|
|||
|
|
expect(extractAllBooks('', '')).toEqual([])
|
|||
|
|
expect(extractAllTVSeries('', '')).toEqual([])
|
|||
|
|
expect(extractAllPlaces('', '')).toEqual([])
|
|||
|
|
expect(extractAllImages('', '')).toEqual([])
|
|||
|
|
expect(extractMagazineSections('')).toEqual([])
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles text with no tags', () => {
|
|||
|
|
const text = 'Just a regular response about movies and music.'
|
|||
|
|
expect(extractAllFilms(text)).toEqual([])
|
|||
|
|
expect(extractAllSongs(text, '')).toEqual([])
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles interleaved tags of different types', () => {
|
|||
|
|
const text = `
|
|||
|
|
Here are some recommendations:
|
|||
|
|
[[film_ext:Inception|2010|Christopher Nolan]]
|
|||
|
|
Then a great song:
|
|||
|
|
[[song_ext:Bohemian Rhapsody|Queen|1975]]
|
|||
|
|
And a book:
|
|||
|
|
[[book_ext:Dune|Frank Herbert|1965]]
|
|||
|
|
`
|
|||
|
|
const films = extractAllFilms(text)
|
|||
|
|
const songs = extractAllSongs(text, '')
|
|||
|
|
const books = extractAllBooks(text, '')
|
|||
|
|
expect(films).toHaveLength(1)
|
|||
|
|
expect(films[0].title).toBe('Inception')
|
|||
|
|
expect(songs).toHaveLength(1)
|
|||
|
|
expect(songs[0].title).toBe('Bohemian Rhapsody')
|
|||
|
|
expect(books).toHaveLength(1)
|
|||
|
|
expect(books[0].title).toBe('Dune')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles malformed tags (missing fields)', () => {
|
|||
|
|
// film_ext requires title|year|director — missing any means no match
|
|||
|
|
expect(extractAllFilms('[[film_ext:Inception]]')).toHaveLength(0)
|
|||
|
|
expect(extractAllFilms('[[film_ext:Inception|2010]]')).toHaveLength(0)
|
|||
|
|
|
|||
|
|
// All 3 fields present — matches
|
|||
|
|
const films = extractAllFilms('[[film_ext:Inception|2010|Christopher Nolan]]')
|
|||
|
|
expect(films).toHaveLength(1)
|
|||
|
|
expect(films[0].title).toBe('Inception')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles unicode content in tags', () => {
|
|||
|
|
const text = '[[film_ext:千と千尋の神隠し|2001|宮崎駿]]'
|
|||
|
|
const films = extractAllFilms(text)
|
|||
|
|
expect(films).toHaveLength(1)
|
|||
|
|
expect(films[0].title).toBe('千と千尋の神隠し')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles tags with extra whitespace in pipe-separated values', () => {
|
|||
|
|
// Regex captures include leading/trailing spaces in groups
|
|||
|
|
// but the tag format requires no spaces around brackets
|
|||
|
|
const text = '[[film_ext:Inception|2010|Christopher Nolan]]'
|
|||
|
|
const films = extractAllFilms(text)
|
|||
|
|
expect(films).toHaveLength(1)
|
|||
|
|
expect(films[0].title).toBe('Inception')
|
|||
|
|
expect(films[0].director).toBe('Christopher Nolan')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles duplicate tags (same title)', () => {
|
|||
|
|
const text = `
|
|||
|
|
[[film_ext:Inception|2010|Christopher Nolan]]
|
|||
|
|
[[film_ext:Inception|2010|Christopher Nolan]]
|
|||
|
|
`
|
|||
|
|
const films = extractAllFilms(text)
|
|||
|
|
// Should deduplicate
|
|||
|
|
expect(films).toHaveLength(1)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles place_ext with all fields', () => {
|
|||
|
|
const text = '[[place_ext:Sushi Nakazawa|40.7258|-74.0030|Japanese|$$$$|New York]]'
|
|||
|
|
const places = extractAllPlaces(text, '')
|
|||
|
|
expect(places).toHaveLength(1)
|
|||
|
|
expect(places[0].name).toBe('Sushi Nakazawa')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles tv_ext tags', () => {
|
|||
|
|
const text = '[[tv_ext:Breaking Bad|2008|Vince Gilligan]]'
|
|||
|
|
const series = extractAllTVSeries(text, '')
|
|||
|
|
expect(series).toHaveLength(1)
|
|||
|
|
expect(series[0].title).toBe('Breaking Bad')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('stripContentTags removes all tag types', () => {
|
|||
|
|
const text = 'Watch [[film_ext:Inception|2010|Nolan]] and listen to [[song_ext:Song|Artist|2020]]'
|
|||
|
|
const stripped = stripContentTags(text)
|
|||
|
|
expect(stripped).not.toContain('[[')
|
|||
|
|
expect(stripped).not.toContain(']]')
|
|||
|
|
expect(stripped).toContain('Watch')
|
|||
|
|
expect(stripped).toContain('and listen to')
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('handles magazine sections with bold markers', () => {
|
|||
|
|
const text = `
|
|||
|
|
- **Bitcoin rallies**: Price surges 5% as ETF inflows hit record.
|
|||
|
|
- **Lightning Network growth**: Capacity doubles in Q1 2026.
|
|||
|
|
- **Mining difficulty**: New all-time high reached.
|
|||
|
|
`
|
|||
|
|
const sections = extractMagazineSections(text)
|
|||
|
|
expect(sections.length).toBeGreaterThanOrEqual(1)
|
|||
|
|
// First section is a summary grouping the items
|
|||
|
|
const titles = sections.map(s => s.title)
|
|||
|
|
expect(titles.length).toBeGreaterThan(0)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
describe('description pairing (operator-reported: cards carried the WRONG description)', () => {
|
|||
|
|
// Verbatim shape of the Bitcoin-films answer that produced mis-paired cards.
|
|||
|
|
// The model's prose was correct; the parser paired each title with the
|
|||
|
|
// PREVIOUS line because `(?:^|\n)`-anchored patterns report m.index at the
|
|||
|
|
// newline, shifting the line window back by one line.
|
|||
|
|
const TRANSCRIPT = [
|
|||
|
|
'Here are some films about Bitcoin:',
|
|||
|
|
'',
|
|||
|
|
'Documentaries:',
|
|||
|
|
'',
|
|||
|
|
'- The Rise and Rise of Bitcoin (2014) – Early documentary following Bitcoin\'s emergence and community.',
|
|||
|
|
'- Banking on Bitcoin (2016) – Explores Bitcoin\'s origins and its potential to disrupt finance.',
|
|||
|
|
'- Cryptopia (2020) – Examines both the promise and pitfalls of crypto.',
|
|||
|
|
].join('\n')
|
|||
|
|
|
|||
|
|
it('does not caption a title with the previous item\'s description', () => {
|
|||
|
|
const series = extractAllTVSeries(TRANSCRIPT, 'recommend me some bitcoin series')
|
|||
|
|
const banking = series.find(s => /Banking on Bitcoin/i.test(s.title))
|
|||
|
|
if (banking?.synopsis) {
|
|||
|
|
expect(banking.synopsis).not.toMatch(/Early documentary/i)
|
|||
|
|
expect(banking.synopsis).toMatch(/Explores Bitcoin/i)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('does not bleed a section header into the first card of a group', () => {
|
|||
|
|
const series = extractAllTVSeries(TRANSCRIPT, 'recommend me some bitcoin series')
|
|||
|
|
for (const s of series) {
|
|||
|
|
expect(s.synopsis ?? '').not.toMatch(/Documentaries:/i)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
it('keeps each item paired with its OWN description', () => {
|
|||
|
|
const series = extractAllTVSeries(TRANSCRIPT, 'recommend me some bitcoin series')
|
|||
|
|
const rise = series.find(s => /Rise and Rise/i.test(s.title))
|
|||
|
|
if (rise?.synopsis) {
|
|||
|
|
expect(rise.synopsis).toMatch(/Early documentary/i)
|
|||
|
|
expect(rise.synopsis).not.toMatch(/Explores Bitcoin/i)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|