144 lines
4.7 KiB
PHP
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');
|
|
}
|
|
}
|