diff --git a/src/Twig/Functions/IconExtension.php b/src/Twig/Functions/IconExtension.php index b092417..f39e842 100644 --- a/src/Twig/Functions/IconExtension.php +++ b/src/Twig/Functions/IconExtension.php @@ -9,13 +9,16 @@ use Twig\TwigFunction; final class IconExtension extends AbstractExtension { - public function __construct(private array $directories) {} + public const DEFAULT_SIZE = 32; private const DEFAULT_OPTIONS = [ 'icon' => null, 'title' => null, + 'size' => self::DEFAULT_SIZE ]; + public function __construct(private array $directories) {} + /** * @inheritDoc */ @@ -30,6 +33,7 @@ final class IconExtension extends AbstractExtension /** * @param array $options + * [ options here ] */ public function renderIcon(array $userOptions): string { @@ -43,6 +47,16 @@ final class IconExtension extends AbstractExtension $markup = $this->addTitleToMarkup($cleanSvgMarkup, $options['title']); } + if ($options['size'] < 0) + throw new \InvalidArgumentException('Size must not be negative'); + + if (!is_int($options['size'])) + throw new \TypeError('Size value must be an integer'); + + + $markup = $this->setSize($markup, $options['size']); + + return $markup; } @@ -87,12 +101,35 @@ final class IconExtension extends AbstractExtension private function addTitleToMarkup(string $markup, ?string $title): string { - if (null === $title) { + if (null === $title) return $markup; - } return preg_replace('/(\n?)/', "$1$title", $markup); } + + private function setSize(string $content, int $size): string + { + $svgAsXmlElement = new \SimpleXMLElement($content); + $svgAsXmlElement = $this->addAttributeToXmlElement($svgAsXmlElement, 'width', $size); + $svgAsXmlElement = $this->addAttributeToXmlElement($svgAsXmlElement, 'height', $size); + return $this->removeXMLDeclaration($svgAsXmlElement->saveXML()); + } + + private function removeXMLDeclaration(string $content): string + { + return trim(preg_replace('/<\?xml.*\?>/', '', $content)); + } + + private function addAttributeToXmlElement(\SimpleXMLElement $xml, string $attrName, mixed $attrValue): \SimpleXMLElement + { + if (isset($xml->attributes()->$attrName)) { + $xml->attributes()->$attrName = strval($attrValue); + } else { + $xml->addAttribute($attrName, strval($attrValue)); + } + + return $xml; + } } class IconNotFound extends \Exception {}; diff --git a/tests/Twig/Functions/IconExtensionTest.php b/tests/Twig/Functions/IconExtensionTest.php index 50a48d4..e633c39 100644 --- a/tests/Twig/Functions/IconExtensionTest.php +++ b/tests/Twig/Functions/IconExtensionTest.php @@ -71,4 +71,76 @@ class IconExtensionTest extends TestCase $this->expectException(\InvalidArgumentException::class); $this->icon->renderIcon(['icon' => self::ICON, 'title' => '']); } + + public function testThrowsWhenPassedNegativeSizeValue(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->icon->renderIcon(['icon' => self::ICON, 'size' => -1]); + } + + public function testThrowsWhenSizeIsNotAnInt(): void + { + $this->expectException(\TypeError::class); + $this->icon->renderIcon(['icon' => self::ICON, 'size' => 1.0]); + } + + public function testSvgWidthIsSet(): void + { + $regex = '/^icon->renderIcon(['icon' => self::ICON, 'size' => 99]); + $this->assertMatchesRegularExpression($regex, $content); + } + + public function testSvgHeightIsSet(): void + { + $regex = '/^icon->renderIcon(['icon' => self::ICON, 'size' => 99]); + $this->assertMatchesRegularExpression($regex, $content); + } + + public function testNoXmlDeclarationIsAdded(): void + { + $content = $this->icon->renderIcon(['icon' => self::ICON, 'size' => 99]); + $this->assertStringNotContainsString('icon->renderIcon(['icon' => self::ICON, 'size' => 99]); + $timesMatched = preg_match_all('/width="99"/', $content); + $this->assertSame(1, $timesMatched); + } + + public function testOnlyOneHeightAttributeIsSet(): void + { + $content = $this->icon->renderIcon(['icon' => self::ICON, 'size' => 99]); + $timesMatched = preg_match_all('/height="99"/', $content); + $this->assertSame(1, $timesMatched); + } + + public function testDefaultSizeIsSetOnSvgIfNoSizeOptionPassed(): void + { + $defaultSize = IconExtension::DEFAULT_SIZE; + $content = $this->icon->renderIcon(['icon' => self::ICON]); + + $regex = "/^assertMatchesRegularExpression($regex, $content); + + $regex = "/^assertMatchesRegularExpression($regex, $content); + } + + public function testDefaultSizeIsOnlySetOnce(): void + { + $defaultSize = IconExtension::DEFAULT_SIZE; + $content = $this->icon->renderIcon(['icon' => self::ICON]); + + $widthRegex = "/width=\"{$defaultSize}\"/"; + $timesMatched = preg_match_all($widthRegex, $content); + $this->assertSame(1, $timesMatched); + + $heightRegex = "/height=\"{$defaultSize}\"/"; + $timesMatched = preg_match_all($heightRegex, $content); + $this->assertSame(1, $timesMatched); + } } diff --git a/tests/icons/test.svg b/tests/icons/test.svg index 2c9ec70..0296bda 100644 --- a/tests/icons/test.svg +++ b/tests/icons/test.svg @@ -1,5 +1,5 @@ Some cross lol - - + +