Skip to content

Commit

Permalink
Add tests for preload function
Browse files Browse the repository at this point in the history
`entryPoints()` accept dynamic options
`preload()` accept dynamic options
  • Loading branch information
ElGigi committed Feb 11, 2022
1 parent 0f3dad1 commit 933a71a
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 33 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. This projec
to [Semantic Versioning] (http://semver.org/). For change log format,
use [Keep a Changelog] (http://keepachangelog.com/).

## [2.2.0] - 2022-02-11

### Added

- Tests for `preload` function

### Changed

- `entryPoints()` accept dynamic options
- `preload()` accept dynamic options

## [2.1.0] - 2022-01-25

### Changed
Expand Down
125 changes: 96 additions & 29 deletions src/Extension/AssetRuntimeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,12 @@ public function entryPoints(
$entryPoint,
array_merge(['as' => 'script'], $preloadOptions)
);
unset($options['preload']);
}

// Defer/Async?
$deferOrAsync = ($options['defer'] ?? false) === true ? ' defer' : '';
$deferOrAsync .= ($options['async'] ?? false) === true ? ' async' : '';

$output .= sprintf(
'<script src="%s"%s></script>',
strip_tags($entryPoint),
$deferOrAsync
'<script%s></script>',
$this->attributes(array_replace($options, ['src' => $entryPoint])),
) . PHP_EOL;
break;
case 'css':
Expand All @@ -133,9 +129,18 @@ public function entryPoints(
$entryPoint,
array_merge(['as' => 'style'], $preloadOptions)
);
unset($options['preload']);
}

$output .= sprintf('<link rel="stylesheet" href="%s">', strip_tags($entryPoint)) . PHP_EOL;
$output .= sprintf(
'<link%s>',
$this->attributes(
array_replace(
$options,
['rel' => 'stylesheet', 'href' => $entryPoint]
)
),
) . PHP_EOL;
break;
}
}
Expand Down Expand Up @@ -166,6 +171,38 @@ public function entryPointsList(string|array $entry, ?string $type = null): arra
}
}

/**
* Make attributes.
*
* @param $attrs
* @param string|null $prefix
*
* @return string
*/
private function attributes($attrs, ?string $prefix = null): string
{
$output = '';

foreach ($attrs as $key => $value) {
if (null === $value || false === $value) {
continue;
}

if (!empty($prefix)) {
$key = $prefix . '-' . $key;
}

if (is_array($value)) {
$output .= $this->attributes($value, $key);
continue;
}

$output .= ' ' . $key . (true !== $value ? '="' . htmlspecialchars($value) . '"' : '');
}

return $output;
}

