diff --git a/README.md b/README.md index 244a760f..ffe58af0 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Ce programme est distribué avec une [licence propriétaire](LICENSE.md) et une [![PHP](https://img.shields.io/badge/PHP-8.2.19-informational?logo=php)](https://www.php.net) [![MySQL](https://img.shields.io/badge/MySQL-5.7.32-informational?logo=mysql)](https://www.mysql.com) [![Apache](https://img.shields.io/badge/Apache-2.4.51-informational?logo=apache)](https://httpd.apache.org) -[![PhpStorm](https://img.shields.io/badge/PhpStorm-2024.1.2-informational?logo=phpstorm)](https://www.jetbrains.com/phpstorm) +[![PhpStorm](https://img.shields.io/badge/PhpStorm-2024.1.3-informational?logo=phpstorm)](https://www.jetbrains.com/phpstorm) ## Qualité du code diff --git a/src/Captcha/AbstractAlphaCaptcha.php b/src/Captcha/AbstractAlphaCaptcha.php index 619c6b7d..14437c7e 100644 --- a/src/Captcha/AbstractAlphaCaptcha.php +++ b/src/Captcha/AbstractAlphaCaptcha.php @@ -88,4 +88,12 @@ protected function getRandomWord(): string { return $this->dictionary->getRandomWord(); } + + /** + * Translates the given message with the 'captcha' domain. + */ + protected function transCaptcha(string $id, array $parameters = []): string + { + return $this->trans($id, $parameters, 'captcha'); + } } diff --git a/src/Captcha/AlphaCaptchaInterface.php b/src/Captcha/AlphaCaptchaInterface.php index ed70262f..e53aa2ac 100644 --- a/src/Captcha/AlphaCaptchaInterface.php +++ b/src/Captcha/AlphaCaptchaInterface.php @@ -31,9 +31,4 @@ public function checkAnswer(string $givenAnswer, string $expectedAnswer): bool; * @return string[] the question and the answer */ public function getChallenge(): array; - - /** - * Gets the default index name. - */ - public static function getDefaultIndexName(): string; } diff --git a/src/Captcha/ConsonantCaptcha.php b/src/Captcha/ConsonantCaptcha.php index 5f20e625..7916ea3c 100644 --- a/src/Captcha/ConsonantCaptcha.php +++ b/src/Captcha/ConsonantCaptcha.php @@ -26,14 +26,6 @@ class ConsonantCaptcha extends AbstractAlphaCaptcha '-1' => 'last', ]; - /** - * Gets the default index name. - */ - public static function getDefaultIndexName(): string - { - return 'ConsonantCaptcha'; - } - protected function getAnswer(string $word, int $letterIndex): string { return $this->findAnswer($word, $letterIndex, self::CONSONANT); @@ -46,12 +38,12 @@ protected function getLetterIndex(): int protected function getQuestion(string $word, int $letterIndex): string { - $params = [ - '%index%' => $this->trans(self::INDEX_MAPPING[$letterIndex], [], 'captcha'), - '%letter%' => $this->trans('consonant', [], 'captcha'), + $parameters = [ + '%index%' => $this->transCaptcha(self::INDEX_MAPPING[$letterIndex]), + '%letter%' => $this->transCaptcha('consonant'), '%word%' => $word, ]; - return $this->trans('sentence', $params, 'captcha'); + return $this->transCaptcha('sentence', $parameters); } } diff --git a/src/Captcha/LetterCaptcha.php b/src/Captcha/LetterCaptcha.php index 26fbe9c3..f3a07494 100644 --- a/src/Captcha/LetterCaptcha.php +++ b/src/Captcha/LetterCaptcha.php @@ -26,14 +26,6 @@ class LetterCaptcha extends AbstractAlphaCaptcha '-1' => 'last', ]; - /** - * Gets the default index name. - */ - public static function getDefaultIndexName(): string - { - return 'LetterCaptcha'; - } - protected function getAnswer(string $word, int $letterIndex): string { if (0 > $letterIndex) { @@ -51,12 +43,12 @@ protected function getLetterIndex(): int protected function getQuestion(string $word, int $letterIndex): string { - $params = [ - '%index%' => $this->trans(self::INDEX_MAPPING[$letterIndex], [], 'captcha'), - '%letter%' => $this->trans('letter', [], 'captcha'), + $parameters = [ + '%index%' => $this->transCaptcha(self::INDEX_MAPPING[$letterIndex]), + '%letter%' => $this->transCaptcha('letter'), '%word%' => $word, ]; - return $this->trans('sentence', $params, 'captcha'); + return $this->transCaptcha('sentence', $parameters); } } diff --git a/src/Captcha/VowelCaptcha.php b/src/Captcha/VowelCaptcha.php index b74e87ed..3b9edd2d 100644 --- a/src/Captcha/VowelCaptcha.php +++ b/src/Captcha/VowelCaptcha.php @@ -26,14 +26,6 @@ class VowelCaptcha extends AbstractAlphaCaptcha private const VOWEL = 'AEIOUY'; - /** - * Gets the default index name. - */ - public static function getDefaultIndexName(): string - { - return 'VowelCaptcha'; - } - protected function getAnswer(string $word, int $letterIndex): string { return $this->findAnswer($word, $letterIndex, self::VOWEL); @@ -46,12 +38,12 @@ protected function getLetterIndex(): int protected function getQuestion(string $word, int $letterIndex): string { - $params = [ - '%index%' => $this->trans(self::INDEX_MAPPING[$letterIndex], [], 'captcha'), - '%letter%' => $this->trans('vowel', [], 'captcha'), + $parameters = [ + '%index%' => $this->transCaptcha(self::INDEX_MAPPING[$letterIndex]), + '%letter%' => $this->transCaptcha('vowel'), '%word%' => $word, ]; - return $this->trans('sentence', $params, 'captcha'); + return $this->transCaptcha('sentence', $parameters); } } diff --git a/src/Pdf/Html/HtmlOlChunk.php b/src/Pdf/Html/HtmlOlChunk.php index 5c21a344..dff83e5c 100644 --- a/src/Pdf/Html/HtmlOlChunk.php +++ b/src/Pdf/Html/HtmlOlChunk.php @@ -57,6 +57,8 @@ public function getType(): HtmlListType /** * Sets the start counting (must be positive). + * + * @psalm-param positive-int $start */ public function setStart(int $start): self { diff --git a/src/Pdf/Html/HtmlParser.php b/src/Pdf/Html/HtmlParser.php index 171a09b0..4e22e906 100644 --- a/src/Pdf/Html/HtmlParser.php +++ b/src/Pdf/Html/HtmlParser.php @@ -65,6 +65,7 @@ private function createLiChunk(string $name, HtmlParentChunk $parent, ?string $c private function createOlChunk(string $name, HtmlParentChunk $parent, ?string $className, \DOMNode $node): HtmlOlChunk { $type = HtmlAttribute::LIST_TYPE->getEnumValue($node, HtmlListType::NUMBER); + /** @psalm-var positive-int $start */ $start = HtmlAttribute::LIST_START->getIntValue($node, 1); $chunk = new HtmlOlChunk($name, $parent, $className); diff --git a/src/Pdf/Html/HtmlStyle.php b/src/Pdf/Html/HtmlStyle.php index b11527de..ae389694 100644 --- a/src/Pdf/Html/HtmlStyle.php +++ b/src/Pdf/Html/HtmlStyle.php @@ -61,6 +61,25 @@ public function __construct() $this->setBorder(PdfBorder::none()); } + /** + * Gets the default style. + * + * The style has the following properties: + * + * - Font: Arial 9 points Regular. + * - Line width: 0.2 mm. + * - Fill color: White. + * - Draw color: Black. + * - Text color: Black. + * - Border: None. + * - Text Alignment: Left + * - Margins: 0.0 + */ + public static function default(): self + { + return new self(); + } + /** * Gets the alignment. */ diff --git a/src/Pdf/Traits/PdfColorTrait.php b/src/Pdf/Traits/PdfColorTrait.php index 54776f74..0c0c98f3 100644 --- a/src/Pdf/Traits/PdfColorTrait.php +++ b/src/Pdf/Traits/PdfColorTrait.php @@ -47,11 +47,14 @@ public function asInt(): int /** * @return array{0: int, 1: int, 2: int} + * + * @psalm-return array{0: int<0, 255>, 1: int<0, 255>, 2: int<0, 255>} */ public function asRGB(): array { $hex = $this->getPhpOfficeColor(); + /** @psalm-var array{0: int<0, 255>, 1: int<0, 255>, 2: int<0, 255>} */ return [ $this->hex2Dec(\substr($hex, 0, 2)), $this->hex2Dec(\substr($hex, 2, 2)), diff --git a/tests/Captcha/AlphaCaptchaTestCase.php b/tests/Captcha/AlphaCaptchaTestCase.php new file mode 100644 index 00000000..9225d4ed --- /dev/null +++ b/tests/Captcha/AlphaCaptchaTestCase.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Captcha; + +use App\Captcha\AbstractAlphaCaptcha; +use App\Service\DictionaryService; +use App\Tests\TranslatorMockTrait; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @template TCaptcha as AbstractAlphaCaptcha + */ +abstract class AlphaCaptchaTestCase extends TestCase +{ + use TranslatorMockTrait; + + protected MockObject&DictionaryService $service; + protected MockObject&TranslatorInterface $translator; + + /** + * @throws Exception + */ + protected function setUp(): void + { + $letters = \implode('', \range('A', 'Z')); + $this->translator = $this->createTranslator(); + $this->service = $this->createMock(DictionaryService::class); + $this->service->expects(self::any()) + ->method('getRandomWord') + ->willReturn($letters); + } + + public function testCheckAnswer(): void + { + $captcha = $this->createCaptcha($this->service); + $captcha->setTranslator($this->translator); + + $challenge = $captcha->getChallenge(); + self::assertCount(2, $challenge); + $actual = $captcha->checkAnswer($challenge[1], $challenge[1]); + self::assertTrue($actual); + } + + /** + * @psalm-return TCaptcha + */ + abstract protected function createCaptcha(DictionaryService $service): AbstractAlphaCaptcha; +} diff --git a/tests/Captcha/ConsonantCaptchaTest.php b/tests/Captcha/ConsonantCaptchaTest.php new file mode 100644 index 00000000..1e894fa8 --- /dev/null +++ b/tests/Captcha/ConsonantCaptchaTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Captcha; + +use App\Captcha\AbstractAlphaCaptcha; +use App\Captcha\ConsonantCaptcha; +use App\Service\DictionaryService; +use PHPUnit\Framework\Attributes\CoversClass; + +/** + * @extends AlphaCaptchaTestCase + */ +#[CoversClass(ConsonantCaptcha::class)] +#[CoversClass(AbstractAlphaCaptcha::class)] +class ConsonantCaptchaTest extends AlphaCaptchaTestCase +{ + protected function createCaptcha(DictionaryService $service): ConsonantCaptcha + { + return new ConsonantCaptcha($service); + } +} diff --git a/tests/Captcha/LetterCaptchaTest.php b/tests/Captcha/LetterCaptchaTest.php new file mode 100644 index 00000000..bd8bf75f --- /dev/null +++ b/tests/Captcha/LetterCaptchaTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Captcha; + +use App\Captcha\AbstractAlphaCaptcha; +use App\Captcha\LetterCaptcha; +use App\Service\DictionaryService; +use PHPUnit\Framework\Attributes\CoversClass; + +/** + * @extends AlphaCaptchaTestCase + */ +#[CoversClass(LetterCaptcha::class)] +#[CoversClass(AbstractAlphaCaptcha::class)] +class LetterCaptchaTest extends AlphaCaptchaTestCase +{ + protected function createCaptcha(DictionaryService $service): LetterCaptcha + { + return new LetterCaptcha($service); + } +} diff --git a/tests/Captcha/VowelCaptchaTest.php b/tests/Captcha/VowelCaptchaTest.php new file mode 100644 index 00000000..7ce98750 --- /dev/null +++ b/tests/Captcha/VowelCaptchaTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Captcha; + +use App\Captcha\AbstractAlphaCaptcha; +use App\Captcha\VowelCaptcha; +use App\Service\DictionaryService; +use PHPUnit\Framework\Attributes\CoversClass; + +/** + * @extends AlphaCaptchaTestCase + */ +#[CoversClass(VowelCaptcha::class)] +#[CoversClass(AbstractAlphaCaptcha::class)] +class VowelCaptchaTest extends AlphaCaptchaTestCase +{ + protected function createCaptcha(DictionaryService $service): VowelCaptcha + { + return new VowelCaptcha($service); + } +} diff --git a/tests/Pdf/Html/HtmlAttributeTest.php b/tests/Pdf/Html/HtmlAttributeTest.php new file mode 100644 index 00000000..02d42e90 --- /dev/null +++ b/tests/Pdf/Html/HtmlAttributeTest.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Enums\MessagePosition; +use App\Enums\StrengthLevel; +use App\Pdf\Html\HtmlAttribute; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlAttribute::class)] +class HtmlAttributeTest extends TestCase +{ + /** + * @throws \DOMException + * + * @psalm-suppress RedundantConditionGivenDocblockType + */ + public function testGetEnumValueInt(): void + { + $expected = StrengthLevel::MEDIUM; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', (string) $expected->value); + $actual = HtmlAttribute::CLASS_NAME->getEnumValue($element, $expected); + self::assertSame($expected, $actual); + } + + /** + * @throws \DOMException + * + * @psalm-suppress RedundantConditionGivenDocblockType + */ + public function testGetEnumValueString(): void + { + $expected = MessagePosition::TOP_LEFT; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', $expected->value); + $actual = HtmlAttribute::CLASS_NAME->getEnumValue($element, $expected); + self::assertSame($expected, $actual); + } + + /** + * @throws \DOMException + */ + public function testGetIntValue(): void + { + $expected = 12456; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', (string) $expected); + $actual = HtmlAttribute::CLASS_NAME->getIntValue($element); + self::assertSame($expected, $actual); + } + + /** + * @throws \DOMException + */ + public function testGetIntValueWithDefault(): void + { + $expected = 12456; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', ''); + $actual = HtmlAttribute::CLASS_NAME->getIntValue($element, $expected); + self::assertSame($expected, $actual); + } + + /** + * @throws \DOMException + */ + public function testGetValue(): void + { + $expected = 'text-start'; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', $expected); + $actual = HtmlAttribute::CLASS_NAME->getValue($element); + self::assertSame($expected, $actual); + } + + /** + * @throws \DOMException + */ + public function testGetValueEmpty(): void + { + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', ''); + $actual = HtmlAttribute::CLASS_NAME->getValue($element); + self::assertNull($actual); + } + + /** + * @throws \DOMException + */ + public function testGetValueNoFound(): void + { + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('fake', 'text-start'); + $actual = HtmlAttribute::CLASS_NAME->getValue($element); + self::assertNull($actual); + } + + /** + * @throws \DOMException + */ + public function testGetValueWithDefault(): void + { + $expected = 'text-start'; + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createElement('div'); + $element->setAttribute('class', ''); + $actual = HtmlAttribute::CLASS_NAME->getValue($element, $expected); + self::assertSame($expected, $actual); + } + + public function testNoAttribute(): void + { + $document = new \DOMDocument('1.0', 'utf-8'); + $element = $document->createComment('comment'); + $actual = HtmlAttribute::CLASS_NAME->getValue($element); + self::assertNull($actual); + } +} diff --git a/tests/Pdf/Html/HtmlBrChunkTest.php b/tests/Pdf/Html/HtmlBrChunkTest.php new file mode 100644 index 00000000..6d1a5f0f --- /dev/null +++ b/tests/Pdf/Html/HtmlBrChunkTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Html\HtmlBrChunk; +use App\Pdf\Html\HtmlTag; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlBrChunk::class)] +class HtmlBrChunkTest extends TestCase +{ + public function testIsNewLine(): void + { + $chunk = new HtmlBrChunk(HtmlTag::LINE_BREAK->value); + self::assertTrue($chunk->isNewLine()); + } +} diff --git a/tests/Pdf/Html/HtmlMarginsTest.php b/tests/Pdf/Html/HtmlMarginsTest.php new file mode 100644 index 00000000..ce250f28 --- /dev/null +++ b/tests/Pdf/Html/HtmlMarginsTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Html\HtmlMargins; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlMargins::class)] +class HtmlMarginsTest extends TestCase +{ + public function testDefault(): void + { + $actual = HtmlMargins::default(); + self::assertSameMargins($actual); + } + + public function testReset(): void + { + $expected = 10.0; + $actual = HtmlMargins::default(); + $actual->setBottom($expected); + $actual->setLeft($expected); + $actual->setRight($expected); + $actual->setTop($expected); + self::assertSameMargins($actual, $expected); + + $actual->reset(); + self::assertSameMargins($actual); + } + + public function testSetMargins(): void + { + $expected = 10.0; + $actual = HtmlMargins::default(); + $actual->setMargins($expected); + self::assertSameMargins($actual, $expected); + } + + protected static function assertSameMargins(HtmlMargins $actual, float $expected = 0.0): void + { + self::assertSame($expected, $actual->getBottom()); + self::assertSame($expected, $actual->getLeft()); + self::assertSame($expected, $actual->getRight()); + self::assertSame($expected, $actual->getTop()); + } +} diff --git a/tests/Pdf/Html/HtmlOlChunkTest.php b/tests/Pdf/Html/HtmlOlChunkTest.php new file mode 100644 index 00000000..abd495f4 --- /dev/null +++ b/tests/Pdf/Html/HtmlOlChunkTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Html\AbstractHtmlListChunk; +use App\Pdf\Html\HtmlLiChunk; +use App\Pdf\Html\HtmlListType; +use App\Pdf\Html\HtmlOlChunk; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlOlChunk::class)] +#[CoversClass(AbstractHtmlListChunk::class)] +class HtmlOlChunkTest extends TestCase +{ + public function testGetBulletLast(): void + { + $olChunk = new HtmlOlChunk('ol'); + $actual = $olChunk->getBulletLast(); + self::assertSame('1.', $actual); + + $olChunk->add(new HtmlLiChunk('li')); + $olChunk->add(new HtmlLiChunk('li')); + $actual = $olChunk->getBulletLast(); + self::assertSame('2.', $actual); + } + + public function testGetBulletText(): void + { + $olChunk = new HtmlOlChunk('ol'); + $liChunk1 = new HtmlLiChunk('li'); + $actual = $olChunk->getBulletText($liChunk1); + self::assertSame('1.', $actual); + + $olChunk->add($liChunk1); + $actual = $olChunk->getBulletText($liChunk1); + self::assertSame('1.', $actual); + + $liChunk2 = new HtmlLiChunk('li'); + $olChunk->add($liChunk2); + $actual = $olChunk->getBulletText($liChunk2); + self::assertSame('2.', $actual); + } + + public function testGetStart(): void + { + $olChunk = new HtmlOlChunk('ol'); + $actual = $olChunk->getStart(); + self::assertSame(1, $actual); + + $olChunk->setStart(2); + $actual = $olChunk->getStart(); + self::assertSame(2, $actual); + } + + public function testType(): void + { + $expected = HtmlListType::NUMBER; + $olChunk = new HtmlOlChunk('ol'); + $actual = $olChunk->getType(); + self::assertSame($expected, $actual); + + $expected = HtmlListType::LETTER_LOWER; + $olChunk->setType($expected); + $actual = $olChunk->getType(); + self::assertSame($expected, $actual); + } +} diff --git a/tests/Pdf/Html/HtmlParserTest.php b/tests/Pdf/Html/HtmlParserTest.php index 8198fe3d..557d9596 100644 --- a/tests/Pdf/Html/HtmlParserTest.php +++ b/tests/Pdf/Html/HtmlParserTest.php @@ -12,6 +12,7 @@ namespace App\Tests\Pdf\Html; +use App\Pdf\Html\AbstractHtmlChunk; use App\Pdf\Html\HtmlBrChunk; use App\Pdf\Html\HtmlLiChunk; use App\Pdf\Html\HtmlOlChunk; @@ -30,11 +31,7 @@ public function testBrChunk(): void { $parser = new HtmlParser('
'); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlBrChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlBrChunk::class); } public function testEmpty(): void @@ -55,11 +52,7 @@ public function testLiChunk(): void { $parser = new HtmlParser('
  • '); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlLiChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlLiChunk::class); } public function testNoBody(): void @@ -73,49 +66,45 @@ public function testOlChunk(): void { $parser = new HtmlParser('
      '); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlOlChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlOlChunk::class); } public function testPageBreakChunk(): void { $parser = new HtmlParser('
      '); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlPageBreakChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlPageBreakChunk::class); } public function testTextChunk(): void { $parser = new HtmlParser('

      My Text

      '); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlTextChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlParentChunk::class, HtmlTextChunk::class); } public function testUlChunk(): void { $parser = new HtmlParser('
        '); $actual = $parser->parse(); - self::assertInstanceOf(HtmlParentChunk::class, $actual); - self::assertNotEmpty($actual->getChildren()); - self::assertCount(1, $actual); - $actual = $actual->getChildren()[0]; - self::assertInstanceOf(HtmlUlChunk::class, $actual); + self::assertChunks($actual, HtmlParentChunk::class, HtmlUlChunk::class); + } + + /** + * @psalm-param class-string ...$classes + */ + protected static function assertChunks(mixed $actual, string ...$classes): void + { + $index = 0; + $last = \count($classes) - 1; + foreach ($classes as $class) { + self::assertInstanceOf($class, $actual); + if ($index < $last && $actual instanceof HtmlParentChunk) { + self::assertNotEmpty($actual->getChildren()); + self::assertCount(1, $actual->getChildren()); + $actual = $actual->getChildren()[0]; + } + ++$index; + } } } diff --git a/tests/Pdf/Html/HtmlStyleTest.php b/tests/Pdf/Html/HtmlStyleTest.php new file mode 100644 index 00000000..e0d6b6e2 --- /dev/null +++ b/tests/Pdf/Html/HtmlStyleTest.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Colors\PdfDrawColor; +use App\Pdf\Colors\PdfFillColor; +use App\Pdf\Colors\PdfTextColor; +use App\Pdf\Html\HtmlBootstrapColor; +use App\Pdf\Html\HtmlStyle; +use fpdf\PdfBorder; +use fpdf\PdfFontName; +use fpdf\PdfFontStyle; +use fpdf\PdfTextAlignment; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlStyle::class)] +class HtmlStyleTest extends TestCase +{ + public function testMargins(): void + { + $expected = 5.0; + $actual = HtmlStyle::default(); + $actual->setXMargins($expected) + ->setYMargins($expected); + self::assertSameMargins($actual, $expected); + + $expected = 15.0; + $actual->setMargins($expected); + self::assertSameMargins($actual, $expected); + } + + public function testProperties(): void + { + $actual = HtmlStyle::default(); + self::assertSame(PdfTextAlignment::LEFT, $actual->getAlignment()); + self::assertSameMargins($actual); + } + + public function testReset(): void + { + $expected = 10.0; + $actual = HtmlStyle::default(); + $actual->setBottomMargin($expected); + $actual->setLeftMargin($expected); + $actual->setRightMargin($expected); + $actual->setTopMargin($expected); + self::assertSameMargins($actual, $expected); + + $actual->reset(); + self::assertSameMargins($actual); + } + + public function testUpdateAlignment(): void + { + $actual = HtmlStyle::default(); + $actual->update('text-justify'); + self::assertSame(PdfTextAlignment::JUSTIFIED, $actual->getAlignment()); + } + + public function testUpdateBorder(): void + { + $actual = HtmlStyle::default(); + $actual->update('border'); + self::assertEqualsCanonicalizing(PdfBorder::all(), $actual->getBorder()); + } + + public function testUpdateColor(): void + { + $actual = HtmlStyle::default(); + $actual->update('primary'); + + $rgb = HtmlBootstrapColor::PRIMARY->asRGB(); + $color = new PdfTextColor($rgb[0], $rgb[1], $rgb[2]); + self::assertEqualsCanonicalizing($color, $actual->getTextColor()); + + $color = new PdfFillColor($rgb[0], $rgb[1], $rgb[2]); + self::assertEqualsCanonicalizing($color, $actual->getFillColor()); + + $color = new PdfDrawColor($rgb[0], $rgb[1], $rgb[2]); + self::assertEqualsCanonicalizing($color, $actual->getDrawColor()); + } + + public function testUpdateEmpty(): void + { + $actual = HtmlStyle::default(); + $actual->update(''); + self::assertSameMargins($actual); + } + + public function testUpdateFont(): void + { + $style = HtmlStyle::default(); + $style->update('fw-bold'); + $actual = $style->getFont()->getStyle(); + self::assertSame(PdfFontStyle::BOLD, $actual); + + $style = HtmlStyle::default(); + $style->update('fst-normal'); + $actual = $style->getFont()->getStyle(); + self::assertSame(PdfFontStyle::REGULAR, $actual); + + $style = HtmlStyle::default(); + $style->update('fst-italic'); + $actual = $style->getFont()->getStyle(); + self::assertSame(PdfFontStyle::ITALIC, $actual); + + $style = HtmlStyle::default(); + $style->update('text-decoration-underline'); + $actual = $style->getFont()->getStyle(); + self::assertSame(PdfFontStyle::UNDERLINE, $actual); + + $style = HtmlStyle::default(); + $style->update('font-monospace'); + $actual = $style->getFont()->getName(); + self::assertSame(PdfFontName::COURIER, $actual); + } + + public function testUpdateMargins(): void + { + $expected = 1.0; + $actual = HtmlStyle::default(); + $actual->update('m-1'); + self::assertSameMargins($actual, $expected); + } + + protected static function assertSameMargins(HtmlStyle $actual, float $expected = 0.0): void + { + self::assertSame($expected, $actual->getBottomMargin()); + self::assertSame($expected, $actual->getLeftMargin()); + self::assertSame($expected, $actual->getRightMargin()); + self::assertSame($expected, $actual->getTopMargin()); + } +} diff --git a/tests/Pdf/Html/HtmlTagTest.php b/tests/Pdf/Html/HtmlTagTest.php new file mode 100644 index 00000000..f9d4ba23 --- /dev/null +++ b/tests/Pdf/Html/HtmlTagTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Colors\PdfTextColor; +use App\Pdf\Html\HtmlStyle; +use App\Pdf\Html\HtmlTag; +use fpdf\PdfFontName; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlTag::class)] +class HtmlTagTest extends TestCase +{ + public static function getMatches(): \Iterator + { + yield [HtmlTag::BODY, 'body', true]; + yield [HtmlTag::BODY, 'BODY', true]; + yield [HtmlTag::BODY, 'fake', false]; + } + + public static function getStyles(): \Iterator + { + yield [HtmlTag::BODY, null]; + yield [HtmlTag::SPAN, null]; + yield [HtmlTag::TEXT, null]; + yield [HtmlTag::LINE_BREAK, null]; + yield [HtmlTag::LIST_ITEM, null]; + + $style = HtmlStyle::default()->setFontBold(); + yield [HtmlTag::BOLD, $style]; + yield [HtmlTag::STRONG, $style]; + + $style = HtmlStyle::default()->setFontItalic(); + yield [HtmlTag::ITALIC, $style]; + yield [HtmlTag::EMPHASIS, $style]; + + yield [HtmlTag::PARAGRAPH, HtmlStyle::default()->setBottomMargin(2.0)]; + yield [HtmlTag::LIST_ORDERED, HtmlStyle::default()->setBottomMargin(1.0)->setLeftMargin(2.0)]; + yield [HtmlTag::LIST_UNORDERED, HtmlStyle::default()->setBottomMargin(1.0)->setLeftMargin(2.0)]; + + $style = HtmlStyle::default()->setFontName(PdfFontName::COURIER); + yield [HtmlTag::SAMPLE, $style]; + yield [HtmlTag::KEYBOARD, $style]; + + yield [HtmlTag::UNDERLINE, HtmlStyle::default()->setFontUnderline()]; + yield [HtmlTag::VARIABLE, HtmlStyle::default()->setFontName(PdfFontName::COURIER)->setFontItalic()]; + + $color = new PdfTextColor(255, 0, 0); + yield [HtmlTag::CODE, HtmlStyle::default()->setFontName(PdfFontName::COURIER) + ->setTextColor($color)]; + + $style = HtmlStyle::default() + ->setFontBold(true) + ->setBottomMargin(2.0) + ->setFontSize(2.5 * 9.0); + yield [HtmlTag::H1, $style]; + + $style = (clone $style)->setFontSize(2.0 * 9.0); + yield [HtmlTag::H2, $style]; + + $style = (clone $style)->setFontSize(1.75 * 9.0); + yield [HtmlTag::H3, $style]; + + $style = (clone $style)->setFontSize(1.5 * 9.0); + yield [HtmlTag::H4, $style]; + + $style = (clone $style)->setFontSize(1.25 * 9.0); + yield [HtmlTag::H5, $style]; + + $style = (clone $style)->setFontSize(1.1 * 9.0); + yield [HtmlTag::H6, $style]; + } + + public function testFindFirst(): void + { + $source = << + +

        Text

        + + HTML; + + $document = new \DOMDocument(); + $actual = $document->loadHTML($source, \LIBXML_NOERROR | \LIBXML_NOBLANKS); + self::assertTrue($actual); + + $actual = HtmlTag::BODY->findFirst($document); + self::assertNotNull($actual); + $actual = HtmlTag::PARAGRAPH->findFirst($document); + self::assertNotNull($actual); + $actual = HtmlTag::CODE->findFirst($document); + self::assertNull($actual); + } + + public function testGetStyle(): void + { + $actual = HtmlTag::getStyle('body'); + self::assertNull($actual); + + $style = HtmlStyle::default()->setFontBold(true) + ->setBottomMargin(2.0) + ->setFontSize(2.5 * 9.0); + $actual = HtmlTag::getStyle('h1'); + self::assertNotNull($actual); + self::assertSameStyle($style, $actual); + } + + #[DataProvider('getMatches')] + public function testMatch(HtmlTag $tag, string $value, bool $expected): void + { + $actual = $tag->match($value); + self::assertSame($expected, $actual); + } + + #[DataProvider('getStyles')] + public function testStyle(HtmlTag $tag, ?HtmlStyle $expected): void + { + self::assertSameStyle($expected, $tag); + } + + protected static function assertSameStyle(?HtmlStyle $expected, HtmlTag|HtmlStyle $tagOrStyle): void + { + $actual = $tagOrStyle instanceof HtmlStyle ? $tagOrStyle : $tagOrStyle->style(); + if (!$expected instanceof HtmlStyle) { + self::assertNull($actual); + + return; + } + + self::assertNotNull($actual); + + self::assertSame($expected->getAlignment(), $actual->getAlignment()); + self::assertSame($expected->getIndent(), $actual->getIndent()); + self::assertSame($expected->getTopMargin(), $actual->getTopMargin()); + self::assertSame($expected->getBottomMargin(), $actual->getBottomMargin()); + self::assertSame($expected->getLeftMargin(), $actual->getLeftMargin()); + self::assertSame($expected->getRightMargin(), $actual->getRightMargin()); + + self::assertEqualsCanonicalizing($expected->getBorder(), $actual->getBorder()); + self::assertEqualsCanonicalizing($expected->getDrawColor(), $actual->getDrawColor()); + self::assertEqualsCanonicalizing($expected->getFillColor(), $actual->getFillColor()); + self::assertEqualsCanonicalizing($expected->getTextColor(), $actual->getTextColor()); + self::assertEqualsCanonicalizing($expected->getFont(), $actual->getFont()); + self::assertEqualsCanonicalizing($expected->getLine(), $actual->getLine()); + } +} diff --git a/tests/Pdf/Html/HtmlTextChunkTest.php b/tests/Pdf/Html/HtmlTextChunkTest.php new file mode 100644 index 00000000..1a2a0695 --- /dev/null +++ b/tests/Pdf/Html/HtmlTextChunkTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Html\HtmlLiChunk; +use App\Pdf\Html\HtmlParentChunk; +use App\Pdf\Html\HtmlTag; +use App\Pdf\Html\HtmlTextChunk; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlTextChunk::class)] +class HtmlTextChunkTest extends TestCase +{ + public function testIsNewLine(): void + { + $chunk = new HtmlTextChunk(HtmlTag::TEXT->value); + $chunk->setText('Text'); + + $actual = $chunk->isNewLine(); + self::assertFalse($actual); + + $parent = new HtmlParentChunk(HtmlTag::PARAGRAPH->value); + $parent->add($chunk); + $actual = $chunk->isNewLine(); + self::assertFalse($actual); + + $liChunk = new HtmlLiChunk(HtmlTag::LIST_ORDERED->value); + $liChunk->add($chunk); + $liChunk->add(new HtmlLiChunk(HtmlTag::LIST_ORDERED->value)); + + $parent = new HtmlParentChunk(HtmlTag::PARAGRAPH->value); + $parent->add($liChunk); + + $actual = $chunk->isNewLine(); + self::assertTrue($actual); + } +} diff --git a/tests/Pdf/Html/HtmlUlChunkTest.php b/tests/Pdf/Html/HtmlUlChunkTest.php new file mode 100644 index 00000000..21872016 --- /dev/null +++ b/tests/Pdf/Html/HtmlUlChunkTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace App\Tests\Pdf\Html; + +use App\Pdf\Html\AbstractHtmlListChunk; +use App\Pdf\Html\HtmlBrChunk; +use App\Pdf\Html\HtmlLiChunk; +use App\Pdf\Html\HtmlUlChunk; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HtmlUlChunk::class)] +#[CoversClass(AbstractHtmlListChunk::class)] +class HtmlUlChunkTest extends TestCase +{ + public function testAdd(): void + { + $ulChunk = new HtmlUlChunk('ul'); + self::assertCount(0, $ulChunk); + $ulChunk->add(new HtmlBrChunk('br')); + self::assertCount(0, $ulChunk); + $ulChunk->add(new HtmlLiChunk('li')); + self::assertCount(1, $ulChunk); + } + + public function testBulletLast(): void + { + $ulChunk = new HtmlUlChunk('name'); + $actual = $ulChunk->getBulletLast(); + self::assertSame(\chr(149), $actual); + } + + public function testBulletText(): void + { + $ulChunk = new HtmlUlChunk('ul'); + $liChunk = new HtmlLiChunk('li'); + $actual = $ulChunk->getBulletText($liChunk); + self::assertSame(\chr(149), $actual); + } +} diff --git a/tests/Pdf/Traits/PdfChartLegendTraitTest.php b/tests/Pdf/Traits/PdfChartLegendTraitTest.php index aba0fcd1..63f69c3a 100644 --- a/tests/Pdf/Traits/PdfChartLegendTraitTest.php +++ b/tests/Pdf/Traits/PdfChartLegendTraitTest.php @@ -43,6 +43,8 @@ public function render(): bool /** * @psalm-param ColorStringType[] $legends + * + * @phpstan-param array $legends */ public function outputLegends(array $legends, bool $horizontal, bool $circle = true): static { @@ -79,6 +81,8 @@ public function render(): bool /** * @psalm-param ColorStringType[] $legends + * + * @phpstan-param array $legends */ public function computeHeights(array $legends, bool $horizontal): float { @@ -115,6 +119,8 @@ public function render(): bool /** * @psalm-param ColorStringType[] $legends + * + * @phpstan-param array $legends */ public function outputLegendsHorizontal(array $legends, bool $circle = true): static { @@ -149,6 +155,8 @@ public function render(): bool /** * @psalm-param ColorStringType[] $legends + * + * @phpstan-param array $legends */ public function outputLegendsVertical(array $legends, bool $circle = true): static { @@ -183,6 +191,8 @@ public function render(): bool /** * @psalm-param ColorStringType[] $legends + * + * @phpstan-param array $legends */ public function computeWidths(array $legends, bool $horizontal): float { @@ -206,6 +216,8 @@ public function computeWidths(array $legends, bool $horizontal): float /** * @psalm-return ColorStringType[] + * + * @phpstan-return array */ private function getLegends(): array {