diff --git a/README.md b/README.md
index 3c3cb8f..e0ede47 100644
--- a/README.md
+++ b/README.md
@@ -195,6 +195,29 @@ Please review [`Collection` source code](./src/Enqueue/Collection.php) to find o
+### Retrieving registered/enqueued assets' collection
+
+The `Assets` class provides a `Assets::collection()` method that returns a `Collection` instance with all the assets that have been enqueued/registered. The collection allows us to act on individual assets (retrieved via `Collection::oneByName()` or `Collection::oneByHandle()`) or collectively on all or some assets (filtered using one of the many methods `Collection` provides, see above).
+
+Here's an example on how this could be leveraged for obtaining "automatic bulk registration" for all our assets in few lines of code that loop files in the assets directory:
+
+```php
+$assets = Assets::forTheme('/dist')->useDependencyExtractionData();
+
+foreach (glob($assets->context()->basePath() . '*.{css,js}', GLOB_BRACE) as $file) {
+ str_ends_with($file, '.css')
+ ? $assets->registerStyle(basename($file, '.css'))
+ : $assets->registerScript(basename($file, '.js'));
+}
+
+add_action('admin_enqueue_scripts'), fn () => $assets->collection()->keep('*-admin')->enqueue());
+add_action('wp_enqueue_scripts'), fn () => $assets->collection()->keep('*-view')->enqueue());
+```
+
+Please note: because `Collection` has an immutable design, do not store the result of `Assets::collection()` but always call that method to retrieve an up-to-date collection.
+
+
+
### Debug
diff --git a/psalm.xml b/psalm.xml
index 6ca6724..eae66bf 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -25,5 +25,6 @@
+
diff --git a/src/Assets.php b/src/Assets.php
index c69bbc9..d68cef6 100644
--- a/src/Assets.php
+++ b/src/Assets.php
@@ -14,10 +14,6 @@
namespace Brain\Assets;
-use Brain\Assets\Enqueue\Collection;
-use Brain\Assets\Enqueue\JsEnqueue;
-use Brain\Assets\Enqueue\Strategy;
-
class Assets
{
public const CSS = 'css';
@@ -26,6 +22,7 @@ class Assets
public const VIDEO = 'videos';
public const FONT = 'fonts';
+ private Enqueue\Collection $collection;
private UrlResolver\MinifyResolver|null $minifyResolver = null;
private string $handlePrefix;
private bool $addVersion = true;
@@ -33,6 +30,7 @@ class Assets
private bool $useDepExtractionData = false;
/** @var array */
private array $subFolders = [];
+ private bool $removalConfigured = false;
/**
* @param string $mainPluginFilePath
@@ -138,6 +136,15 @@ final protected function __construct(private Factory $factory)
{
// Store separately from name, so we can enable & disable as well as changing it.
$this->handlePrefix = $this->context()->name();
+ $this->collection = Enqueue\Collection::new($this);
+ }
+
+ /**
+ * @return Enqueue\Collection
+ */
+ public function collection(): Enqueue\Collection
+ {
+ return clone $this->collection;
}
/**
@@ -542,8 +549,8 @@ public function enqueueScript(
* @param string $name
* @param string $url
* @param array $deps
- * @param Strategy|bool|array|string|null $strategy
- * @return JsEnqueue
+ * @param Enqueue\Strategy|bool|array|string|null $strategy
+ * @return Enqueue\JsEnqueue
*/
public function registerExternalScript(
string $name,
@@ -572,6 +579,42 @@ public function enqueueExternalScript(
return $this->doEnqueueOrRegisterScript('enqueue', $name, $url, $deps, $strategy);
}
+ /**
+ * @param string $name
+ * @return static
+ */
+ public function dequeueStyle(string $name): static
+ {
+ return $this->deregisterOrDequeue($name, self::CSS, deregister: false);
+ }
+
+ /**
+ * @param string $name
+ * @return static
+ */
+ public function deregisterStyle(string $name): static
+ {
+ return $this->deregisterOrDequeue($name, self::CSS, deregister: true);
+ }
+
+ /**
+ * @param string $name
+ * @return static
+ */
+ public function dequeueScript(string $name): static
+ {
+ return $this->deregisterOrDequeue($name, self::JS, deregister: false);
+ }
+
+ /**
+ * @param string $name
+ * @return static
+ */
+ public function deregisterScript(string $name): static
+ {
+ return $this->deregisterOrDequeue($name, self::JS, deregister: true);
+ }
+
/**
* @param string $name
* @return string
@@ -591,10 +634,13 @@ public function handleForName(string $name): string
/**
* @param array $jsDeps
* @param array $cssDeps
- * @return Collection
+ * @return Enqueue\Collection
*/
- public function registerAllFromManifest(array $jsDeps = [], array $cssDeps = []): Collection
- {
+ public function registerAllFromManifest(
+ array $jsDeps = [],
+ array $cssDeps = []
+ ): Enqueue\Collection {
+
$collection = [];
$urls = $this->factory->manifestUrlResolver()->resolveAll();
foreach ($urls as $name => $url) {
@@ -605,7 +651,7 @@ public function registerAllFromManifest(array $jsDeps = [], array $cssDeps = [])
$collection[] = $registered;
}
- $registeredAll = Collection::new($this, ...$collection);
+ $registeredAll = Enqueue\Collection::new($this, ...$collection);
do_action('brain.assets.registered-all-from-manifest', $registeredAll);
return $registeredAll;
@@ -627,22 +673,32 @@ private function doEnqueueOrRegisterStyle(
string $media
): Enqueue\CssEnqueue {
+ $isEnqueue = $type === 'enqueue';
$handle = $this->handleForName($name);
+
+ $existing = $this->maybeRegistered($handle, $isEnqueue, self::CSS);
+ if ($existing instanceof Enqueue\CssEnqueue) {
+ return $existing;
+ }
+
[$url, $useMinify] = ($url === null)
? $this->assetUrlForEnqueue($name, self::CSS)
: [$this->adjustAbsoluteUrl($url), false];
$deps = $this->prepareDeps($deps, $url, $useMinify);
- $isEnqueue = $type === 'enqueue';
/** @var callable $callback */
$callback = $isEnqueue ? 'wp_enqueue_style' : 'wp_register_style';
$callback($handle, $url, $deps, null, $media);
- return $isEnqueue
+ $enqueued = $isEnqueue
? Enqueue\CssEnqueue::new($handle)
: Enqueue\CssEnqueue::newRegistration($handle);
+ $this->collection = $this->collection->append($enqueued);
+ $this->setupRemoval();
+
+ return $enqueued;
}
/**
@@ -650,7 +706,7 @@ private function doEnqueueOrRegisterStyle(
* @param string $name
* @param string|null $url
* @param array $deps
- * @param Strategy|bool|array|string|null $strategy
+ * @param Enqueue\Strategy|bool|array|string|null $strategy
* @return Enqueue\JsEnqueue
*/
private function doEnqueueOrRegisterScript(
@@ -661,7 +717,14 @@ private function doEnqueueOrRegisterScript(
Enqueue\Strategy|bool|array|string|null $strategy
): Enqueue\JsEnqueue {
+ $isEnqueue = $type === 'enqueue';
$handle = $this->handleForName($name);
+
+ $existing = $this->maybeRegistered($handle, $isEnqueue, self::JS);
+ if ($existing instanceof Enqueue\JsEnqueue) {
+ return $existing;
+ }
+
[$url, $useMinify] = ($url === null)
? $this->assetUrlForEnqueue($name, self::JS)
: [$this->adjustAbsoluteUrl($url), false];
@@ -669,15 +732,42 @@ private function doEnqueueOrRegisterScript(
$strategy = Enqueue\Strategy::new($strategy);
$deps = $this->prepareDeps($deps, $url, $useMinify);
- $isEnqueue = $type === 'enqueue';
/** @var callable $callback */
$callback = $isEnqueue ? 'wp_enqueue_script' : 'wp_register_script';
$callback($handle, $url, $deps, null, $strategy->toArray());
- return $isEnqueue
+ $enqueued = $isEnqueue
? Enqueue\JsEnqueue::new($handle, $strategy)
: Enqueue\JsEnqueue::newRegistration($handle, $strategy);
+ $this->collection = $this->collection->append($enqueued);
+ $this->setupRemoval();
+
+ return $enqueued;
+ }
+
+ /**
+ * @param string $name
+ * @param 'css'|'js' $type
+ * @param bool $deregister
+ * @return static
+ */
+ private function deregisterOrDequeue(string $name, string $type, bool $deregister): static
+ {
+ $handle = $this->handleForName($name);
+ $existing = $this->maybeRegistered($handle, null, $type);
+ if ($existing instanceof Enqueue\Enqueue) {
+ $deregister ? $existing->deregister() : $existing->dequeue();
+
+ return $this;
+ }
+
+ match ($type) {
+ 'css' => $deregister ? wp_deregister_style($handle) : wp_dequeue_script($handle),
+ 'js' => $deregister ? wp_deregister_script($handle) : wp_dequeue_script($handle),
+ };
+
+ return $this;
}
/**
@@ -934,4 +1024,45 @@ private function unminifiedUrl(string $url): ?string
return null;
}
+
+ /**
+ * @param string $handle
+ * @param bool|null $enqueue
+ * @param 'css'|'js' $type
+ * @return Enqueue\Enqueue|null
+ */
+ private function maybeRegistered(string $handle, ?bool $enqueue, string $type): ?Enqueue\Enqueue
+ {
+ $existing = $this->collection()->oneByHandle($handle, $type);
+ if (($existing !== null) || ($enqueue === null)) {
+ if (($existing !== null) && ($enqueue === true) && !$existing->isEnqueued()) {
+ $existing->enqueue();
+ }
+
+ return $existing;
+ }
+
+ if (($type === 'css') && wp_styles()->query($handle)) {
+ wp_dequeue_style($handle);
+ wp_deregister_style($handle);
+ } elseif (($type === 'js') && wp_scripts()->query($handle)) {
+ wp_dequeue_script($handle);
+ wp_deregister_script($handle);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return void
+ */
+ private function setupRemoval(): void
+ {
+ $this->removalConfigured or $this->removalConfigured = add_action(
+ 'brain.assets.deregistered',
+ function (Enqueue\Enqueue $enqueue): void {
+ $this->collection = $this->collection->remove($enqueue);
+ }
+ );
+ }
}
diff --git a/src/Enqueue/AbstractEnqueue.php b/src/Enqueue/AbstractEnqueue.php
index 3272ebc..73f019c 100644
--- a/src/Enqueue/AbstractEnqueue.php
+++ b/src/Enqueue/AbstractEnqueue.php
@@ -66,6 +66,7 @@ final public function deregister(): void
$this->isCss()
? wp_deregister_style($this->handle())
: wp_deregister_script($this->handle());
+ do_action('brain.assets.deregistered', $this);
}
/**
diff --git a/src/Enqueue/Collection.php b/src/Enqueue/Collection.php
index f6aa6f8..a759eea 100644
--- a/src/Enqueue/Collection.php
+++ b/src/Enqueue/Collection.php
@@ -42,6 +42,42 @@ final protected function __construct(
$this->collection = $enqueues;
}
+ /**
+ * @param Enqueue $enqueue
+ * @return static
+ */
+ public function append(Enqueue $enqueue): static
+ {
+ $collection = [];
+ $enqueueId = $this->id($enqueue);
+ foreach ($this->collection as $item) {
+ if ($enqueueId === $this->id($item)) {
+ return clone $this;
+ }
+ $collection[] = $item;
+ }
+ $collection[] = $enqueue;
+
+ return new static($this->assets, ...$collection);
+ }
+
+ /**
+ * @param Enqueue $enqueue
+ * @return static
+ */
+ public function remove(Enqueue $enqueue): static
+ {
+ $collection = [];
+ $enqueueId = $this->id($enqueue);
+ foreach ($this->collection as $item) {
+ if ($this->id($item) !== $enqueueId) {
+ $collection[] = $enqueue;
+ }
+ }
+
+ return new static($this->assets, ...$collection);
+ }
+
/**
* @param string $pattern
* @param 'css'|'js'|null $type
@@ -152,13 +188,11 @@ public function merge(Collection $collection): static
{
$merged = [];
foreach ($this->collection as $enqueue) {
- $id = $enqueue->handle() . ($enqueue->isJs() ? '--js' : '--css');
- $merged[$id] = $enqueue;
+ $merged[$this->id($enqueue)] = $enqueue;
}
foreach ($collection->collection as $enqueue) {
- $id = $enqueue->handle() . ($enqueue->isJs() ? '--js' : '--css');
- $merged[$id] = $enqueue;
+ $merged[$this->id($enqueue)] = $enqueue;
}
return static::new($this->assets, ...array_values($merged));
@@ -172,13 +206,11 @@ public function diff(Collection $collection): static
{
$diff = [];
foreach ($this->collection as $enqueue) {
- $id = $enqueue->handle() . ($enqueue->isJs() ? '--js' : '--css');
- $diff[$id] = $enqueue;
+ $diff[$this->id($enqueue)] = $enqueue;
}
foreach ($collection->collection as $enqueue) {
- $id = $enqueue->handle() . ($enqueue->isJs() ? '--js' : '--css');
- unset($diff[$id]);
+ unset($diff[$this->id($enqueue)]);
}
return static::new($this->assets, ...array_values($diff));
@@ -321,7 +353,7 @@ public function lastOf(callable $callback): ?Enqueue
/**
* @param string $name
* @param 'css'|'js'|null $type
- * @return Enqueue|null
+ * @return ($type is 'css' ? CssEnqueue|null : ($type is 'js' ? JsEnqueue|null : Enqueue))
*/
public function oneByName(string $name, ?string $type = null): ?Enqueue
{
@@ -348,7 +380,7 @@ public function oneByName(string $name, ?string $type = null): ?Enqueue
/**
* @param string $handle
* @param 'css'|'js'|null $type
- * @return Enqueue|null
+ * @return ($type is 'css' ? CssEnqueue|null : ($type is 'js' ? JsEnqueue|null : Enqueue|null))
*/
public function oneByHandle(string $handle, ?string $type = null): ?Enqueue
{
@@ -410,6 +442,16 @@ public function enqueue(): void
}
}
+ /**
+ * @return void
+ */
+ public function dequeue(): void
+ {
+ foreach ($this->collection as $enqueue) {
+ $enqueue->dequeue();
+ }
+ }
+
/**
* @param non-empty-string $name
* @param 'css'|'js'|null $type
@@ -561,6 +603,15 @@ private function isRegex(string $pattern): bool
return array_unique($flags) === $flags;
}
+ /**
+ * @param Enqueue $enqueue
+ * @return string
+ */
+ private function id(Enqueue $enqueue): string
+ {
+ return $enqueue->handle() . ($enqueue->isJs() ? '--js' : '--css');
+ }
+
/**
* @param string $str
* @param 'handle'|'name' $varName
diff --git a/src/Enqueue/Enqueue.php b/src/Enqueue/Enqueue.php
index ffedcf3..cd19e7e 100644
--- a/src/Enqueue/Enqueue.php
+++ b/src/Enqueue/Enqueue.php
@@ -41,6 +41,11 @@ public function isEnqueued(): bool;
*/
public function dequeue(): static;
+ /**
+ * @return void
+ */
+ public function deregister(): void;
+
/**
* @return static
*/
diff --git a/src/Enqueue/JsEnqueue.php b/src/Enqueue/JsEnqueue.php
index 1ebd56e..99baf49 100644
--- a/src/Enqueue/JsEnqueue.php
+++ b/src/Enqueue/JsEnqueue.php
@@ -63,9 +63,9 @@ public function handle(): string
/**
* @param string $condition
- * @return JsEnqueue
+ * @return static
*/
- public function withCondition(string $condition): JsEnqueue
+ public function withCondition(string $condition): static
{
wp_scripts()->add_data($this->handle, 'conditional', $condition);
@@ -74,9 +74,9 @@ public function withCondition(string $condition): JsEnqueue
/**
* @param string $jsCode
- * @return JsEnqueue
+ * @return static
*/
- public function prependInline(string $jsCode): JsEnqueue
+ public function prependInline(string $jsCode): static
{
wp_add_inline_script($this->handle, $jsCode, 'before');
@@ -85,9 +85,9 @@ public function prependInline(string $jsCode): JsEnqueue
/**
* @param string $jsCode
- * @return JsEnqueue
+ * @return static
*/
- public function appendInline(string $jsCode): JsEnqueue
+ public function appendInline(string $jsCode): static
{
wp_add_inline_script($this->handle, $jsCode, 'after');
$this->removeStrategy();
@@ -98,9 +98,9 @@ public function appendInline(string $jsCode): JsEnqueue
/**
* @param string $objectName
* @param array $data
- * @return JsEnqueue
+ * @return static
*/
- public function localize(string $objectName, array $data): JsEnqueue
+ public function localize(string $objectName, array $data): static
{
wp_localize_script($this->handle, $objectName, $data);
@@ -108,31 +108,46 @@ public function localize(string $objectName, array $data): JsEnqueue
}
/**
- * @return JsEnqueue
+ * @param Strategy $strategy
+ * @return static
*/
- public function useAsync(): JsEnqueue
+ public function useStrategy(Strategy $strategy): static
{
- $this->useStrategyAttribute(Strategy::ASYNC);
+ $this->strategy = $strategy;
+ $strategyName = match (true) {
+ $strategy->isDefer() => Strategy::DEFER,
+ $strategy->isAsync() => Strategy::ASYNC,
+ default => false,
+ };
+
+ wp_scripts()->add_data($this->handle, 'strategy', $strategyName);
+ wp_scripts()->add_data($this->handle, 'group', $strategy->inFooter() ? 1 : false);
return $this;
}
/**
- * @return JsEnqueue
+ * @return static
*/
- public function useDefer(): JsEnqueue
+ public function useAsync(): static
{
- $this->useStrategyAttribute(Strategy::DEFER);
+ return $this->useStrategy(Strategy::newAsync($this->strategy?->inFooter() ?? false));
+ }
- return $this;
+ /**
+ * @return static
+ */
+ public function useDefer(): static
+ {
+ return $this->useStrategy(Strategy::newDefer($this->strategy?->inFooter() ?? false));
}
/**
* @param string $name
* @param string $value
- * @return JsEnqueue
+ * @return static
*/
- public function useAttribute(string $name, ?string $value): JsEnqueue
+ public function useAttribute(string $name, ?string $value): static
{
$nameLower = strtolower($name);
if (($nameLower !== 'async') && ($nameLower !== 'defer')) {
@@ -152,31 +167,15 @@ public function useAttribute(string $name, ?string $value): JsEnqueue
/**
* @param callable $callback
- * @return JsEnqueue
+ * @return static
*/
- public function addFilter(callable $callback): JsEnqueue
+ public function addFilter(callable $callback): static
{
$this->setupFilters()->add($callback);
return $this;
}
- /**
- * @param 'async'|'defer' $strategy
- * @return void
- *
- * phpcs:disable Generic.Metrics.CyclomaticComplexity
- */
- private function useStrategyAttribute(string $strategyName): void
- {
- wp_scripts()->add_data($this->handle, 'strategy', $strategyName);
-
- $this->strategy = match ($strategyName) {
- Strategy::ASYNC => Strategy::newAsync($this->strategy?->inFooter() ?? false),
- Strategy::DEFER => Strategy::newDefer($this->strategy?->inFooter() ?? false),
- };
- }
-
/**
* @return void
*/
diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php
index 6c461db..f6fdd24 100644
--- a/tests/src/TestCase.php
+++ b/tests/src/TestCase.php
@@ -114,6 +114,85 @@ protected function factoryPathFinder(bool $useAlt, bool $debug): PathFinder
return PathFinder::new($this->factoryContext($useAlt, $debug));
}
+ /**
+ * @param string $handle
+ * @param array $extra
+ * @return \_WP_Dependency
+ *
+ * phpcs:disable Inpsyde.CodeQuality.NestingLevel
+ */
+ protected function factoryWpDependency(string $handle, array $extra = []): \_WP_Dependency
+ {
+ // phpcs:enable Inpsyde.CodeQuality.NestingLevel
+ \Mockery::spy(\_WP_Dependency::class);
+
+ // phpcs:disable
+ /** @psalm-suppress PropertyNotSetInConstructor */
+ return new class ($handle, $extra) extends \_WP_Dependency
+ {
+ /** @var string */
+ public $handle;
+ /** @var array */
+ public $extra;
+
+ public function __construct(string $handle, array $extra)
+ {
+ $this->handle = $handle;
+ $this->extra = $extra;
+ }
+
+ public function add_data(mixed $name, mixed $data): bool
+ {
+ if (is_scalar($name)) {
+ $this->extra[$name] = $data;
+
+ return true;
+ }
+
+ return false;
+ }
+ };
+ // phpcs:enable
+ }
+
+ /**
+ * @param 'style'|'script' $type
+ * @param array> $deps
+ * @return WpAssetsStub
+ */
+ protected function mockWpDependencies(string $type, array $deps = []): WpAssetsStub
+ {
+ assert(in_array($type, ['style', 'script'], true));
+
+ $stub = new WpAssetsStub();
+ foreach ($deps as $status => $elements) {
+ assert(in_array($status, ['registered', 'enqueued', 'to_do', 'done'], true));
+ foreach ($elements as $elementData) {
+ $handle = $elementData[0];
+ $extra = $elementData[1] ?? [];
+ assert(is_string($handle) && ($handle !== ''));
+ assert(is_array($extra));
+ $dep = $this->factoryWpDependency($handle, $extra);
+ $stub->addWpDependencyStub($dep, $status);
+ }
+ }
+
+ Monkey\Functions\expect("wp_{$type}s")->zeroOrMoreTimes()->andReturn($stub);
+ Monkey\Functions\expect("wp_{$type}_is")
+ ->zeroOrMoreTimes()
+ ->andReturnUsing(
+ static function (
+ string $handle,
+ string $status = 'enqueued'
+ ) use ($stub): \_WP_Dependency|bool {
+
+ return $stub->query($handle, $status);
+ }
+ );
+
+ return $stub;
+ }
+
/**
* @param string $url
* @return array{path: string|null, scheme: string|null, ver: string|null}
diff --git a/tests/src/WpAssetsStub.php b/tests/src/WpAssetsStub.php
index 01ada85..c8c8f32 100644
--- a/tests/src/WpAssetsStub.php
+++ b/tests/src/WpAssetsStub.php
@@ -16,17 +16,59 @@
class WpAssetsStub
{
+ /** @var array> */
public array $data = [];
+ /** @var array}> */
+ public array $dependencies = [];
/**
* @param string $handle
- * @param mixed ...$args
+ * @param string $key
+ * @param mixed $value
*
* phpcs:disable PSR1.Methods.CamelCapsMethodName
*/
- public function add_data(string $handle, mixed ...$args): void
+ public function add_data(string $handle, string $key, mixed $value): void
{
// phpcs:enable PSR1.Methods.CamelCapsMethodName
- $this->data[$handle] = $args;
+ isset($this->data[$handle]) or $this->data[$handle] = [];
+ $this->data[$handle][$key] = $value;
+
+ [$dep] = $this->dependencies[$handle] ?? [null];
+ /** @psalm-suppress MixedArgument */
+ $dep?->add_data($key, $value);
+ }
+
+ /**
+ * @param string $handle
+ * @param string $type
+ * @return \_WP_Dependency|bool
+ */
+ public function query(string $handle, string $type = 'registered'): \_WP_Dependency|bool
+ {
+ [$dep, $types] = $this->dependencies[$handle] ?? [null, []];
+ if (($dep !== null) && ($type === 'registered')) {
+ return $dep;
+ }
+
+ return ($dep !== null) && in_array($type, $types, true);
+ }
+
+ /**
+ * @param \_WP_Dependency $dependency
+ * @param 'enqueued'|'registered'|'to_do'|'done' $status
+ * @return static
+ */
+ public function addWpDependencyStub(\_WP_Dependency $dependency, string $status): static
+ {
+ $types = match ($status) {
+ 'registered' => ['registered'],
+ 'enqueued' => ['registered', 'enqueued'],
+ 'to_do' => ['registered', 'enqueued', 'to_do'],
+ 'done' => ['registered', 'enqueued', 'done'],
+ };
+ $this->dependencies[$dependency->handle] = [$dependency, $types];
+
+ return $this;
}
}
diff --git a/tests/unit/AssetsTest.php b/tests/unit/AssetsTest.php
index a646ecb..08022d4 100644
--- a/tests/unit/AssetsTest.php
+++ b/tests/unit/AssetsTest.php
@@ -18,7 +18,7 @@
use Brain\Assets\Enqueue\Enqueue;
use Brain\Assets\Tests\AssetsTestCase;
use Brain\Assets\Tests\TestCase;
-use Brain\Monkey\Functions;
+use Brain\Monkey;
/**
* @ runTestsInSeparateProcesses
@@ -150,7 +150,9 @@ public function testPluginForManifest(): void
*/
public function testPluginEnqueueCss(): void
{
- Functions\expect('wp_enqueue_style')
+ $this->mockWpDependencies('style');
+
+ Monkey\Functions\expect('wp_enqueue_style')
->once()
->with('foo-admin', \Mockery::type('string'), ['jquery'], null, 'all')
->andReturnUsing(
@@ -175,7 +177,9 @@ function (string $handle, string $url): void {
*/
public function testPluginEnqueueCssNoVerNoMin(): void
{
- Functions\expect('wp_enqueue_style')
+ $this->mockWpDependencies('style');
+
+ Monkey\Functions\expect('wp_enqueue_style')
->once()
->with('foo-admin', \Mockery::type('string'), ['jquery'], null, 'all')
->andReturnUsing(
@@ -200,7 +204,9 @@ function (string $handle, string $url): void {
*/
public function testPluginEnqueueCssMinNotFound(): void
{
- Functions\expect('wp_enqueue_style')
+ $this->mockWpDependencies('style');
+
+ Monkey\Functions\expect('wp_enqueue_style')
->once()
->with('foo-no-min', \Mockery::type('string'), [], null, 'screen')
->andReturnUsing(
@@ -358,7 +364,9 @@ public function testThemeJsUrlWithDebug(): void
*/
public function testThemeEnqueueScript(): void
{
- Functions\expect('wp_enqueue_script')
+ $this->mockWpDependencies('script');
+
+ Monkey\Functions\expect('wp_enqueue_script')
->once()
->with('parent-theme', \Mockery::type('string'), ['jquery'], null, true)
->andReturnUsing(
@@ -371,7 +379,7 @@ function (string $handle, string $url): void {
}
);
- Functions\expect('wp_localize_script')
+ Monkey\Functions\expect('wp_localize_script')
->once()
->with('parent-theme', 'ThemeData', ['foo' => 'bar']);
@@ -486,7 +494,7 @@ public function testChildThemeFromParentAbsoluteUrl(): void
}
/**
- * @return void
+ * @test
*/
public function testChildThemeFromExternalAbsoluteUrl(): void
{
@@ -499,7 +507,7 @@ public function testChildThemeFromExternalAbsoluteUrl(): void
}
/**
- * @return void
+ * @test
*/
public function testChildThemeFromExternalAbsoluteUrlWithoutForceSecure(): void
{
@@ -533,7 +541,9 @@ public function testLibraryAutomaticManifest(): void
*/
public function testLibraryEnqueue(): void
{
- Functions\expect('wp_enqueue_style')
+ $this->mockWpDependencies('style');
+
+ Monkey\Functions\expect('wp_enqueue_style')
->once()
->with('foo', "{$this->baseUrl}/foo.abcde.css", [], null, 'screen');
@@ -543,12 +553,73 @@ public function testLibraryEnqueue(): void
->enqueueStyle('foo', [], 'screen');
}
+ /**
+ * @test
+ */
+ public function testLibraryEnqueueHappenOnce(): void
+ {
+ $this->mockWpDependencies('style');
+ Monkey\Functions\expect('wp_register_style')->once();
+ Monkey\Functions\expect('wp_enqueue_style')->once();
+
+ $assets = $this->factoryLibraryAssets();
+
+ $assets->registerStyle('foo');
+ $assets->registerStyle('foo.css');
+ $assets->enqueueStyle('foo');
+ $assets->enqueueStyle('foo.css');
+ $assets->registerStyle('foo');
+ $assets->enqueueStyle('foo');
+ }
+
+ /**
+ * @test
+ */
+ public function testLibraryEnqueueRegisteredFromWp(): void
+ {
+ $this->mockWpDependencies('style', ['registered' => [['lib-foo']]]);
+ $this->mockWpDependencies('script', ['registered' => [['lib-some-script']]]);
+
+ Monkey\Functions\expect('wp_dequeue_style')->once()->with('lib-foo');
+ Monkey\Functions\expect('wp_deregister_style')->once()->with('lib-foo');
+ Monkey\Functions\expect('wp_dequeue_script')->once()->with('lib-some-script');
+ Monkey\Functions\expect('wp_deregister_script')->once()->with('lib-some-script');
+ Monkey\Functions\expect('wp_enqueue_style')->once();
+ Monkey\Functions\expect('wp_enqueue_script')->once();
+
+ $assets = $this->factoryLibraryAssets();
+
+ $assets->enqueueStyle('foo');
+ $assets->enqueueScript('some-script');
+
+ static::assertSame('lib-foo', $assets->collection()->first(Assets::CSS)?->handle());
+ static::assertSame('lib-some-script', $assets->collection()->first(Assets::JS)?->handle());
+ }
+
+ /**
+ * @test
+ */
+ public function testLibraryEnqueueEnqueuedFromWp(): void
+ {
+ $this->mockWpDependencies('style', ['enqueued' => [['lib-foo']]]);
+
+ Monkey\Functions\expect('wp_dequeue_style')->once()->with('lib-foo');
+ Monkey\Functions\expect('wp_deregister_style')->once()->with('lib-foo');
+ Monkey\Functions\expect('wp_enqueue_style')->once();
+
+ $assets = $this->factoryLibraryAssets();
+
+ $assets->enqueueStyle('foo');
+ }
+
/**
* @test
*/
public function testLibraryEnqueueFromDepInfo(): void
{
- Functions\expect('wp_enqueue_script')
+ $this->mockWpDependencies('script');
+
+ Monkey\Functions\expect('wp_enqueue_script')
->once()
->with(
'lib-some-script',
@@ -568,7 +639,9 @@ public function testLibraryEnqueueFromDepInfo(): void
*/
public function testLibraryEnqueueFromDepInfoWithStrategy(): void
{
- Functions\expect('wp_enqueue_script')
+ $this->mockWpDependencies('script');
+
+ Monkey\Functions\expect('wp_enqueue_script')
->once()
->with(
'lib-some-script',
@@ -588,9 +661,11 @@ public function testLibraryEnqueueFromDepInfoWithStrategy(): void
*/
public function testRegisterAndEnqueueManifestPlusDependencyDataExtraction(): void
{
+ $this->mockWpDependencies('script');
+
$assets = $this->factoryManifestsAssets()->useDependencyExtractionData();
- Functions\expect('wp_register_script')
+ Monkey\Functions\expect('wp_register_script')
->once()
->with(
'hello-world-block-a',
@@ -600,7 +675,7 @@ public function testRegisterAndEnqueueManifestPlusDependencyDataExtraction(): vo
['in_footer' => true]
);
- Functions\expect('wp_enqueue_script')
+ Monkey\Functions\expect('wp_enqueue_script')
->once()
->with('hello-world-block-a');
@@ -629,6 +704,9 @@ public function testRegisterAllFromManifestPlusDependencyDataExtraction(): void
'hello-world-front-style',
];
+ $this->mockWpDependencies('style');
+ $this->mockWpDependencies('script');
+
$aString = \Mockery::type('string');
/** @psalm-suppress InvalidArgument */
$aHandle = \Mockery::anyOf(...$handles);
@@ -646,7 +724,7 @@ public function testRegisterAllFromManifestPlusDependencyDataExtraction(): void
static::assertSame($query, 'v=a29c9d677e174811e603');
};
- Functions\expect('wp_register_script')
+ Monkey\Functions\expect('wp_register_script')
->twice()
->with($aHandle, $aString, [], null, ['in_footer' => true])
->andReturnUsing($dataCheck)
@@ -655,13 +733,13 @@ public function testRegisterAllFromManifestPlusDependencyDataExtraction(): void
->with($aHandle, $aString, $deps, null, ['in_footer' => true])
->andReturnUsing($dataCheck);
- Functions\expect('wp_register_style')
+ Monkey\Functions\expect('wp_register_style')
->times(4)
->with($aHandle, $aString, [], null, 'all')
->andReturnUsing($dataCheck);
- Functions\expect('wp_enqueue_script')->times(4)->with($aHandle);
- Functions\expect('wp_enqueue_style')->times(4)->with($aHandle);
+ Monkey\Functions\expect('wp_enqueue_script')->times(4)->with($aHandle);
+ Monkey\Functions\expect('wp_enqueue_style')->times(4)->with($aHandle);
$collection = $this
->factoryManifestsAssets()
@@ -695,6 +773,108 @@ public function testRegisterAllFromManifestPlusDependencyDataExtraction(): void
$collection->enqueue();
}
+ /**
+ * @test
+ */
+ public function testAssetsCollection(): void
+ {
+ $this->mockWpDependencies('style');
+ $this->mockWpDependencies('script');
+
+ $assets = $this->factoryLibraryAssets();
+
+ Monkey\Functions\expect('wp_register_style')->once();
+ Monkey\Functions\expect('wp_enqueue_style')->once();
+ Monkey\Functions\expect('wp_dequeue_style')->once();
+ Monkey\Functions\expect('wp_deregister_style')->once();
+
+ $onDeregister = null;
+ Monkey\Actions\expectAdded('brain.assets.deregistered')
+ ->once()
+ ->whenHappen(static function (callable $callback) use (&$onDeregister): void {
+ $onDeregister = $callback;
+ });
+ Monkey\Actions\expectDone('brain.assets.deregistered')
+ ->once()
+ ->with(\Mockery::type(Enqueue::class))
+ ->whenHappen(static function (Enqueue $enqueue) use (&$onDeregister): void {
+ /** @var callable $onDeregister */
+ $onDeregister($enqueue);
+ });
+
+ $enqueue = $assets->registerStyle('foo');
+ $retrieved = $assets->collection()->cssOnly()->first();
+
+ static::assertSame($enqueue, $retrieved);
+ static::assertFalse($retrieved->isEnqueued());
+
+ $enqueue->enqueue();
+
+ static::assertTrue($retrieved->isEnqueued());
+ static::assertTrue($assets->collection()->cssOnly()->first()->isEnqueued());
+
+ $enqueue->deregister();
+
+ static::assertFalse($retrieved->isEnqueued());
+ static::assertCount(0, $assets->collection());
+ }
+
+ /**
+ * @test
+ */
+ public function testAssetsCollectionsFromManifest(): void
+ {
+ $this->mockWpDependencies('style');
+ $this->mockWpDependencies('script');
+ Monkey\Functions\expect('wp_register_style')->times(4);
+ Monkey\Functions\expect('wp_register_script')->times(4);
+
+ $assets = $this->factoryManifestsAssets();
+ $fromManifest = $assets->useDependencyExtractionData()->registerAllFromManifest();
+ $collection = $assets->collection();
+
+ static::assertCount(8, $fromManifest);
+ static::assertCount(8, $collection);
+ static::assertNotSame($fromManifest, $collection);
+ static::assertSame([], $fromManifest->diff($collection)->handles());
+ static::assertSame([], $collection->diff($fromManifest)->handles());
+ static::assertSame($fromManifest->handles(), $collection->merge($fromManifest)->handles());
+ }
+
+ /**
+ * @test
+ */
+ public function testAssetsCollectionsBulkRegistration(): void
+ {
+ $this->mockWpDependencies('style');
+ $this->mockWpDependencies('script');
+ Monkey\Functions\expect('wp_register_script')->times(4);
+ Monkey\Functions\expect('wp_register_style')->times(4);
+ $handles = [
+ 'hello-world-admin',
+ 'hello-world-admin-style',
+ 'hello-world-block-a',
+ 'hello-world-block-a-style',
+ 'hello-world-block-b',
+ 'hello-world-block-b-style',
+ 'hello-world-front',
+ 'hello-world-front-style',
+ ];
+
+ $assets = $this->factoryManifestsAssets()->useDependencyExtractionData();
+
+ foreach (glob($assets->context()->basePath() . '*.{css,js}', GLOB_BRACE) as $file) {
+ str_ends_with($file, '.css')
+ ? $assets->registerStyle(basename($file, '.css'))
+ : $assets->registerScript(basename($file, '.js'));
+ }
+
+ $collection = $assets->collection();
+
+ static::assertCount(8, $collection);
+ static::assertSame([], array_diff($collection->handles(), $handles));
+ }
+
/**
* @return Assets
*/
@@ -702,11 +882,11 @@ private function factoryPluginAssets(): Assets
{
$pluginFilePath = static::fixturesPath('/plugins/foo/plugin.php');
- Functions\expect('plugin_basename')
+ Monkey\Functions\expect('plugin_basename')
->with($pluginFilePath)
->andReturn('foo/plugin.php');
- Functions\expect('plugins_url')
+ Monkey\Functions\expect('plugins_url')
->with('/assets/', $pluginFilePath)
->andReturn('http://example.com/wp-content/plugins/foo/assets');
@@ -721,9 +901,12 @@ private function factoryThemeAssets(): Assets
$themePath = static::fixturesPath('/themes/parent');
$themeDir = '/wp-content/themes/parent';
- Functions\when('get_template')->justReturn('parent');
- Functions\when('get_template_directory')->justReturn($themePath);
- Functions\when('get_template_directory_uri')->justReturn('http://example.com' . $themeDir);
+ Monkey\Functions\when('get_template')
+ ->justReturn('parent');
+ Monkey\Functions\when('get_template_directory')
+ ->justReturn($themePath);
+ Monkey\Functions\when('get_template_directory_uri')
+ ->justReturn('http://example.com' . $themeDir);
return Assets::forTheme('/assets')->tryMinUrls();
}
@@ -733,20 +916,21 @@ private function factoryThemeAssets(): Assets
*/
private function factoryChildThemeAssets(): Assets
{
- $themeFolder = '/wp-content/themes/parent';
- $childThemeFolder = '/wp-content/themes/child';
-
$baseUrl = 'https://example.com';
$basePath = static::fixturesPath();
- Functions\when('get_template')->justReturn('parent');
- Functions\when('get_stylesheet')->justReturn('child');
+ $themeFolder = '/wp-content/themes/parent';
+ $childThemeFolder = '/wp-content/themes/child';
+ $childThemeUrl = $baseUrl . $childThemeFolder;
+
+ Monkey\Functions\when('get_template')->justReturn('parent');
+ Monkey\Functions\when('get_stylesheet')->justReturn('child');
- Functions\when('get_stylesheet_directory')->justReturn($basePath . '/themes/child');
- Functions\when('get_stylesheet_directory_uri')->justReturn($baseUrl . $childThemeFolder);
+ Monkey\Functions\when('get_stylesheet_directory')->justReturn($basePath . '/themes/child');
+ Monkey\Functions\when('get_stylesheet_directory_uri')->justReturn($childThemeUrl);
- Functions\when('get_template_directory')->justReturn($basePath . '/themes/parent');
- Functions\when('get_template_directory_uri')->justReturn($baseUrl . $themeFolder);
+ Monkey\Functions\when('get_template_directory')->justReturn($basePath . '/themes/parent');
+ Monkey\Functions\when('get_template_directory_uri')->justReturn($baseUrl . $themeFolder);
return Assets::forChildTheme('/', '/assets');
}
diff --git a/tests/unit/Enqueue/CollectionTest.php b/tests/unit/Enqueue/CollectionTest.php
index 080a629..c947161 100644
--- a/tests/unit/Enqueue/CollectionTest.php
+++ b/tests/unit/Enqueue/CollectionTest.php
@@ -228,6 +228,8 @@ public function testByHandle(): void
*/
private function factoryCollection(): Collection
{
+ $this->mockWpDependencies('style');
+ $this->mockWpDependencies('script');
Monkey\Functions\expect('wp_register_style')->times(4);
Monkey\Functions\expect('wp_register_script')->times(4);
diff --git a/tests/unit/Enqueue/CssEnqueueTest.php b/tests/unit/Enqueue/CssEnqueueTest.php
index 7f49610..2e9130d 100644
--- a/tests/unit/Enqueue/CssEnqueueTest.php
+++ b/tests/unit/Enqueue/CssEnqueueTest.php
@@ -17,8 +17,7 @@
use Brain\Assets\Enqueue\CssEnqueue;
use Brain\Assets\Tests\TestCase;
use Brain\Assets\Tests\WpAssetsStub;
-use Brain\Monkey\Functions;
-use Brain\Monkey\Filters;
+use Brain\Monkey;
class CssEnqueueTest extends TestCase
{
@@ -30,13 +29,7 @@ class CssEnqueueTest extends TestCase
protected function setUp(): void
{
parent::setUp();
-
- Functions\when('wp_styles')->alias(
- function (): WpAssetsStub {
- $this->wpStyles or $this->wpStyles = new WpAssetsStub();
- return $this->wpStyles;
- }
- );
+ $this->wpStyles = $this->mockWpDependencies('style');
}
/**
@@ -55,7 +48,7 @@ public function testConditional(): void
{
CssEnqueue::new('h1')->withCondition('lt IE 9');
- static::assertSame(['conditional', 'lt IE 9'], $this->wpStyles?->data['h1']);
+ static::assertSame('lt IE 9', $this->wpStyles?->data['h1']['conditional'] ?? null);
}
/**
@@ -65,7 +58,7 @@ public function testAlternate(): void
{
CssEnqueue::new('h2')->asAlternate();
- static::assertSame(['alt', true], $this->wpStyles?->data['h2']);
+ static::assertSame(true, $this->wpStyles?->data['h2']['alt'] ?? null);
}
/**
@@ -75,7 +68,7 @@ public function testTitle(): void
{
CssEnqueue::new('h3')->withTitle('Hello');
- static::assertSame(['title', 'Hello'], $this->wpStyles?->data['h3']);
+ static::assertSame('Hello', $this->wpStyles?->data['h3']['title'] ?? null);
}
/**
@@ -85,7 +78,7 @@ public function testInline(): void
{
$inline = 'p { display:none }';
- Functions\expect('wp_add_inline_style')->once()->with('h4', $inline);
+ Monkey\Functions\expect('wp_add_inline_style')->once()->with('h4', $inline);
CssEnqueue::new('h4')->appendInline($inline);
}
@@ -100,7 +93,7 @@ public function testFilters(): void
/** @var callable|null $filterCallback */
$filterCallback = null;
- Filters\expectAdded('style_loader_tag')
+ Monkey\Filters\expectAdded('style_loader_tag')
->once()
->whenHappen(
static function (callable $callback) use (&$filterCallback): void {
@@ -108,7 +101,7 @@ static function (callable $callback) use (&$filterCallback): void {
}
);
- Filters\expectApplied('style_loader_tag')
+ Monkey\Filters\expectApplied('style_loader_tag')
->once()
->with($tag, $handle)
->andReturnUsing(
diff --git a/tests/unit/Enqueue/JsEnqueueTest.php b/tests/unit/Enqueue/JsEnqueueTest.php
index 375f1d3..d45e92c 100644
--- a/tests/unit/Enqueue/JsEnqueueTest.php
+++ b/tests/unit/Enqueue/JsEnqueueTest.php
@@ -29,13 +29,7 @@ class JsEnqueueTest extends TestCase
protected function setUp(): void
{
parent::setUp();
-
- Monkey\Functions\when('wp_scripts')->alias(
- function (): WpAssetsStub {
- $this->wpScripts or $this->wpScripts = new WpAssetsStub();
- return $this->wpScripts;
- }
- );
+ $this->wpScripts = $this->mockWpDependencies('script');
}
/**
@@ -54,7 +48,7 @@ public function testConditional(): void
{
JsEnqueue::new('h1')->withCondition('lt IE 9');
- static::assertSame(['conditional', 'lt IE 9'], $this->wpScripts?->data['h1']);
+ static::assertSame('lt IE 9', $this->wpScripts()->data['h1']['conditional'] ?? null);
}
/**
@@ -161,7 +155,7 @@ static function (string $tag, string $handle) use (&$tagCb): mixed {
. '">'
. $after;
- static::assertSame(['strategy', 'async'], $this->wpScripts?->data[$handle]);
+ static::assertSame('async', $this->wpScripts()->data[$handle]['strategy'] ?? null);
static::assertSame($expected, $filtered);
}
@@ -171,25 +165,35 @@ static function (string $tag, string $handle) use (&$tagCb): mixed {
public function testAsyncDefer(): void
{
$enqueue = JsEnqueue::new('handle')->useDefer();
- static::assertSame(['strategy', 'defer'], $this->wpScripts?->data['handle']);
+ static::assertSame('defer', $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useAsync();
- static::assertSame(['strategy', 'async'], $this->wpScripts?->data['handle']);
+ static::assertSame('async', $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useAttribute('defer', null);
- static::assertSame(['strategy', 'defer'], $this->wpScripts?->data['handle']);
+ static::assertSame('defer', $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useAttribute('async', 'true');
- static::assertSame(['strategy', 'async'], $this->wpScripts?->data['handle']);
+ static::assertSame('async', $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useAttribute('async', 'false');
- static::assertSame(['strategy', false], $this->wpScripts?->data['handle']);
+ static::assertSame(false, $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useDefer();
$enqueue->useDefer();
- static::assertSame(['strategy', 'defer'], $this->wpScripts?->data['handle']);
+ static::assertSame('defer', $this->wpScripts()->data['handle']['strategy'] ?? null);
$enqueue->useAsync();
- static::assertSame(['strategy', 'async'], $this->wpScripts?->data['handle']);
+ static::assertSame('async', $this->wpScripts()->data['handle']['strategy'] ?? null);
+ }
+
+ /**
+ * @return WpAssetsStub
+ */
+ private function wpScripts(): WpAssetsStub
+ {
+ assert($this->wpScripts instanceof WpAssetsStub);
+
+ return $this->wpScripts;
}
}