Files
electionvue/vendor/tecnickcom/tc-lib-pdf/test/HTMLRealPageCorpusTest.php
T
2026-08-30 22:02:02 +00:00

220 lines
7.0 KiB
PHP

<?php
/**
* HTMLRealPageCorpusTest.php
*
* @since 2002-08-03
* @category Library
* @package Pdf
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-pdf
*
* This file is part of tc-lib-pdf software library.
*/
namespace Test;
use PHPUnit\Framework\Attributes\DataProvider;
class HTMLRealPageCorpusTest extends TestUtil
{
private const CORPUS_FILE = __DIR__ . '/fixtures/html/real_pages/corpus.json';
/**
* @param array<array-key, mixed> $value
* @return array<string, mixed>
*/
private static function toStringKeyMap(array $value): array
{
$typed = [];
foreach (\array_keys($value) as $key) {
if (!\is_string($key)) {
continue;
}
$typed[$key] = $value[$key] ?? null;
}
return $typed;
}
private static function scalarToString(mixed $value): string
{
return \is_scalar($value) ? (string) $value : '';
}
/** @return list<string> */
private static function toStringList(mixed $value): array
{
if (!\is_array($value)) {
return [];
}
$list = [];
$items = \array_filter($value, static fn(mixed $item): bool => \is_scalar($item));
return \array_values(\array_map(static fn(string|int|float|bool $item): string => (string) $item, $items));
}
/** @return array<int, array<string, mixed>> */
private static function toPageList(mixed $value): array
{
if (!\is_array($value)) {
return [];
}
/** @var array<int, array<string, mixed>> $pages */
$pages = [];
/** @var array<int, array<array-key, mixed>> $pageRows */
$pageRows = \array_values(\array_filter($value, static fn(mixed $page): bool => \is_array($page)));
foreach ($pageRows as $page) {
$pages[] = self::toStringKeyMap($page);
}
return $pages;
}
public static function setUpBeforeClass(): void
{
self::setUpFontsPath();
}
/**
* @return array{
* version: int,
* failure_tags: array<int, string>,
* severity_levels: array<int, string>,
* pages: array<int, array<string, mixed>>
* }
* @throws \Throwable
*/
private static function loadCorpus(): array
{
$raw = \file_get_contents(self::CORPUS_FILE);
if ($raw === false) {
throw new \RuntimeException('Unable to read corpus manifest: ' . self::CORPUS_FILE);
}
/** @var array<string, mixed>|null $data */
$data = \json_decode($raw, true);
if (!\is_array($data)) {
throw new \RuntimeException('Invalid JSON in corpus manifest: ' . self::CORPUS_FILE);
}
/** @var array{
* version: int,
* failure_tags: array<int, string>,
* severity_levels: array<int, string>,
* pages: array<int, array<string, mixed>>
* } $typed
*/
return [
'version' => \is_int($data['version'] ?? null) ? $data['version'] : 0,
'failure_tags' => self::toStringList($data['failure_tags'] ?? []),
'severity_levels' => self::toStringList($data['severity_levels'] ?? []),
'pages' => self::toPageList($data['pages'] ?? []),
];
}
/** @throws \Throwable */
public function testRealPageCorpusManifestHasRequiredArchetypesAndSeverityTaggedFailures(): void
{
$corpus = self::loadCorpus();
$this->assertSame(1, $corpus['version']);
$expectedArchetypes = [
'long-form article',
'invoice and statement',
'product/documentation page',
'admin/report dashboard with tables',
'form-heavy page',
];
$this->assertNotEmpty($corpus['failure_tags']);
$this->assertNotEmpty($corpus['severity_levels']);
$this->assertNotEmpty($corpus['pages']);
$archetypes = [];
$totalFailures = 0;
foreach ($corpus['pages'] as $page) {
$pageId = self::scalarToString($page['id'] ?? '');
$fixture = self::scalarToString($page['fixture'] ?? '');
$archetype = self::scalarToString($page['archetype'] ?? '');
$this->assertNotSame('', $pageId);
$this->assertNotSame('', $fixture);
$this->assertNotSame('', $archetype);
$fixturePath = __DIR__ . '/fixtures/html/real_pages/' . $fixture;
$this->assertFileExists($fixturePath, 'Missing corpus fixture for page id: ' . $pageId);
$archetypes[$archetype] = true;
$failures = [];
if (isset($page['failures']) && \is_array($page['failures'])) {
$failures = $page['failures'];
}
/** @var array<int, array<array-key, mixed>> $failureRows */
$failureRows = \array_values(\array_filter($failures, static fn(mixed $failure): bool => \is_array(
$failure,
)));
foreach ($failureRows as $failure) {
$typedFailure = self::toStringKeyMap($failure);
$tag = self::scalarToString($typedFailure['tag'] ?? '');
$severity = self::scalarToString($typedFailure['severity'] ?? '');
$this->assertContains($tag, $corpus['failure_tags']);
$this->assertContains($severity, $corpus['severity_levels']);
$totalFailures++;
}
}
\sort($expectedArchetypes);
$actualArchetypes = \array_keys($archetypes);
\sort($actualArchetypes);
$this->assertSame($expectedArchetypes, $actualArchetypes);
$this->assertGreaterThanOrEqual(0, $totalFailures, 'Corpus should track severity-tagged failures.');
}
/**
* @return array<string, array{0: string, 1: string}>
* @throws \Throwable
*/
public static function corpusPageProvider(): array
{
$corpus = self::loadCorpus();
$dataset = [];
foreach ($corpus['pages'] as $page) {
$pageId = self::scalarToString($page['id'] ?? 'unknown');
$fixture = self::scalarToString($page['fixture'] ?? '');
$dataset[$pageId] = [$pageId, $fixture];
}
return $dataset;
}
/** @throws \Throwable */
#[DataProvider('corpusPageProvider')]
public function testRealPageCorpusFixturesRenderWithoutFatal(string $pageId, string $fixture): void
{
$obj = new \Com\Tecnick\Pdf\Tcpdf();
$this->initFontAndPage($obj);
$fixturePath = __DIR__ . '/fixtures/html/real_pages/' . $fixture;
$html = \file_get_contents($fixturePath);
$this->assertNotFalse($html, 'Unable to read fixture for page id: ' . $pageId);
$obj->addHTMLCell($html, 10, 10, 190, 0);
$pdf = $obj->getOutPDFString();
$this->assertNotSame('', $pdf, 'Expected rendered PDF output for page id: ' . $pageId);
}
}