Skip to content

Commit

Permalink
[Icons] Group icons fetching per prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
smnandre committed Nov 8, 2024
1 parent 8215922 commit 218c000
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/Icons/src/Iconify.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,54 @@ public function fetchIcon(string $prefix, string $name): Icon
]);
}

public function fetchIcons(string $prefix, array $names): array
{
if (!isset($this->sets()[$prefix])) {
throw new IconNotFoundException(\sprintf('The icon set "%s" does not exist on iconify.design.', $prefix));
}

// Sort to enforce cache hits
sort($names);
$queryString = implode(',', $names);
if (!preg_match('#^[a-z0-9-,]+$#', $queryString)) {
throw new \InvalidArgumentException('Invalid icon names.');
}

// URL must be 500 chars max (iconify limit)
// -39 chars: https://api.iconify.design/XXX.json?icons=
// -safe margin
if (450 < \strlen($prefix.$queryString)) {
throw new \InvalidArgumentException('The query string is too long.');
}

$response = $this->http->request('GET', \sprintf('/%s.json', $prefix), [
'headers' => [
'Accept' => 'application/json',
],
'query' => [
'icons' => strtolower($queryString),
],
]);

if (200 !== $response->getStatusCode()) {
throw new IconNotFoundException(\sprintf('The icon set "%s" does not exist on iconify.design.', $prefix));
}

$data = $response->toArray();

$icons = [];
foreach ($data['icons'] as $iconName => $iconData) {
$height = $iconData['height'] ?? $data['height'] ??= $this->sets()[$prefix]['height'] ?? null;
$width = $iconData['width'] ?? $data['width'] ??= $this->sets()[$prefix]['width'] ?? null;

$icons[$iconName] = new Icon($iconData['body'], [
'viewBox' => \sprintf('0 0 %d %d', $width ?? $height, $height ?? $width),
]);
}

return $icons;
}

public function hasIconSet(string $prefix): bool
{
return isset($this->sets()[$prefix]);
Expand Down
66 changes: 66 additions & 0 deletions src/Icons/tests/Unit/IconifyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\JsonMockResponse;
use Symfony\UX\Icons\Exception\IconNotFoundException;
use Symfony\UX\Icons\Icon;
use Symfony\UX\Icons\Iconify;

/**
Expand Down Expand Up @@ -162,6 +163,71 @@ public function testFetchIconThrowsWhenStatusCodeNot200(): void
$iconify->fetchIcon('bi', 'heart');
}

public function testFetchIcons(): void
{
$iconify = new Iconify(
cache: new NullAdapter(),
endpoint: 'https://example.com',
http: new MockHttpClient([
new JsonMockResponse([
'bi' => [],
]),
new JsonMockResponse([
'icons' => [
'heart' => [
'body' => '<path d="M0 0h24v24H0z" fill="none"/>',
'height' => 17,
],
'bar' => [
'body' => '<path d="M0 0h24v24H0z" fill="none"/>',
'height' => 17,
],
],
]),
]),
);

$icons = $iconify->fetchIcons('bi', ['heart', 'bar']);

$this->assertCount(2, $icons);
$this->assertSame(['heart', 'bar'], array_keys($icons));
$this->assertContainsOnlyInstancesOf(Icon::class, $icons);
}

public function testFetchIconsThrowsWithInvalidIconNames(): void
{
$iconify = new Iconify(
cache: new NullAdapter(),
endpoint: 'https://example.com',
http: new MockHttpClient([
new JsonMockResponse([
'bi' => [],
]),
]),
);

$this->expectException(\InvalidArgumentException::class);

$iconify->fetchIcons('bi', ['à', 'foo']);
}

public function testFetchIconsThrowsWithTooManyIcons(): void
{
$iconify = new Iconify(
cache: new NullAdapter(),
endpoint: 'https://example.com',
http: new MockHttpClient([
new JsonMockResponse([
'bi' => [],
]),
]),
);

$this->expectException(\InvalidArgumentException::class);

$iconify->fetchIcons('bi', array_fill(0, 50, '1234567890'));
}

public function testGetMetadata(): void
{
$responseFile = __DIR__.'/../Fixtures/Iconify/collections.json';
Expand Down

0 comments on commit 218c000

Please sign in to comment.