toBeInstanceOf(CcliPasteParser::class); }); test('CcliPasteParser can be instantiated with closure injections', function (): void { $parser = new CcliPasteParser( sectionDetector: fn (string $line): bool => str_starts_with($line, 'Verse'), metadataDetector: fn (string $line): bool => str_contains($line, '©'), ); expect($parser)->toBeInstanceOf(CcliPasteParser::class); }); test('CcliPasteParser resolves from Laravel container', function (): void { $parser = app(CcliPasteParser::class); expect($parser)->toBeInstanceOf(CcliPasteParser::class); }); test('CcliPasteParser::parse returns ParsedCcliSong DTO', function (): void { $parser = new CcliPasteParser(); $result = $parser->parse("Test Song\nTest Artist\n\nVerse 1\nSome text"); expect($result)->toBeInstanceOf(ParsedCcliSong::class); }); test('ParsedCcliSong DTO has all required properties', function (): void { $song = new ParsedCcliSong( title: 'Test Song', author: 'Test Author', ccliId: '9999001', year: '2024', copyrightText: '© 2024 Test', sourceUrl: 'https://songselect.ccli.com/Songs/9999001', sections: [], ); expect($song->title)->toBe('Test Song'); expect($song->author)->toBe('Test Author'); expect($song->ccliId)->toBe('9999001'); expect($song->year)->toBe('2024'); expect($song->copyrightText)->toBe('© 2024 Test'); expect($song->sourceUrl)->toContain('songselect.ccli.com'); expect($song->sections)->toBeArray(); }); test('ParsedCcliSection DTO has all required properties including linesTranslated', function (): void { $section = new ParsedCcliSection( label: 'Verse 1', kind: 'Verse', number: '1', modifier: null, lines: ['Line 1', 'Line 2'], linesTranslated: ['Zeile 1', 'Zeile 2'], ); expect($section->label)->toBe('Verse 1'); expect($section->kind)->toBe('Verse'); expect($section->number)->toBe('1'); expect($section->modifier)->toBeNull(); expect($section->lines)->toBe(['Line 1', 'Line 2']); expect($section->linesTranslated)->toBe(['Zeile 1', 'Zeile 2']); }); test('ParsedCcliSection linesTranslated defaults to null', function (): void { $section = new ParsedCcliSection( label: 'Chorus', kind: 'Chorus', number: null, modifier: null, lines: ['Line 1'], ); expect($section->linesTranslated)->toBeNull(); }); test('CcliPasteParser can be injected with closure override in container', function (): void { $called = false; $fakeParser = new CcliPasteParser( sectionDetector: function () use (&$called): bool { $called = true; return true; }, ); app()->instance(CcliPasteParser::class, $fakeParser); $resolved = app(CcliPasteParser::class); expect($resolved)->toBe($fakeParser); app()->forgetInstance(CcliPasteParser::class); });