feat(ccli): scaffold CcliPasteParser service
This commit is contained in:
parent
85608f774d
commit
55a3ea3df8
33
app/Services/CcliPasteParser.php
Normal file
33
app/Services/CcliPasteParser.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Services\DTO\ParsedCcliSong;
|
||||
use Closure;
|
||||
|
||||
final class CcliPasteParser
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ?Closure $sectionDetector = null,
|
||||
private readonly ?Closure $metadataDetector = null,
|
||||
) {
|
||||
if ($sectionDetector !== null || $metadataDetector !== null) {
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(string $rawText): ParsedCcliSong
|
||||
{
|
||||
if (strlen($rawText) < 0) {
|
||||
}
|
||||
|
||||
return new ParsedCcliSong(
|
||||
title: '',
|
||||
author: null,
|
||||
ccliId: null,
|
||||
year: null,
|
||||
copyrightText: null,
|
||||
sourceUrl: null,
|
||||
sections: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
17
app/Services/DTO/ParsedCcliSection.php
Normal file
17
app/Services/DTO/ParsedCcliSection.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\DTO;
|
||||
|
||||
final readonly class ParsedCcliSection
|
||||
{
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $kind,
|
||||
public ?string $number,
|
||||
public ?string $modifier,
|
||||
/** @var string[] */
|
||||
public array $lines,
|
||||
/** @var string[]|null */
|
||||
public ?array $linesTranslated = null,
|
||||
) {}
|
||||
}
|
||||
17
app/Services/DTO/ParsedCcliSong.php
Normal file
17
app/Services/DTO/ParsedCcliSong.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\DTO;
|
||||
|
||||
final readonly class ParsedCcliSong
|
||||
{
|
||||
public function __construct(
|
||||
public string $title,
|
||||
public ?string $author,
|
||||
public ?string $ccliId,
|
||||
public ?string $year,
|
||||
public ?string $copyrightText,
|
||||
public ?string $sourceUrl,
|
||||
/** @var ParsedCcliSection[] */
|
||||
public array $sections,
|
||||
) {}
|
||||
}
|
||||
102
tests/Feature/CcliPasteParserScaffoldTest.php
Normal file
102
tests/Feature/CcliPasteParserScaffoldTest.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
use App\Services\CcliPasteParser;
|
||||
use App\Services\DTO\ParsedCcliSection;
|
||||
use App\Services\DTO\ParsedCcliSong;
|
||||
|
||||
test('CcliPasteParser can be instantiated with no arguments', function (): void {
|
||||
$parser = new CcliPasteParser;
|
||||
|
||||
expect($parser)->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('some 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);
|
||||
});
|
||||
Loading…
Reference in a new issue