220 lines
7.0 KiB
PHP
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);
|
|
}
|
|
}
|