test(generator): end-to-end structural verification against ProPresenter format

This commit is contained in:
Thorsten Bus 2026-03-30 08:17:00 +02:00
parent 82a9673874
commit 9db2702b5f

View file

@ -6,6 +6,9 @@ namespace ProPresenter\Parser\Tests;
use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use ProPresenter\Parser\PresentationBundle;
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProBundleWriter;
use ProPresenter\Parser\ProFileGenerator; use ProPresenter\Parser\ProFileGenerator;
use ProPresenter\Parser\ProFileReader; use ProPresenter\Parser\ProFileReader;
use ProPresenter\Parser\ProFileWriter; use ProPresenter\Parser\ProFileWriter;
@ -1014,4 +1017,140 @@ class ProFileGeneratorTest extends TestCase
$this->assertNotNull($image->getFile()->getLocalUrl()); $this->assertNotNull($image->getFile()->getLocalUrl());
$this->assertSame('file:///Users/test/AI/Media/test-image.png', $image->getFile()->getLocalUrl()->getAbsoluteString()); $this->assertSame('file:///Users/test/AI/Media/test-image.png', $image->getFile()->getLocalUrl()->getAbsoluteString());
} }
#[Test]
public function testGeneratedProFileMatchesProPresenterNativeStructure(): void
{
$song = ProFileGenerator::generate(
'TestBild',
[[
'name' => 'Verse 1',
'color' => [0.0, 0.0, 0.0, 1.0],
'slides' => [[
'label' => 'test-background',
'media' => 'file:///Users/thorsten/AI/propresenter/ref/Media/test-background.png',
'format' => 'png',
'mediaWidth' => 200,
'mediaHeight' => 150,
]],
]],
[['name' => 'normal', 'groupNames' => ['Verse 1']]],
);
$p = $song->getPresentation();
// Fix 1: UUIDs are uppercase
$this->assertMatchesRegularExpression(
'/^[A-F0-9]{8}-[A-F0-9]{4}-4[A-F0-9]{3}-[89AB][A-F0-9]{3}-[A-F0-9]{12}$/',
$p->getUuid()->getString(),
'Presentation UUID must be uppercase',
);
// Fix 2: Separate platform and application versions
$appInfo = $p->getApplicationInfo();
$this->assertSame(14, $appInfo->getPlatformVersion()->getMajorVersion());
$this->assertSame(8, $appInfo->getPlatformVersion()->getMinorVersion());
$this->assertSame(3, $appInfo->getPlatformVersion()->getPatchVersion());
$this->assertSame(20, $appInfo->getApplicationVersion()->getMajorVersion());
$this->assertSame('335544354', $appInfo->getApplicationVersion()->getBuild());
// Fix 3a: Background present
$this->assertNotNull($p->getBackground(), 'Background must be set');
$this->assertSame(1.0, $p->getBackground()->getColor()->getAlpha());
// Fix 3b: ChordChart on Presentation
$this->assertNotNull($p->getChordChart(), 'Presentation chordChart must be set');
// Fix 3c: CCLI always present (even with no data)
$this->assertNotNull($p->getCcli(), 'CCLI must always be set');
// Fix 3d: Timeline
$this->assertNotNull($p->getTimeline(), 'Timeline must be set');
$this->assertSame(300.0, $p->getTimeline()->getDuration());
// Fix 4: HotKey on Group and Cue
$this->assertNotNull($p->getCueGroups()[0]->getGroup()->getHotKey(), 'Group must have hotKey');
$this->assertNotNull($p->getCues()[0]->getHotKey(), 'Cue must have hotKey');
// Fix 5a: Slide size 1920x1080
$slidePresentation = $p->getCues()[0]->getActions()[0]->getSlide()->getPresentation();
$size = $slidePresentation->getBaseSlide()->getSize();
$this->assertNotNull($size, 'Slide must have size');
$this->assertSame(1920.0, $size->getWidth());
$this->assertSame(1080.0, $size->getHeight());
// Fix 5b: PresentationSlide chordChart
$this->assertNotNull($slidePresentation->getChordChart(), 'PresentationSlide must have chordChart');
// Fix 6: LOCAL PATH ROOT_USER_HOME for /Users/ paths
$mediaUrl = $p->getCues()[0]->getActions()[1]->getMedia()->getElement()->getUrl();
$this->assertSame(2, $mediaUrl->getLocal()->getRoot(), 'Must use ROOT_USER_HOME (2) for /Users/ paths');
$this->assertSame('AI/propresenter/ref/Media/test-background.png', $mediaUrl->getLocal()->getPath());
// Fix 7a: Media action name
$mediaAction = $p->getCues()[0]->getActions()[1];
$this->assertSame('test-background', $mediaAction->getName());
// Fix 7b: MediaType.audio set (oneof discriminator)
$this->assertNotNull($mediaAction->getMedia()->getAudio(), 'MediaType must have audio set');
// Fix 7c: ImageTypeProperties with drawing and file
$img = $mediaAction->getMedia()->getElement()->getImage();
$this->assertNotNull($img->getDrawing(), 'ImageTypeProperties must have drawing');
$this->assertSame(200.0, $img->getDrawing()->getNaturalSize()->getWidth());
$this->assertSame(150.0, $img->getDrawing()->getNaturalSize()->getHeight());
$this->assertNotNull($img->getDrawing()->getCustomImageBounds());
$this->assertNotNull($img->getDrawing()->getCropInsets());
$this->assertNotNull($img->getFile(), 'ImageTypeProperties must have file');
$this->assertNotNull($img->getFile()->getLocalUrl());
$this->assertSame(
'file:///Users/thorsten/AI/propresenter/ref/Media/test-background.png',
$img->getFile()->getLocalUrl()->getAbsoluteString(),
);
}
#[Test]
public function testBundleRoundTripWithGeneratedSong(): void
{
$song = ProFileGenerator::generate(
'TestBild',
[[
'name' => 'Verse 1',
'color' => [0.0, 0.0, 0.0, 1.0],
'slides' => [[
'label' => 'test-background',
'media' => 'file:///Users/thorsten/AI/propresenter/ref/Media/test-background.png',
'format' => 'png',
'mediaWidth' => 200,
'mediaHeight' => 150,
]],
]],
[['name' => 'normal', 'groupNames' => ['Verse 1']]],
);
$fakeImageBytes = "\x89PNG\r\n\x1a\n" . str_repeat("\x00", 100);
$bundle = new PresentationBundle(
$song,
'TestBild.pro',
['Media/test-background.png' => $fakeImageBytes],
);
$path = $this->tmpDir . '/TestBild.probundle';
ProBundleWriter::write($bundle, $path);
$this->assertFileExists($path);
$read = ProBundleReader::read($path);
$this->assertSame('TestBild', $read->getName());
$this->assertSame('TestBild.pro', $read->getProFilename());
$this->assertSame(1, $read->getMediaFileCount());
$this->assertTrue($read->hasMediaFile('Media/test-background.png'));
// UUID preserved through serialization
$this->assertSame(
$song->getPresentation()->getUuid()->getString(),
$read->getSong()->getPresentation()->getUuid()->getString(),
);
}
} }