Files
myep-team/tests/Service/Pdf/WorkMonthRendererTest.php
T

144 lines
4.7 KiB
PHP

<?php
namespace App\Tests\Service\Pdf;
use App\Entity\Teamer;
use App\Entity\TeamerWorkMonth;
use App\Model\WorkMonthDays;
use App\Service\Pdf\WorkMonthRenderer;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Twig\Environment;
/**
* Renders a real document rather than mocking Twig: the point of the test is that the
* template, the duration filter and Dompdf agree with each other.
*/
class WorkMonthRendererTest extends WebTestCase
{
public function testItRendersAPdf(): void
{
$pdf = $this->render($this->createWorkMonth());
$this->assertStringStartsWith('%PDF', $pdf);
$this->assertGreaterThan(2000, strlen($pdf), 'A month of rows should not fit in a stub.');
}
/**
* A month whose days the SaaS never reported still has to produce a full sheet, so the
* teamer can write on the empty lines.
*/
public function testItPlacesTheLogoInTheTopRightCorner(): void
{
$pdf = $this->render($this->createWorkMonth());
// A4 is 595.28 x 841.89pt and the page margins are 15mm at the side, 18mm at the top,
// so the content corner sits at 552.76 / 790.87. Asserting the placement rather than
// the markup is the only way to know the logo actually landed there.
$this->assertSame(1, preg_match(
'#([\d.]+) 0 0 ([\d.]+) ([\d.]+) ([\d.]+) cm\s*/\w+ Do#',
$this->contentStreams($pdf),
$placement
), 'The logo should be drawn exactly once.');
[, $width, $height, $x, $y] = array_map('floatval', $placement);
$this->assertEqualsWithDelta(552.76, $x + $width, 1.0, 'Flush with the right margin.');
$this->assertEqualsWithDelta(790.87, $y + $height, 1.0, 'Flush with the top margin.');
$this->assertEqualsWithDelta(230 / 70, $width / $height, 0.05, 'The logo must not be stretched.');
}
/**
* A working-time record is worth printing without its letterhead.
*/
public function testItRendersWithoutALogo(): void
{
$renderer = new WorkMonthRenderer(
static::getContainer()->get(Environment::class),
['project_dir' => sys_get_temp_dir()],
);
$this->assertStringStartsWith('%PDF', $renderer->render($this->createWorkMonth()));
}
private function contentStreams(string $pdf): string
{
preg_match_all('#stream\r?\n(.*?)endstream#s', $pdf, $matches);
$decoded = '';
foreach ($matches[1] as $stream) {
$inflated = @gzuncompress(ltrim($stream, "\r\n"));
if (false !== $inflated) {
$decoded .= $inflated;
}
}
return $decoded;
}
public function testItRendersAMonthWithoutAnyBookings(): void
{
$workMonth = (new TeamerWorkMonth())
->setTeamer($this->createTeamer())
->setYear(2026)
->setMonth(2)
->setDays(WorkMonthDays::fromArray([]))
;
$this->assertStringStartsWith('%PDF', $this->render($workMonth));
}
public function testTheFilenameCarriesTheTeamerAndTheMonth(): void
{
$this->assertSame(
'Arbeitszeitnachweis_Test_2026-03.pdf',
$this->createRenderer()->filename($this->createWorkMonth()),
);
}
public function testTheFilenameSurvivesAnAwkwardName(): void
{
$renderer = $this->createRenderer();
$workMonth = $this->createWorkMonth();
$workMonth->getTeamer()->setLastName('Müller-Lüdenscheidt');
$this->assertSame('Arbeitszeitnachweis_M_ller_L_denscheidt_2026-03.pdf', $renderer->filename($workMonth));
}
private function render(TeamerWorkMonth $workMonth): string
{
return $this->createRenderer()->render($workMonth);
}
private function createRenderer(): WorkMonthRenderer
{
$projectDir = static::getContainer()->getParameter('kernel.project_dir');
/** @var Environment $twig */
$twig = static::getContainer()->get(Environment::class);
return new WorkMonthRenderer($twig, ['project_dir' => $projectDir]);
}
private function createWorkMonth(): TeamerWorkMonth
{
return (new TeamerWorkMonth())
->setTeamer($this->createTeamer())
->setYear(2026)
->setMonth(3)
->setDays(WorkMonthDays::fromArray([
'2026-03-01' => ['minutes' => 285],
'2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25'],
'2026-03-12' => ['minutes' => 510],
]))
->setSourceIstMinutes(795)
;
}
private function createTeamer(): Teamer
{
return (new Teamer())->setFirstName('Theo')->setLastName('Test');
}
}