import { describe, expect, it } from "vitest"; import { detexify, parseArticleSections, parseBeamerFrames, splitLong, stripComments } from "../lib/rag/latex.ts"; const SAMPLE_BEAMER = String.raw` \documentclass{beamer} \begin{document} \begin{frame}[plain,noframenumbering] \titlepage \end{frame} \begin{frame}{Plan de la séance} \tableofcontents \end{frame} \section{Première section} \begin{frame}{Titre A} \begin{defbox}[Valeur marchande] Le prix le plus probable. % commentaire \end{defbox} \begin{itemize} \item Premier \textbf{point} important \item Deuxième point \end{itemize} \end{frame} \begin{frame}{Titre B (1/2)} Contenu avec formule $V = \frac{RNE}{TGA}$ et tableau : \begin{tabular}{ll} \toprule Poste & Montant \\ Revenus & 100~000 \\ \bottomrule \end{tabular} \end{frame} \end{document}`; describe("Parseur beamer", () => { const frames = parseBeamerFrames(SAMPLE_BEAMER); it("numérote les diapositives comme le PDF (page titre non numérotée)", () => { // Plan = diapositive 1, Titre A = 2, Titre B = 3 const a = frames.find((f) => f.title === "Titre A"); expect(a?.slideNumber).toBe(2); }); it("capture la section courante et les boîtes sémantiques", () => { const a = frames.find((f) => f.title === "Titre A")!; expect(a.sectionTitle).toBe("Première section"); expect(a.boxTypes).toContain("defbox"); expect(a.content).toContain("Définition — Valeur marchande"); expect(a.content).toContain("prix le plus probable"); expect(a.content).not.toContain("commentaire"); }); it("aplatit les listes et conserve les maths", () => { const a = frames.find((f) => f.title === "Titre A")!; expect(a.content).toContain("• Premier point important"); const b = frames.find((f) => f.title.startsWith("Titre B"))!; expect(b.content).toContain("$V ="); expect(b.content).toContain("Revenus | 100 000"); }); }); describe("detexify", () => { it("convertit les guillemets français et symboles", () => { expect(detexify(String.raw`\og valeur \fg{} \rightarrow 15~\%`)).toBe("« valeur » → 15 %"); expect(detexify(String.raw`a \rightarrow b`)).toContain("→"); }); it("supprime les commandes inconnues sans perdre le texte", () => { expect(detexify(String.raw`\foobar{} Texte \textcolor{red}{rouge} conservé`)).toContain("Texte rouge conservé"); }); }); describe("parseArticleSections", () => { it("découpe par sections avec chemin hiérarchique", () => { const tex = String.raw`\begin{document} \section{Grande section} Texte A suffisant pour être conservé dans la sortie finale. \subsection{Sous-partie} Texte B suffisant pour être conservé dans la sortie finale. \end{document}`; const sections = parseArticleSections(tex); expect(sections).toHaveLength(2); expect(sections[1].path).toBe("Grande section > Sous-partie"); }); }); describe("splitLong", () => { it("respecte les paragraphes et garde un chevauchement", () => { const text = Array.from({ length: 30 }, (_, i) => `Paragraphe ${i} avec du contenu assez long pour compter.`).join("\n\n"); const parts = splitLong(text, 500); expect(parts.length).toBeGreaterThan(2); for (const p of parts) expect(p.length).toBeLessThan(700); }); }); describe("stripComments", () => { it("préserve les pourcentages échappés", () => { expect(stripComments("15\\% du prix % vrai commentaire")).toBe("15\\% du prix "); }); });