import { describe, expect, it } from 'vitest' import { extractFormulas, extractSourceAssets } from '@/lib/ai/services/slide-source-assets' import { assertDeckHasSubstance, injectEquationSlidesFromFormulas, normalizeSlideDeck, } from '@/lib/ai/services/slide-content-quality' import { buildPresentationHTML } from '@/lib/ai/tools/slides-html-builder' import { encodeSlideIntentDescription, decodeSlideIntentDescription } from '@/lib/ai/services/slide-intent' describe('extractFormulas for STEM notes', () => { it('extracts display math $$...$$', () => { const text = 'On étudie $$\\frac{dy}{dx} + P(x)y = Q(x)$$ et la solution.' const f = extractFormulas(text) expect(f.some((x) => x.includes('dy') || x.includes('frac'))).toBe(true) }) it('extracts equation-like lines', () => { const text = 'La dérivée:\ny\' + 2y = e^x\nPuis on intègre.' const f = extractFormulas(text) expect(f.length).toBeGreaterThan(0) }) it('flags math domain for differential equations notes', () => { const assets = extractSourceAssets( 'Cours sur les équations différentielles. $$y\' + P(x)y = Q(x)$$. Méthode du facteur intégrant.', ) expect(assets.hasMath).toBe(true) expect(assets.formulas.length).toBeGreaterThan(0) }) }) describe('equation slides render with katex hooks', () => { it('emits data-latex and katex CDN for equation slides', () => { const deck = normalizeSlideDeck({ title: 'EDO', theme: 'clinical-precision', slides: [ { type: 'title', title: 'Équations différentielles', subtitle: 'Cours' }, { type: 'equation', title: 'Forme générale', equations: [{ latex: "y' + P(x)y = Q(x)", label: 'Linéaire 1er ordre' }], explanation: 'Forme standard', }, { type: 'bullets', title: 'Méthode', items: ['Identifier P et Q', 'Calculer le facteur intégrant', 'Intégrer les deux membres'], }, { type: 'summary', title: 'À retenir', items: ['Forme standard', 'Facteur intégrant', 'Condition initiale'], }, ], }) expect(assertDeckHasSubstance(deck).ok).toBe(true) const html = buildPresentationHTML({ title: deck.title, theme: deck.theme, slides: deck.slides as any, }) expect(html).toContain('katex') expect(html).toContain('data-latex') expect(html).toContain("y' + P(x)y = Q(x)") // cards grid must not force 2 empty columns for 1 card const oneCard = buildPresentationHTML({ title: 't', theme: 'architectural-saas', slides: [ { type: 'title', title: 'T' }, { type: 'cards', title: 'One', cards: [{ title: 'Seul', description: 'Plein largeur' }], }, { type: 'summary', title: 'S', items: ['a', 'b'] }, ] as any, }) expect(oneCard).toContain('repeat(1,minmax(0,1fr))') }) }) describe('STEM gate + model + intent', () => { it('fails substance when formulas required but no equation latex', () => { const deck = normalizeSlideDeck({ title: 'EDO', slides: [ { type: 'title', title: 'EDO' }, { type: 'bullets', title: 'Intro', items: ['a', 'b', 'c'] }, { type: 'summary', title: 'Fin', items: ['x', 'y'] }, ], }) const gate = assertDeckHasSubstance(deck, { requireFormulas: ["y' + P y = Q"], }) expect(gate.ok).toBe(false) }) it('injects equation slides from formulas', () => { const thin = normalizeSlideDeck({ title: 'EDO', theme: 'clinical-precision', slides: [ { type: 'title', title: 'EDO', subtitle: 'Cours' }, { type: 'bullets', title: 'Méthode', items: ['A', 'B', 'C'] }, { type: 'summary', title: 'Fin', items: ['1', '2'] }, ], }) const filled = injectEquationSlidesFromFormulas(thin, ["y' + P(x)y = Q(x)", '\\mu(x) = e^{\\int P}'], { lang: 'fr', }) expect(filled.slides.some((s) => s.type === 'equation')).toBe(true) const gate = assertDeckHasSubstance(filled, { requireFormulas: ["y' + P(x)y = Q(x)"], }) expect(gate.ok).toBe(true) }) it('encodes and decodes slide intent', () => { const enc = encodeSlideIntentDescription({ purpose: 'course', audience: 'student', slideCount: 6, template: 'auto', }) const dec = decodeSlideIntentDescription(enc) expect(dec.purpose).toBe('course') expect(dec.audience).toBe('student') expect(dec.slideCount).toBe(6) }) })