/**
* Function preload to pre loading of request for HTTP 2 protocol.
*
Expand All @@ -176,42 +213,35 @@ public function entryPointsList(string|array $entry, ?string $type = null): arra
*/
public function preload(string $link, array $parameters = []): string
{
$push = !(!empty($parameters['nopush']) && $parameters['nopush'] == true);

if (true === $push && in_array(md5($link), $this->h2pushCache)) {
if (($push = (false === ($parameters['nopush'] ?? false))) && in_array(md5($link), $this->h2pushCache)) {
return $link;
}

$header = sprintf('Link: <%s>; rel=preload', $link);

// as
if (!empty($parameters['as'])) {
$header = sprintf('%s; as=%s', $header, $parameters['as']);
}
// type
if (!empty($parameters['type'])) {
$header = sprintf('%s; type=%s', $header, $parameters['as']);
}
// crossorigin
if (!empty($parameters['crossorigin']) && $parameters['crossorigin'] == true) {
$header .= '; crossorigin';
}
// nopush
if (!$push) {
$header .= '; nopush';
foreach ($parameters as $key => $value) {
if (!is_scalar($value)) {
continue;
}

if (false === $value) {
continue;
}

$header .= '; ' . $key . (true !== $value ? '=' . $value : '');
}

if (true === headers_sent()) {
if (true === $this->isHeadersSent()) {
return $link;
}

header($header, false);
$this->sendHeader($header, false);

// Cache
if ($push) {
$this->h2pushCache[] = md5($link);

setcookie(
$this->setCookie(
sprintf('%s[%s]', self::H2PUSH_CACHE_COOKIE, md5($link)),
'1',
[
Expand All @@ -227,4 +257,41 @@ public function preload(string $link, array $parameters = []): string

return $link;
}

/**
* Is headers sent?
*
* @return bool
*/
public function isHeadersSent(): bool
{
return headers_sent();
}

/**
* Send header.
*
* @param string $header
* @param bool $replace
*
* @return void
*/
public function sendHeader(string $header, bool $replace = true): void
{
header($header, $replace);
}

/**
* Set cookie.
*
* @param string $name
* @param string $value
* @param array $options
*
* @return void
*/
public function setCookie(string $name, string $value, array $options = []): void
{
setcookie($name, $value, $options);
}
}
104 changes: 100 additions & 4 deletions tests/Extension/AssetRuntimeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,21 @@ public function testEntryPoints_withOptions()
$extensionRuntime = new AssetRuntimeExtension($this->assets);

$this->assertEquals(
'<link rel="stylesheet" href="/assets/css/website.css">' . PHP_EOL .
'<script src="/assets/js/website.js" defer async></script>' . PHP_EOL .
'<script src="/assets/js/vendor.js" defer async></script>' . PHP_EOL,
$extensionRuntime->entryPoints('website', options: ['async' => true, 'defer' => true])
'<link async defer attr="fake" data-first="value1" data-second="value2" data-third rel="stylesheet" href="/assets/css/website.css">' . PHP_EOL .
'<script async defer attr="fake" data-first="value1" data-second="value2" data-third src="/assets/js/website.js"></script>' . PHP_EOL .
'<script async defer attr="fake" data-first="value1" data-second="value2" data-third src="/assets/js/vendor.js"></script>' . PHP_EOL,
$extensionRuntime->entryPoints('website', options: [
'async' => true,
'defer' => true,
'attr' => 'fake',
'attr2' => null,
'data' => [
'first' => 'value1',
'second' => 'value2',
'third' => true,
'none' => null,
]
])
);
}

Expand Down Expand Up @@ -137,4 +148,89 @@ public function testEntryPointsList_notFound()

$this->assertEquals([], $extensionRuntime->entryPointsList('fake'));
}

public function providesPreload()
{
return [
[
'link' => 'https://getberlioz.com/fake',
'parameters' => [
'crossorigin' => false,
],
'expectedHeader' => 'Link: <https://getberlioz.com/fake>; rel=preload',
'expectedCookie' => [
'h2pushes[d51704684c8d3f1febc7c281cc2c8f26]',
'1',
[
'expires' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
]
],
],
[
'link' => 'https://getberlioz.com/fake',
'parameters' => [
'crossorigin' => true,
],
'expectedHeader' => 'Link: <https://getberlioz.com/fake>; rel=preload; crossorigin',
'expectedCookie' => [
'h2pushes[d51704684c8d3f1febc7c281cc2c8f26]',
'1',
[
'expires' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
]
],
],
[
'link' => 'https://getberlioz.com/fake',
'parameters' => [
'nopush' => true,
'crossorigin' => true,
],
'expectedHeader' => 'Link: <https://getberlioz.com/fake>; rel=preload; nopush; crossorigin',
'expectedCookie' => null,
]
];
}

/**
* @dataProvider providesPreload
*/
public function testPreload(string $link, array $parameters, ?string $expectedHeader, ?array $expectedCookie)
{
$headerArguments = $cookieArguments = null;

$assetRuntimeMock = $this->createPartialMock(
AssetRuntimeExtension::class,
['isHeadersSent', 'sendHeader', 'setCookie']
);
$assetRuntimeMock
->method('isHeadersSent')
->willReturnCallback(fn() => false);
$assetRuntimeMock
->method('sendHeader')
->willReturnCallback(function ($header) use (&$headerArguments) {
$headerArguments = $header;
});
$assetRuntimeMock
->method('setCookie')
->willReturnCallback(function (...$args) use (&$cookieArguments) {
$cookieArguments = $args;
});

$result = $assetRuntimeMock->preload($link, $parameters);

$this->assertEquals($link, $result);
$this->assertEquals($expectedHeader, $headerArguments);
$this->assertEquals($expectedCookie, $cookieArguments);
}
}

0 comments on commit 933a71a

Please sign in to comment.