SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
3.4 KB · 99 lines typescript
Raw Blame History
1import { describe, expect, it } from "vitest";2import { detexify, parseArticleSections, parseBeamerFrames, splitLong, stripComments } from "../lib/rag/latex.ts";34const SAMPLE_BEAMER = String.raw`5\documentclass{beamer}6\begin{document}7\begin{frame}[plain,noframenumbering]8\titlepage9\end{frame}10\begin{frame}{Plan de la séance}11\tableofcontents12\end{frame}13\section{Première section}14\begin{frame}{Titre A}15\begin{defbox}[Valeur marchande]16Le prix le plus probable. % commentaire17\end{defbox}18\begin{itemize}19    \item Premier \textbf{point} important20    \item Deuxième point21\end{itemize}22\end{frame}23\begin{frame}{Titre B (1/2)}24Contenu avec formule $V = \frac{RNE}{TGA}$ et tableau :25\begin{tabular}{ll}26\toprule27Poste & Montant \\28Revenus & 100~000 \\29\bottomrule30\end{tabular}31\end{frame}32\end{document}`;3334describe("Parseur beamer", () => {35  const frames = parseBeamerFrames(SAMPLE_BEAMER);3637  it("numérote les diapositives comme le PDF (page titre non numérotée)", () => {38    // Plan = diapositive 1, Titre A = 2, Titre B = 339    const a = frames.find((f) => f.title === "Titre A");40    expect(a?.slideNumber).toBe(2);41  });4243  it("capture la section courante et les boîtes sémantiques", () => {44    const a = frames.find((f) => f.title === "Titre A")!;45    expect(a.sectionTitle).toBe("Première section");46    expect(a.boxTypes).toContain("defbox");47    expect(a.content).toContain("Définition — Valeur marchande");48    expect(a.content).toContain("prix le plus probable");49    expect(a.content).not.toContain("commentaire");50  });5152  it("aplatit les listes et conserve les maths", () => {53    const a = frames.find((f) => f.title === "Titre A")!;54    expect(a.content).toContain("• Premier point important");55    const b = frames.find((f) => f.title.startsWith("Titre B"))!;56    expect(b.content).toContain("$V =");57    expect(b.content).toContain("Revenus | 100 000");58  });59});6061describe("detexify", () => {62  it("convertit les guillemets français et symboles", () => {63    expect(detexify(String.raw`\og valeur \fg{} \rightarrow 15~\%`)).toBe("« valeur » → 15 %");64    expect(detexify(String.raw`a \rightarrow b`)).toContain("→");65  });66  it("supprime les commandes inconnues sans perdre le texte", () => {67    expect(detexify(String.raw`\foobar{} Texte \textcolor{red}{rouge} conservé`)).toContain("Texte rouge conservé");68  });69});7071describe("parseArticleSections", () => {72  it("découpe par sections avec chemin hiérarchique", () => {73    const tex = String.raw`\begin{document}74\section{Grande section}75Texte A suffisant pour être conservé dans la sortie finale.76\subsection{Sous-partie}77Texte B suffisant pour être conservé dans la sortie finale.78\end{document}`;79    const sections = parseArticleSections(tex);80    expect(sections).toHaveLength(2);81    expect(sections[1].path).toBe("Grande section > Sous-partie");82  });83});8485describe("splitLong", () => {86  it("respecte les paragraphes et garde un chevauchement", () => {87    const text = Array.from({ length: 30 }, (_, i) => `Paragraphe ${i} avec du contenu assez long pour compter.`).join("\n\n");88    const parts = splitLong(text, 500);89    expect(parts.length).toBeGreaterThan(2);90    for (const p of parts) expect(p.length).toBeLessThan(700);91  });92});9394describe("stripComments", () => {95  it("préserve les pourcentages échappés", () => {96    expect(stripComments("15\\% du prix % vrai commentaire")).toBe("15\\% du prix ");97  });98});99