dorfteich/packages/plugins/chordpro/src/chordpro.test.ts
Claude Fable 5 a879561ec7
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m30s
CI / Build container images (pull_request) Successful in 10s
CI / Auth e2e pack (pull_request) Successful in 7m39s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 17s
CD / Deploy to Test (push) Successful in 14s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m46s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m1s
CI / Import/export fidelity gate (push) Successful in 54s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 44s
Prod deploy / Deploy the released images to Prod (push) Successful in 15s
#155: ChordPro-Plugin — Akkordblätter/Leadsheets als Block
Neues Referenz-Plugin packages/plugins/chordpro nach dem
mermaid-Muster: bewusst ohne Fremdbibliothek (Supply-Chain-Lehre aus
#136) — eigener minimaler ChordPro-Parser (Direktiven title/subtitle/
artist/key/capo/tempo/comment, Chorus-Fences, [Akkord]-Marker,
#-Kommentare) plus SVG-Formatter mit Monospace-Raster: Akkorde über dem
Text, Titelkopf, Chorus-Einrückung, XML-escaped. Persistenz {source,
svg} — der Snapshot bedient Lesemodus, Public-Ansicht und Exporte über
den PluginFallbackRenderer. Edit-Modus: Textarea + debounced
Live-Preview. ZIP 20 KB (Limits 64/256 MiB), 4 Parser-/Formatter-Tests,
i18n de+en, Doku-Listen (site-admin en+de, plugin-architecture)
ergänzt. Prod-Installation wie üblich per Site-Admin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
2026-07-20 02:11:36 +02:00

60 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { chordProToSvg, parseChordPro } from './chordpro';
const SHEET = `{title: Bühnenlied}
{artist: Nadia Morgenstern}
{key: C}
# internes Kommentar — unsichtbar
[C]Hallo du [G]schöne [Am]Welt
Ohne Akkorde geht es [F]auch
{start_of_chorus}
{comment: Refrain}
[C]La la [G]la
{end_of_chorus}`;
describe('chordpro parser + svg formatter (issue #155)', () => {
it('parses directives, chords with columns, comments, and chorus', () => {
const sheet = parseChordPro(SHEET);
expect(sheet.meta).toMatchObject({
title: 'Bühnenlied',
subtitle: 'Nadia Morgenstern',
key: 'C',
});
const first = sheet.lines[0]!;
expect(first.lyrics).toBe('Hallo du schöne Welt');
expect(first.chords).toEqual([
{ at: 0, chord: 'C' },
{ at: 9, chord: 'G' },
{ at: 16, chord: 'Am' },
]);
const chorusLine = sheet.lines.find((l) => l.chorus && l.kind === 'pair')!;
expect(chorusLine.lyrics).toBe('La la la');
const comment = sheet.lines.find((l) => l.kind === 'comment')!;
expect(comment.lyrics).toBe('Refrain');
// `#` lines never show up.
expect(sheet.lines.some((l) => l.lyrics.includes('internes'))).toBe(false);
});
it('renders a self-contained SVG with title, chords, and umlauts escaped safely', () => {
const svg = chordProToSvg(SHEET);
expect(svg).toMatch(/^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg"/);
expect(svg).toContain('Bühnenlied');
expect(svg).toContain('>Am</text>');
expect(svg).toContain('Hallo du schöne Welt');
expect(svg).not.toContain('<script');
});
it('escapes XML-hostile input', () => {
const svg = chordProToSvg('{title: <img src=x>}\n[C]a & b < c');
expect(svg).toContain('&lt;img src=x&gt;');
expect(svg).toContain('a &amp; b &lt; c');
});
it('throws on an empty sheet so the plugin can show its hint', () => {
expect(() => chordProToSvg(' \n# nur Kommentar')).toThrow();
});
});