pp-planer/tests/Feature/CcliPasteParserTest.php

133 lines
5.5 KiB
PHP

<?php
use App\Services\CcliPasteParser;
use App\Services\DTO\ParsedCcliSection;
use App\Services\DTO\ParsedCcliSong;
function ccliFixturePath(string $filename): string
{
return base_path("tests/fixtures/ccli/{$filename}");
}
function ccliFixtureContent(string $filename): string
{
return file_get_contents(ccliFixturePath($filename));
}
test('each fixture parses into a valid ParsedCcliSong DTO', function (): void {
$parser = new CcliPasteParser();
foreach (glob(base_path('tests/fixtures/ccli/*.txt')) as $path) {
$filename = basename($path);
$result = $parser->parse(ccliFixtureContent($filename));
expect($result)->toBeInstanceOf(ParsedCcliSong::class);
expect($result->title)->not->toBeEmpty("Fixture {$filename}: title should not be empty");
expect($result->sections)->not->toBeEmpty("Fixture {$filename}: should have at least one section");
foreach ($result->sections as $section) {
expect($section)->toBeInstanceOf(ParsedCcliSection::class);
expect($section->kind)->not->toBeEmpty("Fixture {$filename}: section kind should not be empty");
expect($section->lines)->not->toBeEmpty("Fixture {$filename}: section should have lines");
}
}
});
test('english-only-multi-verse.txt parses 4+ sections without translation', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('english-only-multi-verse.txt'));
expect(count($result->sections))->toBeGreaterThanOrEqual(4);
expect($result->ccliId)->not->toBeNull();
$hasTranslated = false;
foreach ($result->sections as $section) {
if ($section->linesTranslated !== null) {
$hasTranslated = true;
}
}
expect($hasTranslated)->toBeFalse('English-only should have no linesTranslated');
});
test('english-german-side-by-side.txt extracts both languages per section', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('english-german-side-by-side.txt'));
$translatedSections = array_filter($result->sections, fn (ParsedCcliSection $section): bool => $section->linesTranslated !== null);
expect(count($translatedSections))->toBeGreaterThanOrEqual(1, 'Should have at least 1 section with translation');
$first = array_values($translatedSections)[0];
expect($first->lines)->not->toBeEmpty();
expect($first->linesTranslated)->not->toBeEmpty();
});
test('german-only.txt detects German section labels', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('german-only.txt'));
$kinds = array_map(fn (ParsedCcliSection $section): string => $section->kind, $result->sections);
$hasGermanKind = array_filter($kinds, fn (string $kind): bool => in_array(mb_strtolower($kind), ['strophe', 'refrain', 'brücke'], true));
expect(count($hasGermanKind))->toBeGreaterThanOrEqual(1, 'Should detect at least one German section label');
});
test('repeat-marker.txt preserves modifier in section DTO', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('repeat-marker.txt'));
$repeatSections = array_filter($result->sections, fn (ParsedCcliSection $section): bool => $section->modifier !== null);
expect(count($repeatSections))->toBeGreaterThanOrEqual(1, 'Should have at least 1 section with Repeat modifier');
});
test('umlauts.txt preserves Unicode characters', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('umlauts.txt'));
$allText = $result->title;
foreach ($result->sections as $section) {
$allText .= implode(' ', $section->lines);
}
expect((bool) preg_match('/[äöüßÄÖÜ]/u', $allText))->toBeTrue('Umlauts should be preserved');
});
test('missing-copyright.txt returns null copyrightText', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('missing-copyright.txt'));
expect($result->ccliId)->not->toBeNull('CCLI ID should still be extracted');
expect($result->copyrightText)->toBeNull('No © line should mean null copyrightText');
expect($result->year)->toBeNull('No © means no year either');
});
test('5-verses.txt handles 5 verse sections correctly', function (): void {
$parser = new CcliPasteParser();
$result = $parser->parse(ccliFixtureContent('5-verses.txt'));
$verseSections = array_filter($result->sections, fn (ParsedCcliSection $section): bool => in_array(mb_strtolower($section->kind), ['verse', 'strophe'], true));
expect(count($verseSections))->toBeGreaterThanOrEqual(5, 'Should have 5 verse sections');
});
test('parse throws InvalidArgumentException on empty input', function (): void {
$parser = new CcliPasteParser();
expect(fn () => $parser->parse(''))->toThrow(InvalidArgumentException::class);
});
test('parse throws InvalidArgumentException on text with no section labels', function (): void {
$parser = new CcliPasteParser();
expect(fn () => $parser->parse('Just some random text without any section labels'))->toThrow(InvalidArgumentException::class);
});
test('parse error messages are in German', function (): void {
$parser = new CcliPasteParser();
try {
$parser->parse('');
} catch (InvalidArgumentException $exception) {
expect($exception->getMessage())->toMatch('/[A-Za-zÄÖÜäöü]/u');
expect($exception->getMessage())->not->toContain('Error:');
}
});