diff --git a/.github/workflows/split_monorepo.yml b/.github/workflows/split_monorepo.yml
index a3a820ab..9092420c 100644
--- a/.github/workflows/split_monorepo.yml
+++ b/.github/workflows/split_monorepo.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# required for matrix of packages set
- uses: shivammathur/setup-php@v2
@@ -42,7 +42,7 @@ jobs:
package: ${{fromJson(needs.provide_packages_json.outputs.matrix)}}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
# no tag
-
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
index b20f3b6f..0cdea233 100644
--- a/.github/workflows/update-changelog.yml
+++ b/.github/workflows/update-changelog.yml
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
@@ -21,7 +21,7 @@ jobs:
release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG
- uses: stefanzweifel/git-auto-commit-action@v4
+ uses: stefanzweifel/git-auto-commit-action@v5
with:
branch: main
commit_message: Update CHANGELOG
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19409894..e61314be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## v0.12.9 - 2023-11-27
+
+### Changed
+
+- Removed PHPUnit 10 support to prevent a breaking change. Moved to 1.x.
+
+## v0.12.8 - 2023-11-21
+
+### Changed
+
+- Added PHPUnit 10 support.
+
+## v0.12.8 - 2023-11-14
+
+### Added
+
+- Adding block assertions to strings.
+- Allow partial matching of HTML content by xpath selectors.
+- Add a shutdown handler to the installation script to prevent silent fatals.
+
+### Fixed
+
+- Ensure factories can be used with data providers.
+
+## v0.12.7 - 2023-10-02
+
+### Added
+
+- Adding date query builder for posts.
+- Adds a trait to easily silence remote requests during testing.
+
+### Changed
+
+- Improve the messaging of assertions when testing.
+
+### Fixed
+
+- Ensure that attribute and action methods are deduplicated in service providers.
+
## v0.12.6 - 2023-09-06
### Fixed
@@ -92,7 +131,7 @@ No changes, just a re-release to fix a bad tag.
## v0.11.2 - 2023-07-21
-- Add back-support for Wordpress 6.0 when testing.
+- Add back-support for WordPress 6.0 when testing.
## v0.11.1 - 2023-05-31
@@ -207,7 +246,7 @@ No changes, just a re-release to fix a bad tag.
- Cast the item to an array inside of only_children.
- Adding keywords to trigger --dev.
- Separate requires based on what they include.
-- Compatibility layer for Refresh_Database and Installs_Wordpress.
+- Compatibility layer for Refresh_Database and Installs_WordPress.
## v0.6.1 - 2022-09-20
diff --git a/composer.json b/composer.json
index dc659807..8ea00062 100644
--- a/composer.json
+++ b/composer.json
@@ -47,13 +47,14 @@
},
"require-dev": {
"alleyinteractive/alley-coding-standards": "^1.0.1",
+ "alleyinteractive/wp-match-blocks": "^1.0 || ^2.0 || ^3.0",
"guzzlehttp/guzzle": "^7.7",
"league/flysystem-aws-s3-v3": "^3.15",
"mockery/mockery": "^1.6.6",
"php-stubs/wp-cli-stubs": "^2.8",
"phpstan/phpdoc-parser": "^1.23.1",
- "phpstan/phpstan": "1.10.32",
- "phpunit/phpunit": "^9.6.10",
+ "phpstan/phpstan": "1.10.43",
+ "phpunit/phpunit": "^9.3.3",
"predis/predis": "^2.2.0",
"squizlabs/php_codesniffer": "^3.7",
"symplify/monorepo-builder": "^10.3.3",
diff --git a/phpcs.xml b/phpcs.xml
index eb25c772..42444f7c 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -28,6 +28,7 @@
+
diff --git a/src/mantle/application/autoload.php b/src/mantle/application/autoload.php
index 34420cc2..e7d49d7d 100644
--- a/src/mantle/application/autoload.php
+++ b/src/mantle/application/autoload.php
@@ -82,3 +82,21 @@ function storage_path( string $path = '' ): string {
return app()->get_storage_path( $path );
}
}
+
+if ( ! function_exists( 'now' ) ) {
+ /**
+ * Create a new Carbon instance for the current time.
+ *
+ * @todo Allow this to be faked and mocked during testing.
+ *
+ * @param DateTimeZone|string|null $tz Timezone.
+ * @return Carbon\Carbon
+ */
+ function now( \DateTimeZone|string|null $tz = null ): Carbon\Carbon {
+ if ( ! $tz ) {
+ $tz = function_exists( 'wp_timezone' ) ? wp_timezone() : new DateTimeZone( 'UTC' );
+ }
+
+ return Carbon\Carbon::now( $tz );
+ }
+}
diff --git a/src/mantle/application/composer.json b/src/mantle/application/composer.json
index 96348f72..af2b9b94 100644
--- a/src/mantle/application/composer.json
+++ b/src/mantle/application/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/application",
"description": "The Mantle Framework Application Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/assets/composer.json b/src/mantle/assets/composer.json
index 33631fb7..23467eb3 100644
--- a/src/mantle/assets/composer.json
+++ b/src/mantle/assets/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/assets",
"description": "The Mantle Framework Asset Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/auth/composer.json b/src/mantle/auth/composer.json
index 1acfa867..eb7291e5 100644
--- a/src/mantle/auth/composer.json
+++ b/src/mantle/auth/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/auth",
"description": "The Mantle Framework Auth Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/blocks/composer.json b/src/mantle/blocks/composer.json
index 8b1703a8..f7adf7f5 100644
--- a/src/mantle/blocks/composer.json
+++ b/src/mantle/blocks/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/blocks",
"description": "The Mantle Framework Blocks Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/cache/composer.json b/src/mantle/cache/composer.json
index 32ead84b..0f19d55f 100644
--- a/src/mantle/cache/composer.json
+++ b/src/mantle/cache/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/cache",
"description": "The Mantle Framework Cache Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/config/composer.json b/src/mantle/config/composer.json
index da9d3d14..769be096 100644
--- a/src/mantle/config/composer.json
+++ b/src/mantle/config/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/config",
"description": "The Mantle Framework Config Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/console/composer.json b/src/mantle/console/composer.json
index f7bc086f..01dfce03 100644
--- a/src/mantle/console/composer.json
+++ b/src/mantle/console/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/console",
"description": "The Mantle Framework Console Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/container/class-bound-method.php b/src/mantle/container/class-bound-method.php
index 885cc37a..f6d2006d 100644
--- a/src/mantle/container/class-bound-method.php
+++ b/src/mantle/container/class-bound-method.php
@@ -155,7 +155,7 @@ protected static function get_call_reflector( $callback ) {
* Get the dependency for the given call parameter.
*
* @param Container $container Container instance.
- * @param \ReflectionParameter $parameter Reflect Paramater.
+ * @param \ReflectionParameter $parameter Reflect Parameter.
* @param array $parameters Parameters to pass.
* @param array $dependencies Class dependencies.
* @return void
diff --git a/src/mantle/container/composer.json b/src/mantle/container/composer.json
index 66f631d4..c3aa2104 100644
--- a/src/mantle/container/composer.json
+++ b/src/mantle/container/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/container",
"description": "The Mantle Framework Container Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/contracts/composer.json b/src/mantle/contracts/composer.json
index 6e0dd1d0..2d24d9b4 100644
--- a/src/mantle/contracts/composer.json
+++ b/src/mantle/contracts/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/contracts",
"description": "The Mantle Framework Contracts Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/contracts/interface-application.php b/src/mantle/contracts/interface-application.php
index 895f4e75..c0772d12 100644
--- a/src/mantle/contracts/interface-application.php
+++ b/src/mantle/contracts/interface-application.php
@@ -191,7 +191,7 @@ public function booted( callable $callback ): static;
public function terminating( callable $callback ): static;
/**
- * Termine the application.
+ * Terminate the application.
*
* @return void
*/
diff --git a/src/mantle/database/class-factory-service-provider.php b/src/mantle/database/class-factory-service-provider.php
index 23d948af..7ec98f2f 100644
--- a/src/mantle/database/class-factory-service-provider.php
+++ b/src/mantle/database/class-factory-service-provider.php
@@ -46,7 +46,7 @@ function ( $app, $parameters ) {
$locale = config( 'app.faker_locale', Factory::DEFAULT_LOCALE );
if ( ! isset( static::$fakers[ $locale ] ) ) {
- static::$fakers[ $locale ] = Factory::create();
+ static::$fakers[ $locale ] = Factory::create( $locale );
static::$fakers[ $locale ]->addProvider(
new Faker_Provider( static::$fakers[ $locale ] )
diff --git a/src/mantle/database/composer.json b/src/mantle/database/composer.json
index 218f8076..f5ee6edc 100644
--- a/src/mantle/database/composer.json
+++ b/src/mantle/database/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/database",
"description": "The Mantle Framework Database Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/database/factory/class-attachment-factory.php b/src/mantle/database/factory/class-attachment-factory.php
index 4363966b..d5018746 100644
--- a/src/mantle/database/factory/class-attachment-factory.php
+++ b/src/mantle/database/factory/class-attachment-factory.php
@@ -8,7 +8,6 @@
namespace Mantle\Database\Factory;
use Closure;
-use Faker\Generator;
use Mantle\Contracts\Database\Core_Object;
use Mantle\Database\Model\Attachment;
use RuntimeException;
@@ -19,7 +18,11 @@
/**
* Attachment Factory
*
- * @template TObject of \Mantle\Database\Model\Attachment
+ * @template TModel of \Mantle\Database\Model\Attachment
+ * @template TObject of \WP_Post
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Attachment_Factory extends Post_Factory {
use Concerns\Generates_Images;
@@ -27,7 +30,7 @@ class Attachment_Factory extends Post_Factory {
/**
* Model to use when creating objects.
*
- * @var class-string
+ * @var class-string
*/
protected string $model = Attachment::class;
diff --git a/src/mantle/database/factory/class-blog-factory.php b/src/mantle/database/factory/class-blog-factory.php
index f2a76ff7..29ed305c 100644
--- a/src/mantle/database/factory/class-blog-factory.php
+++ b/src/mantle/database/factory/class-blog-factory.php
@@ -14,13 +14,17 @@
/**
* Blog Factory
*
- * @template TObject of \Mantle\Database\Model\Site
+ * @template TModel of \Mantle\Database\Model\Site
+ * @template TObject of \WP_Site
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Blog_Factory extends Factory {
/**
* Model to use when creating objects.
*
- * @var class-string
+ * @var class-string
*/
protected string $model = Site::class;
diff --git a/src/mantle/database/factory/class-comment-factory.php b/src/mantle/database/factory/class-comment-factory.php
index 3f8abb31..b71189db 100644
--- a/src/mantle/database/factory/class-comment-factory.php
+++ b/src/mantle/database/factory/class-comment-factory.php
@@ -14,7 +14,11 @@
/**
* Term Factory
*
- * @template TObject of \Mantle\Database\Model\Comment
+ * @template TModel of \Mantle\Database\Model\Comment
+ * @template TObject of \WP_Comment
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Comment_Factory extends Factory {
/**
diff --git a/src/mantle/database/factory/class-factory-container.php b/src/mantle/database/factory/class-factory-container.php
index 1e324cef..ff2e8923 100644
--- a/src/mantle/database/factory/class-factory-container.php
+++ b/src/mantle/database/factory/class-factory-container.php
@@ -7,7 +7,9 @@
namespace Mantle\Database\Factory;
+use Faker\Generator;
use Mantle\Contracts\Container;
+use Mantle\Faker\Faker_Provider;
/**
* Collect all the Database Factories for IDE Support
@@ -21,72 +23,72 @@ class Factory_Container {
/**
* Attachment Factory
*
- * @var Attachment_Factory<\WP_Post|\Mantle\Database\Model\Attachment>
+ * @var Attachment_Factory<\Mantle\Database\Model\Attachment, \WP_Post, \WP_Post>
*/
- public $attachment;
+ public Attachment_Factory $attachment;
/**
* Blog Factory
*
- * @var Blog_Factory<\WP_Site|\Mantle\Database\Model\Site>
+ * @var Blog_Factory<\Mantle\Database\Model\Site, \WP_Site, \WP_Site>
*/
- public $blog;
+ public Blog_Factory $blog;
/**
* Category Factory
*
- * @var Term_Factory<\WP_Term|\Mantle\Database\Model\Term>
+ * @var Term_Factory<\Mantle\Database\Model\Term, \WP_Term, \WP_Term>
*/
- public $category;
+ public Term_Factory $category;
/**
* Comment Factory
*
- * @var Comment_Factory<\WP_Comment>
+ * @var Comment_Factory<\Mantle\Database\Model\Comment, \WP_Comment, \WP_Comment>
*/
- public $comment;
+ public Comment_Factory $comment;
/**
* Network Factory
*
- * @var Network_Factory<\WP_Network>
+ * @var Network_Factory
*/
- public $network;
+ public Network_Factory $network;
/**
* Page Factory
*
- * @var Post_Factory<\WP_Post|\Mantle\Database\Model\Post>
+ * @var Post_Factory<\Mantle\Database\Model\Post, \WP_Post, \WP_Post>
*/
public $page;
/**
* Post Factory
*
- * @var Post_Factory<\WP_Post|\Mantle\Database\Model\Post>
+ * @var Post_Factory<\Mantle\Database\Model\Post, \WP_Post, \WP_Post>
*/
- public $post;
+ public Post_Factory $post;
/**
* Tag Factory
*
- * @var Term_Factory<\WP_Term|\Mantle\Database\Model\Term>
+ * @var Term_Factory<\Mantle\Database\Model\Term, \WP_Term, \WP_Term>
*/
- public $tag;
+ public Term_Factory $tag;
/**
* Term Factory (alias for Tag Factory).
*
- * @var Term_Factory<\WP_Term|\Mantle\Database\Model\Term>
+ * @var Term_Factory<\Mantle\Database\Model\Term, \WP_Term, \WP_Term>
*/
- public $term;
+ public Term_Factory $term;
/**
* User Factory
*
- * @var User_Factory<\WP_User|\Mantle\Database\Model\User>
+ * @var User_Factory<\Mantle\Database\Model\User, \WP_User, \WP_User>
*/
- public $user;
+ public User_Factory $user;
/**
* Constructor.
@@ -94,6 +96,8 @@ class Factory_Container {
* @param Container $container Container instance.
*/
public function __construct( Container $container ) {
+ $this->setup_faker( $container );
+
$this->attachment = $container->make( Attachment_Factory::class );
$this->category = $container->make( Term_Factory::class, [ 'taxonomy' => 'category' ] );
$this->comment = $container->make( Comment_Factory::class );
@@ -108,4 +112,27 @@ public function __construct( Container $container ) {
$this->network = $container->make( Network_Factory::class );
}
}
+
+ /**
+ * Set up the Faker instance in the container.
+ *
+ * Primarily used when faker/factory is called from a data provider and the
+ * application hasn't been setup yet.
+ *
+ * @param Container $container Container instance.
+ */
+ protected function setup_faker( Container $container ): void {
+ $container->singleton_if(
+ Generator::class,
+ function () {
+ $generator = \Faker\Factory::create();
+
+ $generator->unique();
+
+ $generator->addProvider( new Faker_Provider( $generator ) );
+
+ return $generator;
+ },
+ );
+ }
}
diff --git a/src/mantle/database/factory/class-factory.php b/src/mantle/database/factory/class-factory.php
index c7471684..767fd590 100644
--- a/src/mantle/database/factory/class-factory.php
+++ b/src/mantle/database/factory/class-factory.php
@@ -24,9 +24,11 @@
/**
* Base Factory
*
- * @template TObject of \Mantle\Database\Model\Model
+ * @template TModel of \Mantle\Database\Model\Model
+ * @template TObject
+ * @template TReturnValue
*
- * @method \Mantle\Database\Factory\Fluent_Factory count(int $count)
+ * @method \Mantle\Database\Factory\Fluent_Factory count(int $count)
*/
abstract class Factory {
use Concerns\Resolves_Factories,
@@ -74,7 +76,7 @@ abstract public function definition(): array;
* Retrieves an object by ID.
*
* @param int $object_id The object ID.
- * @return mixed
+ * @return TModel|TObject|null
*/
abstract public function get_object_by_id( int $object_id );
@@ -91,7 +93,7 @@ public function create( array $args = [] ): mixed {
/**
* Generate models from the factory.
*
- * @return static
+ * @return static
*/
public function as_models() {
return tap(
@@ -103,7 +105,7 @@ public function as_models() {
/**
* Generate core WordPress objects from the factory.
*
- * @return static
+ * @return static
*/
public function as_objects() {
return tap(
@@ -145,8 +147,10 @@ public function without_middleware() {
*
* @throws \InvalidArgumentException If the model does not extend from the base model class.
*
- * @param class-string $model The model to use.
- * @return static
+ * @template TNewModel of \Mantle\Database\Model\Model
+ *
+ * @param class-string $model The model to use.
+ * @return static
*/
public function with_model( string $model ) {
// Validate that model extends from the base model class.
@@ -210,7 +214,7 @@ public function create_many( int $count, array $args = [] ) {
* Creates an object and returns its object.
*
* @param array $args Optional. The arguments for the object to create. Default is empty array.
- * @return TObject The created object.
+ * @return TReturnValue The created object.
*/
public function create_and_get( array $args = [] ) {
return $this->get_object_by_id( $this->create( $args ) );
diff --git a/src/mantle/database/factory/class-fluent-factory.php b/src/mantle/database/factory/class-fluent-factory.php
index 653306c1..8b45d878 100644
--- a/src/mantle/database/factory/class-fluent-factory.php
+++ b/src/mantle/database/factory/class-fluent-factory.php
@@ -18,7 +18,11 @@
* Extends upon the factory that is included with Mantle (one that is designed
* to mirror WordPress) and builds upon it to provide a fluent interface.
*
- * @template TObject of \Mantle\Database\Model\Model
+ * @template TModel of \Mantle\Database\Model\Model
+ * @template TObject
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Fluent_Factory extends Factory {
/**
@@ -57,7 +61,7 @@ public function count( int $count ): static {
* Create one or multiple objects and return the IDs.
*
* @param array $args Arguments to pass to the factory.
- * @return \Mantle\Support\Collection|mixed
+ * @return \Mantle\Support\Collection|mixed
*/
public function create( array $args = [] ): mixed {
if ( 1 === $this->count ) {
@@ -71,7 +75,7 @@ public function create( array $args = [] ): mixed {
* Create one or multiple objects and return the objects.
*
* @param array $args Arguments to pass to the factory.
- * @return \Mantle\Support\Collection|TObject
+ * @return \Mantle\Support\Collection|TReturnValue
*/
public function create_and_get( array $args = [] ): mixed {
if ( 1 === $this->count ) {
@@ -98,7 +102,7 @@ public function definition(): array {
* Retrieves an object by ID.
*
* @param mixed $object_id The object ID.
- * @return TObject
+ * @return TReturnValue
*/
public function get_object_by_id( mixed $object_id ): mixed {
return $this->factory->get_object_by_id( $object_id );
diff --git a/src/mantle/database/factory/class-network-factory.php b/src/mantle/database/factory/class-network-factory.php
index 65f5b802..f708462f 100644
--- a/src/mantle/database/factory/class-network-factory.php
+++ b/src/mantle/database/factory/class-network-factory.php
@@ -7,12 +7,14 @@
namespace Mantle\Database\Factory;
-use Faker\Generator;
-
/**
* Network Factory
*
- * @template TObject
+ * @template TModel
+ * @template TObject of \WP_Network
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Network_Factory extends Factory {
/**
diff --git a/src/mantle/database/factory/class-post-factory.php b/src/mantle/database/factory/class-post-factory.php
index c69f53c9..1ad971b9 100644
--- a/src/mantle/database/factory/class-post-factory.php
+++ b/src/mantle/database/factory/class-post-factory.php
@@ -19,7 +19,11 @@
/**
* Post Factory
*
- * @template TObject of \Mantle\Database\Model\Post
+ * @template TModel of \Mantle\Database\Model\Post
+ * @template TObject
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Post_Factory extends Factory {
use Concerns\With_Meta;
@@ -27,7 +31,7 @@ class Post_Factory extends Factory {
/**
* Model to use when creating objects.
*
- * @var class-string
+ * @var class-string
*/
protected string $model = Post::class;
@@ -44,7 +48,7 @@ public function __construct( Generator $faker, public string $post_type = 'post'
/**
* Create a new factory instance to create posts with a set of terms.
*
- * @param array|\WP_Term|int|string ...$terms Terms to assign to the post.
+ * @param array>|\WP_Term|int|string ...$terms Terms to assign to the post.
* @return static
*/
public function with_terms( ...$terms ): static {
@@ -172,6 +176,7 @@ public function create_ordered_set(
*
* @param int $object_id The object ID.
* @return Post|WP_Post|null
+ * @phpstan-return TModel|TObject|null
*/
public function get_object_by_id( int $object_id ) {
return $this->as_models
diff --git a/src/mantle/database/factory/class-term-factory.php b/src/mantle/database/factory/class-term-factory.php
index 52db8f8c..4d49ecab 100644
--- a/src/mantle/database/factory/class-term-factory.php
+++ b/src/mantle/database/factory/class-term-factory.php
@@ -16,7 +16,11 @@
/**
* Term Factory
*
- * @template TObject of \Mantle\Database\Model\Term
+ * @template TModel of \Mantle\Database\Model\Term
+ * @template TObject of \WP_Term
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class Term_Factory extends Factory {
use Concerns\With_Meta;
@@ -24,7 +28,7 @@ class Term_Factory extends Factory {
/**
* Model to use when creating objects.
*
- * @var class-string
+ * @var class-string
*/
protected string $model = Term::class;
diff --git a/src/mantle/database/factory/class-user-factory.php b/src/mantle/database/factory/class-user-factory.php
index ce41b3d4..927057ad 100644
--- a/src/mantle/database/factory/class-user-factory.php
+++ b/src/mantle/database/factory/class-user-factory.php
@@ -14,7 +14,11 @@
/**
* User Factory
*
- * @template TObject of \Mantle\Database\Model\User
+ * @template TModel of \Mantle\Database\Model\User
+ * @template TObject of \WP_User
+ * @template TReturnValue
+ *
+ * @extends Factory
*/
class User_Factory extends Factory {
use Concerns\With_Meta;
@@ -22,7 +26,7 @@ class User_Factory extends Factory {
/**
* Model to use when creating objects.
*
- * @var class-string
+ * @var class-string
*/
protected string $model = User::class;
diff --git a/src/mantle/database/model/class-attachment.php b/src/mantle/database/model/class-attachment.php
index d92ac3be..5edf4be8 100644
--- a/src/mantle/database/model/class-attachment.php
+++ b/src/mantle/database/model/class-attachment.php
@@ -7,11 +7,12 @@
namespace Mantle\Database\Model;
-use Mantle\Contracts;
use Mantle\Facade\Storage;
/**
* Attachment Model
+ *
+ * @method static \Mantle\Database\Factory\Post_Factory factory( array|callable|null $state = null )
*/
class Attachment extends Post {
/**
diff --git a/src/mantle/database/model/class-comment.php b/src/mantle/database/model/class-comment.php
index 7df3c992..60fe3c34 100644
--- a/src/mantle/database/model/class-comment.php
+++ b/src/mantle/database/model/class-comment.php
@@ -12,6 +12,8 @@
/**
* Comment Model
+ *
+ * @method static \Mantle\Database\Factory\Post_Factory factory( array|callable|null $state = null )
*/
class Comment extends Model implements Contracts\Database\Core_Object, Contracts\Database\Model_Meta, Contracts\Database\Updatable {
use Meta\Model_Meta,
diff --git a/src/mantle/database/model/class-model.php b/src/mantle/database/model/class-model.php
index b07ebccb..470b714c 100644
--- a/src/mantle/database/model/class-model.php
+++ b/src/mantle/database/model/class-model.php
@@ -25,6 +25,8 @@
/**
* Database Model
*
+ * @template TModelObject of object
+ *
* @method static \Mantle\Support\Collection all()
* @method static static first()
* @method static static first_or_fail()
@@ -41,6 +43,7 @@ abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializab
Concerns\Has_Aliases,
Concerns\Has_Attributes,
Concerns\Has_Events,
+ /** @use Concerns\Has_Factory */
Concerns\Has_Factory,
Concerns\Has_Global_Scopes,
Concerns\Has_Relationships;
diff --git a/src/mantle/database/model/class-post.php b/src/mantle/database/model/class-post.php
index 65eb4157..f6d72c87 100644
--- a/src/mantle/database/model/class-post.php
+++ b/src/mantle/database/model/class-post.php
@@ -17,6 +17,8 @@
/**
* Post Model
*
+ * @extends Model<\WP_Post>
+ *
* @property int $comment_count
* @property int $ID
* @property int $menu_order
@@ -49,6 +51,7 @@
* @property string $status Alias to post_status.
* @property string $title Alias to post_title.
*
+ * @method static \Mantle\Database\Factory\Post_Factory factory( array|callable|null $state = null )
* @method static \Mantle\Database\Query\Post_Query_Builder anyStatus()
* @method static \Mantle\Database\Query\Post_Query_Builder where( string|array $attribute, mixed $value )
* @method static \Mantle\Database\Query\Post_Query_Builder whereId( int $id )
@@ -67,6 +70,18 @@
* @method static \Mantle\Database\Query\Post_Query_Builder where_raw( array|string $column, ?string $operator = null, mixed $value = null, string $boolean = 'AND' )
* @method static \Mantle\Database\Query\Post_Query_Builder orWhereRaw( array|string $column, ?string $operator = null, mixed $value = null, string $boolean = 'AND' )
* @method static \Mantle\Database\Query\Post_Query_Builder or_where_raw( array|string $column, ?string $operator = null, mixed $value = null, string $boolean = 'AND' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder whereDate( DateTimeInterface|int|string $date, string $compare = '=', string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder whereUtcDate( DateTimeInterface|int|string $date, string $compare = '=' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder whereModifiedDate( DateTimeInterface|int|string $date, string $compare = '=' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder whereModifiedUtcDate( DateTimeInterface|int|string $date, string $compare = '=' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder olderThan( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder olderThanOrEqualTo( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder older_than( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder older_than_or_equal_to( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder newerThan( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder newerThanOrEqualTo( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder newer_than( DateTimeInterface|int $date, string $column = 'post_date' )
+ * @method static \Mantle\Database\Query\Post_Query_Builder newer_than_or_equal_to( DateTimeInterface|int $date, string $column = 'post_date' )
*/
class Post extends Model implements Contracts\Database\Core_Object, Contracts\Database\Model_Meta, Contracts\Database\Updatable {
use Events\Post_Events,
diff --git a/src/mantle/database/model/class-site.php b/src/mantle/database/model/class-site.php
index 0d328cfe..bd75aa39 100644
--- a/src/mantle/database/model/class-site.php
+++ b/src/mantle/database/model/class-site.php
@@ -12,6 +12,8 @@
/**
* Site Model
+ *
+ * @method static \Mantle\Database\Factory\Post_Factory factory( array|callable|null $state = null )
*/
class Site extends Model implements Contracts\Database\Core_Object, Contracts\Database\Updatable {
/**
diff --git a/src/mantle/database/model/class-term.php b/src/mantle/database/model/class-term.php
index 94aa3eef..19fa2771 100644
--- a/src/mantle/database/model/class-term.php
+++ b/src/mantle/database/model/class-term.php
@@ -22,6 +22,7 @@
* @property string $slug
* @property string $taxonomy
*
+ * @method static \Mantle\Database\Factory\Term_Factory factory( array|callable|null $state = null )
* @method static \Mantle\Database\Query\Term_Query_Builder whereId( int $id )
* @method static \Mantle\Database\Query\Term_Query_Builder whereName(string $name)
* @method static \Mantle\Database\Query\Term_Query_Builder whereSlug(string $slug)
diff --git a/src/mantle/database/model/class-user.php b/src/mantle/database/model/class-user.php
index aceb6be7..8a429921 100644
--- a/src/mantle/database/model/class-user.php
+++ b/src/mantle/database/model/class-user.php
@@ -12,6 +12,8 @@
/**
* User Model
+ *
+ * @method static \Mantle\Database\Factory\User_Factory factory( array|callable|null $state = null )
*/
class User extends Model implements Contracts\Database\Core_Object, Contracts\Database\Model_Meta, Contracts\Database\Updatable {
use Meta\Model_Meta,
diff --git a/src/mantle/database/model/concerns/trait-has-factory.php b/src/mantle/database/model/concerns/trait-has-factory.php
index 04147b46..1ba2a18b 100644
--- a/src/mantle/database/model/concerns/trait-has-factory.php
+++ b/src/mantle/database/model/concerns/trait-has-factory.php
@@ -12,14 +12,16 @@
use Mantle\Database\Factory\Term_Factory;
/**
- * Model Database Factory
+ * Trait to add a factory to a model.
+ *
+ * @template TObject
*/
trait Has_Factory {
/**
* Create a builder for the model.
*
* @param array|callable $state Default state array or callable that will be invoked to set state.
- * @return \Mantle\Database\Factory\Factory
+ * @return \Mantle\Database\Factory\Factory
*/
public static function factory( array|callable|null $state = null ): Factory {
$factory = static::new_factory() ?: Factory::factory_for_model( static::class );
@@ -43,7 +45,7 @@ public static function factory( array|callable|null $state = null ): Factory {
*
* Optional: allows for the model factory to be overridden by application code.
*
- * @return \Mantle\Database\Factory\Factory|null
+ * @return \Mantle\Database\Factory\Factory|null
*/
protected static function new_factory(): ?Factory {
return null;
diff --git a/src/mantle/database/query/class-builder.php b/src/mantle/database/query/class-builder.php
index 401cf85c..969ad838 100644
--- a/src/mantle/database/query/class-builder.php
+++ b/src/mantle/database/query/class-builder.php
@@ -3,7 +3,7 @@
* Builder class file.
*
* phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
- * phpcs:disable Squiz.Commenting.FunctionComment
+ * phpcs:disable Squiz.Commenting.VariableComment.Missing, Squiz.Commenting.FunctionComment
* phpcs:disable PEAR.Functions.FunctionCallSignature.CloseBracketLine, PEAR.Functions.FunctionCallSignature.MultipleArguments, PEAR.Functions.FunctionCallSignature.ContentAfterOpenBracket
*
* @package Mantle
@@ -26,8 +26,6 @@
use Mantle\Support\Str;
use Mantle\Support\Traits\Conditionable;
-use function Mantle\Support\Helpers\collect;
-
/**
* Builder Query Builder
*
@@ -41,9 +39,9 @@ abstract class Builder {
/**
* Model to build on.
*
- * @var string[]|string
+ * @var class-string|array>
*/
- protected $model;
+ protected array|string $model;
/**
* Result limit per-page.
@@ -132,9 +130,9 @@ abstract class Builder {
/**
* Storage of the found rows for a query.
*
- * @var int
+ * @var int|null
*/
- protected int $found_rows = 0;
+ protected ?int $found_rows = 0;
/**
* Relationships to eager load.
@@ -302,6 +300,11 @@ public function where( array|string $attribute, mixed $value = '' ): static {
$attribute = $this->resolve_attribute( $attribute );
+ // Pass date attributes to the date query builder if available.
+ if ( method_exists( $this, 'whereDate' ) && in_array( $attribute, [ 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' ], true ) ) {
+ return $this->whereDate( $value, '=', $attribute );
+ }
+
$this->wheres[ $attribute ] = $value;
return $this;
@@ -909,16 +912,13 @@ public function __call( $method, $args ) {
/**
* Collect all the model object names in an associative Collection.
*
- * @return Collection Collection with object names as keys and model
- * class names as values.
+ * @return Collection> Collection of model class names keyed by object name.
*/
public function get_model_object_names(): Collection {
- return collect( (array) $this->model )
+ return ( new Collection( (array) $this->model ) ) // @phpstan-ignore-line should return
->combine( $this->model )
->map(
- function ( $model ) {
- return $model::get_object_name();
- }
+ fn ( $model ) => $model::get_object_name(),
)
->flip();
}
diff --git a/src/mantle/database/query/class-collection.php b/src/mantle/database/query/class-collection.php
new file mode 100644
index 00000000..fa49b958
--- /dev/null
+++ b/src/mantle/database/query/class-collection.php
@@ -0,0 +1,190 @@
+
+ */
+class Collection extends Base_Collection {
+ /**
+ * Total number of rows found for the query.
+ *
+ * @var int|null
+ */
+ public ?int $found_rows = null;
+
+ /**
+ * Set the total number of rows found for the query.
+ *
+ * @param int|null $found_rows Total number of rows found for the query.
+ * @return static
+ */
+ public function with_found_rows( ?int $found_rows ): static {
+ $this->found_rows = $found_rows;
+
+ return $this;
+ }
+
+ /**
+ * Get the total number of rows found for the query.
+ *
+ * @return int|null
+ */
+ public function found_rows(): ?int {
+ return $this->found_rows;
+ }
+
+ /**
+ * Retrieve the models in the collection.
+ *
+ * @return \Mantle\Support\Collection>
+ */
+ public function models(): Base_Collection {
+ return $this->map( fn ( $model ) => $model::class )->values()->unique();
+ }
+
+ /**
+ * Run a map over each of the items.
+ *
+ * @template TMapValue
+ *
+ * @param callable(TModel, TKey): TMapValue $callback The callback to run.
+ * @return \Mantle\Support\Collection|static
+ */
+ public function map( callable $callback ) {
+ $result = parent::map( $callback );
+
+ if ( $result instanceof self ) {
+ $result->with_found_rows( $this->found_rows );
+ }
+
+ return $result->contains( fn ( $item ) => ! $item instanceof Model )
+ ? $result->to_base()
+ : $result;
+ }
+
+ /**
+ * Run an associative map over each of the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @template TMapWithKeysKey of array-key
+ * @template TMapWithKeysValue
+ *
+ * @param callable(TModel, TKey): array $callback The callback to run.
+ * @return \Mantle\Support\Collection|static
+ */
+ public function map_with_keys( callable $callback ) {
+ $result = parent::map_with_keys( $callback );
+
+ if ( $result instanceof self ) {
+ $result->with_found_rows( $this->found_rows );
+ }
+
+ return $result->contains( fn ( $item ) => ! $item instanceof Model )
+ ? $result->to_base()
+ : $result;
+ }
+
+ /**
+ * The following methods are intercepted to always return base collections.
+ */
+
+ /**
+ * Count the number of items in the collection by a field or using a callback.
+ *
+ * @param (callable(TModel, TKey): array-key)|string|null $count_by
+ * @return \Mantle\Support\Collection
+ */
+ public function count_by( $count_by = null ) {
+ return $this->to_base()->count_by( $count_by );
+ }
+
+ /**
+ * Collapse the collection of items into a single array.
+ *
+ * @return \Mantle\Support\Collection
+ */
+ public function collapse() {
+ return $this->to_base()->collapse();
+ }
+
+ /**
+ * Get a flattened array of the items in the collection.
+ *
+ * @param int|float $depth
+ * @return \Mantle\Support\Collection
+ */
+ public function flatten( $depth = INF ) {
+ return $this->to_base()->flatten( $depth );
+ }
+
+ /**
+ * Flip the items in the collection.
+ *
+ * @return \Mantle\Support\Collection
+ */
+ public function flip() {
+ return $this->to_base()->flip();
+ }
+
+ /**
+ * Get the keys of the collection items.
+ *
+ * @return \Mantle\Support\Collection
+ */
+ public function keys() {
+ return $this->to_base()->keys();
+ }
+
+ /**
+ * Pad collection to the specified length with a value.
+ *
+ * @template TPadValue
+ *
+ * @param int $size
+ * @param TPadValue $value
+ * @return \Mantle\Support\Collection
+ */
+ public function pad( $size, $value ) {
+ return $this->to_base()->pad( $size, $value );
+ }
+
+ /**
+ * Get an array with the values of a given key.
+ *
+ * @param string|array $value
+ * @param string|null $key
+ * @return \Mantle\Support\Collection
+ */
+ public function pluck( $value, $key = null ) {
+ return $this->to_base()->pluck( $value, $key );
+ }
+
+ /**
+ * Zip the collection together with one or more arrays.
+ *
+ * @template TZipValue
+ *
+ * @param \Mantle\Contracts\Support\Arrayable|iterable ...$items
+ * @return static>
+ */
+ public function zip( ...$items ) { // @phpstan-ignore-line return
+ return $this->to_base()->zip( ...$items );
+ }
+}
diff --git a/src/mantle/database/query/class-post-query-builder.php b/src/mantle/database/query/class-post-query-builder.php
index 08f2158f..995f4dbd 100644
--- a/src/mantle/database/query/class-post-query-builder.php
+++ b/src/mantle/database/query/class-post-query-builder.php
@@ -9,8 +9,9 @@
namespace Mantle\Database\Query;
use Mantle\Database\Model\Term;
+use Mantle\Database\Query\Concerns\Queries_Dates;
use Mantle\Support\Helpers;
-use Mantle\Support\Collection;
+use RuntimeException;
use WP_Term;
use function Mantle\Support\Helpers\collect;
@@ -30,7 +31,7 @@
* @method \Mantle\Database\Query\Post_Query_Builder whereType( string $type )
*/
class Post_Query_Builder extends Builder {
- use Queries_Relationships;
+ use Queries_Dates, Queries_Relationships;
/**
* Query Variable Aliases
@@ -38,10 +39,16 @@ class Post_Query_Builder extends Builder {
* @var array
*/
protected array $query_aliases = [
- 'id' => 'p',
- 'post_author' => 'author',
- 'post_name' => 'name',
- 'slug' => 'name',
+ 'date_gmt' => 'post_date_gmt',
+ 'date_utc' => 'post_date_gmt',
+ 'date' => 'post_date',
+ 'id' => 'p',
+ 'modified_gmt' => 'post_modified_gmt',
+ 'modified_utc' => 'post_modified_gmt',
+ 'modified' => 'post_modified',
+ 'post_author' => 'author',
+ 'post_name' => 'name',
+ 'slug' => 'name',
];
/**
@@ -110,7 +117,6 @@ public function get_query_args(): array {
return array_merge(
[
- 'fields' => 'ids',
'ignore_sticky_posts' => true,
'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'order' => $order,
@@ -121,7 +127,11 @@ public function get_query_args(): array {
'suppress_filters' => false,
'tax_query' => $this->tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
],
+ $this->get_date_query_args(),
$this->wheres,
+ [
+ 'fields' => 'ids',
+ ]
);
}
@@ -140,14 +150,21 @@ public function get(): Collection {
fn () => $query->query( $this->get_query_args() ),
);
- $this->found_rows = $query->found_posts;
- $post_ids = $query->posts;
+ if ( empty( $query->found_posts ) && count( $query->posts ) > 0 ) {
+ $this->found_rows = null;
+ } else {
+ $this->found_rows = $query->found_posts;
+ }
+
+ $post_ids = $query->posts;
if ( empty( $post_ids ) ) {
- return collect();
+ return ( new Collection() )->with_found_rows( $this->found_rows ); // @phpstan-ignore-line should return
}
- $models = $this->get_models( $post_ids );
+ $models = $this
+ ->get_models( $post_ids )
+ ->with_found_rows( $this->found_rows );
// Return the models if there are no models or if multiple model instances
// are used. Eager loading does not currently support multiple models.
@@ -187,11 +204,18 @@ public function count(): int {
protected function get_models( array $post_ids ): Collection {
if ( is_array( $this->model ) ) {
$model_object_types = $this->get_model_object_names();
- return collect( $post_ids )
+
+ return Collection::from( $post_ids )
->map(
function ( $post_id ) use ( $model_object_types ) {
$post_type = \get_post_type( $post_id );
+ if ( empty( $model_object_types[ $post_type ] ) ) {
+ throw new RuntimeException(
+ "Missing model for object type [{ $post_type }]."
+ );
+ }
+
if ( empty( $post_type ) ) {
return null;
}
@@ -199,12 +223,13 @@ function ( $post_id ) use ( $model_object_types ) {
return $model_object_types[ $post_type ]::find( $post_id );
}
)
- ->filter();
- } else {
- return collect( $post_ids )
- ->map( [ $this->model, 'find' ] )
- ->filter();
+ ->filter()
+ ->values();
}
+
+ return Collection::from( $post_ids )
+ ->map( [ $this->model, 'find' ] )
+ ->filter();
}
/**
@@ -287,6 +312,18 @@ public function orWhereTerm( ...$args ) {
return $this->whereTerm( ...$args );
}
+ /**
+ * Fetch the query with 'no_found_rows' set to a value.
+ *
+ * Setting to 'true' prevents counting all the available rows for a query.
+ *
+ * @param bool $value Whether to set 'no_found_rows' to true.
+ * @return static
+ */
+ public function withNoFoundRows( bool $value = true ): static {
+ return $this->where( 'no_found_rows', $value );
+ }
+
/**
* Dump the SQL query being executed.
*
diff --git a/src/mantle/database/query/class-term-query-builder.php b/src/mantle/database/query/class-term-query-builder.php
index c624dc9d..40eddc3c 100644
--- a/src/mantle/database/query/class-term-query-builder.php
+++ b/src/mantle/database/query/class-term-query-builder.php
@@ -7,9 +7,6 @@
namespace Mantle\Database\Query;
-use Mantle\Support\Collection;
-use function Mantle\Support\Helpers\collect;
-
/**
* Term Query Builder
*
@@ -90,7 +87,6 @@ public function get_query_args(): array {
return array_merge(
[
- 'fields' => 'ids',
'hide_empty' => false,
'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'number' => $this->limit,
@@ -100,6 +96,9 @@ public function get_query_args(): array {
'taxonomy' => $taxonomies,
],
$this->wheres,
+ [
+ 'fields' => 'ids',
+ ]
);
}
@@ -113,18 +112,23 @@ public function get(): Collection {
$this->query_hash = spl_object_hash( $query );
+ /**
+ * Fetch the terms IDs for the query.
+ *
+ * @var int[]
+ */
$term_ids = $this->with_clauses(
fn (): array => $query->query( $this->get_query_args() ),
);
if ( empty( $term_ids ) ) {
- return collect();
+ return new Collection(); // @phpstan-ignore-line should return
}
$models = array_map( [ $this->model, 'find' ], $term_ids );
return $this->eager_load_relations(
- collect( $models )->filter()->values(),
+ Collection::from( $models )->filter()->values(),
);
}
@@ -140,7 +144,7 @@ public function count(): int {
$this->query_hash = spl_object_hash( $query );
- return $this->with_clauses(
+ return (int) $this->with_clauses(
fn (): int => (int) $query->query(
array_merge(
$this->get_query_args(),
diff --git a/src/mantle/database/query/concerns/trait-queries-dates.php b/src/mantle/database/query/concerns/trait-queries-dates.php
new file mode 100644
index 00000000..7e269e1a
--- /dev/null
+++ b/src/mantle/database/query/concerns/trait-queries-dates.php
@@ -0,0 +1,303 @@
+
+ */
+ protected array $date_constraints = [];
+
+ /**
+ * The valid comparison operators for a date query.
+ *
+ * @var array
+ */
+ protected array $date_operators = [
+ '=',
+ '!=',
+ '>',
+ '>=',
+ '<',
+ '<=',
+ ];
+
+ /**
+ * Add a date query for a date to the query.
+ *
+ * Defaults to comparing against the post published date.
+ *
+ * @throws InvalidArgumentException If an invalid comparison operator is provided.
+ *
+ * @param DateTimeInterface|int|string $date
+ * @param string $compare Comparison operator, defaults to '='.
+ * @param string $column Column to compare against, defaults to 'post_date'.
+ * @return static
+ */
+ public function whereDate( DateTimeInterface|int|string $date, string $compare = '=', string $column = 'post_date' ): static {
+ if ( ! in_array( $compare, $this->date_operators, true ) ) {
+ throw new InvalidArgumentException( 'Invalid date comparison operator: ' . $compare );
+ }
+
+ $this->date_constraints[] = compact( 'date', 'compare', 'column' );
+
+ return $this;
+ }
+
+ /**
+ * Add a date query for the UTC publish date to the query.
+ *
+ * @param DateTimeInterface|int|string $date Date to compare against.
+ * @param string $compare Comparison operator, defaults to '='.
+ * @return static
+ */
+ public function whereUtcDate( DateTimeInterface|int|string $date, string $compare = '=' ): static {
+ return $this->whereDate( $date, $compare, 'post_date_gmt' );
+ }
+
+ /**
+ * Add a date query for the modified date to the query.
+ *
+ * @param DateTimeInterface|int|string $date Date to compare against.
+ * @param string $compare Comparison operator, defaults to '='.
+ * @return static
+ */
+ public function whereModifiedDate( DateTimeInterface|int|string $date, string $compare = '=' ): static {
+ return $this->whereDate( $date, $compare, 'post_modified' );
+ }
+
+ /**
+ * Add a date query for the modified UTC date to the query.
+ *
+ * @param DateTimeInterface|int|string $date Date to compare against.
+ * @param string $compare Comparison operator, defaults to '='.
+ * @return static
+ */
+ public function whereModifiedUtcDate( DateTimeInterface|int|string $date, string $compare = '=' ): static {
+ return $this->whereDate( $date, $compare, 'post_modified_gmt' );
+ }
+
+ /**
+ * Query for objects older than the given date.
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function olderThan( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->whereDate( $date, '<', $column );
+ }
+
+ /**
+ * Query for objects older than or equal to the given date.
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function olderThanOrEqualTo( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->whereDate( $date, '<=', $column );
+ }
+
+ /**
+ * Query for objects older than or equal to now.
+ *
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function olderThanNow( string $column = 'post_date' ): static {
+ return $this->olderThanOrEqualTo( now(), $column );
+ }
+
+ /**
+ * Alias for olderThan().
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function older_than( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->olderThan( $date, $column );
+ }
+
+ /**
+ * Alias for olderThanOrEqualTo().
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function older_than_or_equal_to( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->whereDate( $date, '<=', $column );
+ }
+
+ /**
+ * Query for objects newer than the given date.
+ *
+ * @param DateTimeInterface|int $date
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function newerThan( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->whereDate( $date, '>', $column );
+ }
+
+ /**
+ * Query for objects newer than now (in the future from now).
+ *
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function newerThanNow( string $column = 'post_date' ): static {
+ return $this->newerThan( now(), $column );
+ }
+
+ /**
+ * Query for objects newer than or equal to the given date.
+ *
+ * @param DateTimeInterface|int $date
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function newerThanOrEqualTo( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->whereDate( $date, '>=', $column );
+ }
+
+ /**
+ * Alias for newerThan().
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function newer_than( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->newerThan( $date, $column );
+ }
+
+ /**
+ * Alias for newerThanOrEqualTo().
+ *
+ * @param DateTimeInterface|int $date Date to compare against.
+ * @param string $column Column to compare against.
+ * @return static
+ */
+ public function newer_than_or_equal_to( DateTimeInterface|int $date, string $column = 'post_date' ): static {
+ return $this->newerThanOrEqualTo( $date, $column );
+ }
+
+ /**
+ * Calculate the arguments for the date query to pass to either WP_Query.
+ *
+ * @return array
+ */
+ protected function get_date_query_args(): array {
+ if ( empty( $this->date_constraints ) ) {
+ return [];
+ }
+
+ $date_query = [];
+
+ foreach ( $this->date_constraints as $constraint ) {
+ $date = $constraint['date'];
+
+ if ( is_int( $date ) ) {
+ $date = Carbon::createFromTimestamp( $date, wp_timezone() );
+ } elseif ( is_string( $date ) ) {
+ $date = Carbon::parse( $date, wp_timezone() );
+ } elseif ( $date instanceof DateTimeInterface ) {
+ $date = Carbon::instance( $date );
+ }
+
+ switch ( $constraint['compare'] ) {
+ case '<':
+ $date_query[] = [
+ 'column' => $constraint['column'],
+ 'before' => $date->toDateTimeString(),
+ ];
+ break;
+
+ case '<=':
+ $date_query[] = [
+ 'column' => $constraint['column'],
+ 'before' => $date->toDateTimeString(),
+ 'inclusive' => true,
+ ];
+ break;
+
+ case '>':
+ $date_query[] = [
+ 'column' => $constraint['column'],
+ 'after' => $date->toDateTimeString(),
+ ];
+ break;
+
+ case '>=':
+ $date_query[] = [
+ 'column' => $constraint['column'],
+ 'after' => $date->toDateTimeString(),
+ 'inclusive' => true,
+ ];
+ break;
+
+ // TODO: Review if a query for a specific date can be improved.
+ case '=':
+ $date_query[] = [
+ 'relation' => 'and',
+ [
+ 'column' => $constraint['column'],
+ 'before' => $date->toDateTimeString(),
+ 'inclusive' => true,
+ ],
+ [
+ 'column' => $constraint['column'],
+ 'after' => $date->toDateTimeString(),
+ 'inclusive' => true,
+ ],
+ ];
+ break;
+
+ case '!=':
+ $date_query[] = [
+ 'relation' => 'or',
+ [
+ 'column' => $constraint['column'],
+ 'before' => $date->toDateTimeString(),
+ 'inclusive' => false,
+ ],
+ [
+ 'column' => $constraint['column'],
+ 'after' => $date->toDateTimeString(),
+ 'inclusive' => false,
+ ],
+ ];
+ break;
+ }
+ }
+
+ return [
+ 'date_query' => $date_query,
+ ];
+ }
+}
diff --git a/src/mantle/events/composer.json b/src/mantle/events/composer.json
index 41fbaab1..04f0cae1 100644
--- a/src/mantle/events/composer.json
+++ b/src/mantle/events/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/events",
"description": "The Mantle Framework Events Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/events/trait-wordpress-action.php b/src/mantle/events/trait-wordpress-action.php
index 0f6c1471..628a9e6e 100644
--- a/src/mantle/events/trait-wordpress-action.php
+++ b/src/mantle/events/trait-wordpress-action.php
@@ -157,7 +157,7 @@ protected function validate_argument_type( $argument, ReflectionParameter $param
* to that event will pass the argument down to the callback for the action/filter.
*
* @param mixed $argument Argument value.
- * @param ReflectionParameter $parameter Callback paramater.
+ * @param ReflectionParameter $parameter Callback parameter.
*/
$modified_argument = $this->dispatch( 'mantle-typehint-resolve:' . $type->getName(), [ null, $argument, $parameter ] );
diff --git a/src/mantle/facade/composer.json b/src/mantle/facade/composer.json
index e18192c4..c13afa74 100644
--- a/src/mantle/facade/composer.json
+++ b/src/mantle/facade/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/facade",
"description": "The Mantle Framework Facade Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/faker/class-faker-provider.php b/src/mantle/faker/class-faker-provider.php
index 37bd9724..52b6f7ae 100644
--- a/src/mantle/faker/class-faker-provider.php
+++ b/src/mantle/faker/class-faker-provider.php
@@ -14,6 +14,32 @@
* Faker Block Provider
*/
class Faker_Provider extends Base {
+ /**
+ * Compile a set of blocks.
+ *
+ * @param array $blocks Blocks to compile.
+ * @return string
+ */
+ public static function blocks( array $blocks ): string {
+ return implode( "\n\n", $blocks );
+ }
+
+ /**
+ * Build a heading block.
+ *
+ * @param int $level Heading level.
+ * @return string
+ */
+ public static function heading_block( int $level = 2 ): string {
+ return static::block(
+ 'heading',
+ sprintf( '%s', $level, Lorem::sentence(), $level ),
+ [
+ 'level' => $level,
+ ],
+ );
+ }
+
/**
* Build a paragraph block.
*
@@ -51,22 +77,12 @@ public function paragraph_blocks( int $count = 3, bool $as_text = true ) {
* @param array $attributes Attributes for the block.
* @return string
*/
- public static function block( string $block_name, string $content = '', array $attributes = [] ) {
- $attributes = ! empty( $attributes ) ? \wp_json_encode( $attributes ) . ' ' : '';
-
- if ( empty( $content ) ) {
- return sprintf(
- '',
- $block_name,
- $attributes
- );
+ public static function block( string $block_name, string $content = '', array $attributes = [] ): string {
+ // Add a newline before and after the content.
+ if ( ! empty( $content ) ) {
+ $content = "\n{$content}\n";
}
- return sprintf(
- '%3$s',
- $block_name,
- $attributes,
- PHP_EOL . $content . PHP_EOL
- );
+ return get_comment_delimited_block_content( $block_name, $attributes, $content );
}
}
diff --git a/src/mantle/faker/composer.json b/src/mantle/faker/composer.json
index d787bd33..b9b983ad 100644
--- a/src/mantle/faker/composer.json
+++ b/src/mantle/faker/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/faker",
"description": "The Mantle Framework Faker Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/filesystem/composer.json b/src/mantle/filesystem/composer.json
index 81458456..a98a2f14 100644
--- a/src/mantle/filesystem/composer.json
+++ b/src/mantle/filesystem/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/filesystem",
"description": "The Mantle Framework Filesystem Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/framework/console/class-hook-usage-command.php b/src/mantle/framework/console/class-hook-usage-command.php
index d326f0f0..5171519b 100644
--- a/src/mantle/framework/console/class-hook-usage-command.php
+++ b/src/mantle/framework/console/class-hook-usage-command.php
@@ -212,7 +212,7 @@ protected function read_file( string $file ): Collection {
}
/**
- * Detrmine if the cache should be used.
+ * Determine if the cache should be used.
*
* @return bool
*/
diff --git a/src/mantle/framework/console/generators/class-block-make-command.php b/src/mantle/framework/console/generators/class-block-make-command.php
index 480f386b..9510c32c 100644
--- a/src/mantle/framework/console/generators/class-block-make-command.php
+++ b/src/mantle/framework/console/generators/class-block-make-command.php
@@ -309,7 +309,7 @@ protected function get_blocks_path(): string {
}
/**
- * Get the base path for the genereated block.
+ * Get the base path for the generated block.
*
* @param string $name The block name.
* @return string
@@ -328,7 +328,7 @@ protected function get_views_path(): string {
}
/**
- * Get the base path for the genereated block.
+ * Get the base path for the generated block.
*
* @param string $namespace The block namespace.
* @return string
diff --git a/src/mantle/framework/console/generators/stubs/factory-post.stub b/src/mantle/framework/console/generators/stubs/factory-post.stub
index a36a9ac6..62f0e851 100644
--- a/src/mantle/framework/console/generators/stubs/factory-post.stub
+++ b/src/mantle/framework/console/generators/stubs/factory-post.stub
@@ -15,13 +15,13 @@ use App\Models\{{ class }};
/**
* {{ class }} Factory
*
- * @extends \Mantle\Database\Factory\Post_Factory<\App\Models\{{ class }}>
+ * @extends \Mantle\Database\Factory\Post_Factory<\App\Models\{{ class }}, \WP_Post, {{ class }}>
*/
class {{ class }}_Factory extends \Mantle\Database\Factory\Post_Factory {
/**
* Model to use when creating objects.
*
- * @var class-string<\Mantle\Database\Model\Model>
+ * @var class-string<{{ class }}>
*/
protected string $model = {{ class }}::class;
diff --git a/src/mantle/framework/console/generators/stubs/factory-term.stub b/src/mantle/framework/console/generators/stubs/factory-term.stub
index 03fe50e7..9e08af1e 100644
--- a/src/mantle/framework/console/generators/stubs/factory-term.stub
+++ b/src/mantle/framework/console/generators/stubs/factory-term.stub
@@ -15,7 +15,7 @@ use App\Models\{{ class }};
/**
* {{ class }} Factory
*
- * @extends \Mantle\Database\Factory\Term_Factory<\App\Models\{{ class }}>
+ * @extends \Mantle\Database\Factory\Term_Factory<\App\Models\{{ class }}, \WP_Term, {{ class }}>
*/
class {{ class }}_Factory extends \Mantle\Database\Factory\Term_Factory {
/**
diff --git a/src/mantle/framework/events/class-discover-events.php b/src/mantle/framework/events/class-discover-events.php
index 7dafadfe..47feddac 100644
--- a/src/mantle/framework/events/class-discover-events.php
+++ b/src/mantle/framework/events/class-discover-events.php
@@ -20,8 +20,6 @@
/**
* Discover events within a specific directory.
- *
- * @todo Add support for WordPress hooks using attributes.
*/
class Discover_Events {
/**
@@ -80,23 +78,22 @@ protected static function get_listener_events( $listeners, string $base_path ):
}
foreach ( $listener->getMethods( ReflectionMethod::IS_PUBLIC ) as $method ) {
- // Check for attribute support with PHP 8.
- if ( version_compare( phpversion(), '8.0.0', '>=' ) ) {
- // Check if the method has an attribute action.
- $action_attributes = $method->getAttributes( Action::class );
-
- if ( ! empty( $action_attributes ) ) {
- foreach ( $action_attributes as $attribute ) {
- $instance = $attribute->newInstance();
-
- $listener_events[ $listener->name . '@' . $method->name ] = [
- [ $instance->action ],
- $instance->priority,
- ];
- }
-
- continue;
+ // Check if the method has an attribute action.
+ $action_attributes = $method->getAttributes( Action::class );
+
+ if ( ! empty( $action_attributes ) ) {
+ foreach ( $action_attributes as $attribute ) {
+ $instance = $attribute->newInstance();
+
+ $listener_events[ $listener->name . '@' . $method->name ] = [
+ [
+ $instance->hook_name,
+ ],
+ $instance->priority,
+ ];
}
+
+ continue;
}
// Handle WordPress hooks being registered with a listener.
@@ -131,7 +128,7 @@ protected static function get_listener_events( $listeners, string $base_path ):
}
$listener_events[ $listener->name . '@' . $method->name ] = [
- Reflector::get_paramater_class_names(
+ Reflector::get_parameter_class_names(
$method->getParameters()[0]
),
$priority,
diff --git a/src/mantle/http-client/composer.json b/src/mantle/http-client/composer.json
index c1a0e710..bc39b927 100644
--- a/src/mantle/http-client/composer.json
+++ b/src/mantle/http-client/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/http-client",
"description": "The Mantle Framework Http Client Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/wp-concurrent-remote-requests": "^1.0.2",
diff --git a/src/mantle/http/composer.json b/src/mantle/http/composer.json
index 6a1c4af4..d2013e91 100644
--- a/src/mantle/http/composer.json
+++ b/src/mantle/http/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/http",
"description": "The Mantle Framework Http Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"illuminate/view": "^9.52.15",
diff --git a/src/mantle/log/composer.json b/src/mantle/log/composer.json
index f9a713a5..101393c4 100644
--- a/src/mantle/log/composer.json
+++ b/src/mantle/log/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/log",
"description": "The Mantle Framework Log Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/new-relic/composer.json b/src/mantle/new-relic/composer.json
index 024b1d47..105d2f85 100644
--- a/src/mantle/new-relic/composer.json
+++ b/src/mantle/new-relic/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/new-relic",
"description": "The Mantle Framework New Relic Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/query-monitor/composer.json b/src/mantle/query-monitor/composer.json
index 05b8bc0c..67cdbd05 100644
--- a/src/mantle/query-monitor/composer.json
+++ b/src/mantle/query-monitor/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/query-monitor",
"description": "The Mantle Framework Query Monitor Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/queue/composer.json b/src/mantle/queue/composer.json
index 68874ed5..b8dd169a 100644
--- a/src/mantle/queue/composer.json
+++ b/src/mantle/queue/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/queue",
"description": "The Mantle Framework Queue Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/rest-api/composer.json b/src/mantle/rest-api/composer.json
index e0375b7b..d467ecaf 100644
--- a/src/mantle/rest-api/composer.json
+++ b/src/mantle/rest-api/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/rest-api",
"description": "The Mantle Framework REST API Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/scheduling/composer.json b/src/mantle/scheduling/composer.json
index 150201d2..2ca5d600 100644
--- a/src/mantle/scheduling/composer.json
+++ b/src/mantle/scheduling/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/scheduling",
"description": "The Mantle Framework Scheduling Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/support/attributes/class-action.php b/src/mantle/support/attributes/class-action.php
index 57ab06e5..ead14ada 100644
--- a/src/mantle/support/attributes/class-action.php
+++ b/src/mantle/support/attributes/class-action.php
@@ -12,15 +12,15 @@
/**
* Hook Action Attribute
*
- * Used to hook a method to an WordPress action at a specific priority.
+ * Used to hook a method to an WordPress hook at a specific priority.
*/
#[Attribute]
class Action {
/**
* Constructor.
*
- * @param string $action Action name.
+ * @param string $hook_name Hook name.
* @param int $priority Priority, defaults to 10.
*/
- public function __construct( public string $action, public int $priority = 10 ) {}
+ public function __construct( public string $hook_name, public int $priority = 10 ) {}
}
diff --git a/src/mantle/support/class-arr.php b/src/mantle/support/class-arr.php
index 26b27312..9614a77b 100644
--- a/src/mantle/support/class-arr.php
+++ b/src/mantle/support/class-arr.php
@@ -126,7 +126,7 @@ public static function dot( $array, string $prepend = '' ): array {
* Get all of the given array except for a specified array of keys.
*
* @param array $array Array to process.
- * @param array|string $keys Keys toi filter by.
+ * @param array|string $keys Keys to filter by.
* @return array
*/
public static function except( array $array, $keys ): array {
diff --git a/src/mantle/support/class-collection.php b/src/mantle/support/class-collection.php
index 1970e0ed..923a5312 100644
--- a/src/mantle/support/class-collection.php
+++ b/src/mantle/support/class-collection.php
@@ -446,7 +446,7 @@ public function first( callable $callback = null, $default = null ) {
/**
* Get a flattened array of the items in the collection.
*
- * @param int|float $depth
+ * @param int|float $depth
* @return static
*/
public function flatten( $depth = INF ) {
@@ -456,7 +456,7 @@ public function flatten( $depth = INF ) {
/**
* Flip the items in the collection.
*
- * @return static
+ * @return static
*/
public function flip() {
return new static( array_flip( $this->items ) );
@@ -1327,7 +1327,7 @@ public function values() {
* @template TZipValue
*
* @param \Mantle\Contracts\Support\Arrayable|iterable ...$items
- * @return static>
+ * @return static>
*/
public function zip( ...$items ) {
$arrayable_items = array_map(
diff --git a/src/mantle/support/class-driver-manager.php b/src/mantle/support/class-driver-manager.php
index 3fd3e4b3..2b2893f6 100644
--- a/src/mantle/support/class-driver-manager.php
+++ b/src/mantle/support/class-driver-manager.php
@@ -11,7 +11,7 @@
use InvalidArgumentException;
/**
- * Driver Manager for managing multiple stores and plugable drivers.
+ * Driver Manager for managing multiple stores and pluggable drivers.
*/
abstract class Driver_Manager {
/**
diff --git a/src/mantle/support/class-reflector.php b/src/mantle/support/class-reflector.php
index 6437e6c5..c35b7e6b 100644
--- a/src/mantle/support/class-reflector.php
+++ b/src/mantle/support/class-reflector.php
@@ -50,7 +50,7 @@ public static function get_parameter_class_name( $parameter ) {
* @param \ReflectionParameter $parameter
* @return array
*/
- public static function get_paramater_class_names( $parameter ) {
+ public static function get_parameter_class_names( $parameter ) {
$type = $parameter->getType();
if ( ! $type instanceof ReflectionUnionType ) {
diff --git a/src/mantle/support/class-service-provider.php b/src/mantle/support/class-service-provider.php
index 33b94a1d..c6676f0e 100644
--- a/src/mantle/support/class-service-provider.php
+++ b/src/mantle/support/class-service-provider.php
@@ -80,24 +80,36 @@ public function boot_provider() {
}
$this->boot_action_hooks();
- $this->boot_attribute_hooks();
$this->boot();
}
/**
- * Boot all actions on the service provider.
+ * Boot all actions and attribute methods on the service provider.
*
- * Allow methods in the 'on_{hook}_at_priority' and 'on_{hook}' format
- * to automatically register WordPress hooks.
+ * Collects all of the `on_{hook}` and `on_{hook}_at_{priority}` methods as
+ * well as the attribute based `#[Action]` methods and registers them with
+ * the respective WordPress hooks.
*/
- protected function boot_action_hooks() {
- collect( get_class_methods( static::class ) )
+ protected function boot_action_hooks(): void {
+ $this->collect_action_methods()
+ ->merge( $this->collect_attribute_hooks() )
+ ->unique()
+ ->each(
+ fn ( array $item ) => add_action( $item['hook'], [ $this, $item['method'] ], $item['priority'] ),
+ );
+ }
+
+ /**
+ * Collect all action methods from the service provider.
+ *
+ * @return Collection
+ */
+ protected function collect_action_methods(): Collection {
+ return collect( get_class_methods( static::class ) )
->filter(
- function( string $method ) {
- return Str::starts_with( $method, 'on_' );
- }
+ fn ( string $method ) => Str::starts_with( $method, 'on_' )
)
- ->each(
+ ->map(
function( string $method ) {
$hook = Str::after( $method, 'on_' );
$priority = 10;
@@ -108,30 +120,42 @@ function( string $method ) {
$hook = Str::before_last( $hook, '_at_' );
}
- add_action( $hook, [ $this, $method ], $priority );
+ return [
+ 'hook' => $hook,
+ 'method' => $method,
+ 'priority' => $priority,
+ ];
}
);
}
/**
- * Boot all attribute actions on the service provider.
+ * Collect all attribute actions on the service provider.
+ *
+ * Allow methods with the `#[Action]` attribute to automatically register
+ * WordPress hooks.
+ *
+ * @return Collection
*/
- protected function boot_attribute_hooks() {
+ protected function collect_attribute_hooks(): Collection {
+ $items = new Collection();
$class = new ReflectionClass( static::class );
foreach ( $class->getMethods() as $method ) {
- $action_attributes = $method->getAttributes( Action::class );
-
- if ( empty( $action_attributes ) ) {
- continue;
- }
-
- foreach ( $action_attributes as $attribute ) {
+ foreach ( $method->getAttributes( Action::class ) as $attribute ) {
$instance = $attribute->newInstance();
- add_action( $instance->action, [ $this, $method->name ], $instance->priority );
+ $items->push(
+ [
+ 'hook' => $instance->hook_name,
+ 'method' => $method->getName(),
+ 'priority' => $instance->priority,
+ ]
+ );
}
}
+
+ return $items;
}
/**
diff --git a/src/mantle/support/composer.json b/src/mantle/support/composer.json
index daa6e599..c42a6e30 100644
--- a/src/mantle/support/composer.json
+++ b/src/mantle/support/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/support",
"description": "The Mantle Framework Support Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/testing/class-utils.php b/src/mantle/testing/class-utils.php
index 8e82d5e6..9d1a9977 100644
--- a/src/mantle/testing/class-utils.php
+++ b/src/mantle/testing/class-utils.php
@@ -472,8 +472,32 @@ public static function ensure_composer_loaded() {
foreach ( $paths as $path ) {
if ( ! is_dir( $path ) && file_exists( $path ) ) {
require_once $path;
+
return;
}
}
}
+
+ /**
+ * Register a shutdown function to handle errors.
+ *
+ * Used during the WordPress installation process to catch silent errors.
+ */
+ public static function register_shutdown_function(): void {
+ register_shutdown_function( [ static::class, 'handle_shutdown' ] );
+ }
+
+ /**
+ * Handle a shutdown error and display it.
+ */
+ public static function handle_shutdown(): void {
+ $error = error_get_last();
+
+ if ( ! $error ) {
+ return;
+ }
+
+ static::error( '🚨 Error during test run:', 'Shutdown' );
+ static::code( $error );
+ }
}
diff --git a/src/mantle/testing/composer.json b/src/mantle/testing/composer.json
index 92628574..6b8f9083 100644
--- a/src/mantle/testing/composer.json
+++ b/src/mantle/testing/composer.json
@@ -2,7 +2,7 @@
"name": "mantle-framework/testing",
"description": "The Mantle Framework Testing Package",
"keywords": ["testing", "mantle"],
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/testing/concerns/trait-assertions.php b/src/mantle/testing/concerns/trait-assertions.php
index 23512c9d..3c4bec91 100644
--- a/src/mantle/testing/concerns/trait-assertions.php
+++ b/src/mantle/testing/concerns/trait-assertions.php
@@ -2,15 +2,19 @@
/**
* This file contains the Assertions trait
*
+ * phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_print_r
+ *
* @package Mantle
*/
namespace Mantle\Testing\Concerns;
+use Mantle\Contracts\Database\Core_Object;
use Mantle\Database\Model\Post;
use Mantle\Database\Model\Term;
use Mantle\Database\Model\User;
use PHPUnit\Framework\Assert as PHPUnit;
+use WP_Post;
use WP_Term;
use function Mantle\Support\Helpers\get_term_object;
@@ -19,7 +23,8 @@
* Assorted Test_Cast assertions.
*/
trait Assertions {
- use Asset_Assertions;
+ use Asset_Assertions,
+ Block_Assertions;
/**
* Asserts that the given value is an instance of WP_Error.
@@ -84,7 +89,7 @@ public static function assertEqualsIgnoreEOL( $expected, $actual ) {
* @param array $expected Expected array.
* @param array $actual Array to check.
*/
- public static function assertEqualSets( $expected, $actual ) {
+ public static function assertEqualSets( $expected, $actual ): void {
sort( $expected );
sort( $actual );
PHPUnit::assertEquals( $expected, $actual );
@@ -96,7 +101,7 @@ public static function assertEqualSets( $expected, $actual ) {
* @param array $expected Expected array.
* @param array $actual Array to check.
*/
- public static function assertEqualSetsWithIndex( $expected, $actual ) {
+ public static function assertEqualSetsWithIndex( $expected, $actual ): void {
ksort( $expected );
ksort( $actual );
PHPUnit::assertEquals( $expected, $actual );
@@ -107,7 +112,7 @@ public static function assertEqualSetsWithIndex( $expected, $actual ) {
*
* @param array $array Array to check.
*/
- public static function assertNonEmptyMultidimensionalArray( $array ) {
+ public static function assertNonEmptyMultidimensionalArray( $array ): void {
PHPUnit::assertTrue( is_array( $array ) );
PHPUnit::assertNotEmpty( $array );
@@ -129,7 +134,7 @@ public static function assertNonEmptyMultidimensionalArray( $array ) {
* @param string ...$prop Any number of WP_Query properties that are expected
* to be true for the current request.
*/
- public static function assertQueryTrue( ...$prop ) {
+ public static function assertQueryTrue( ...$prop ): void {
global $wp_query;
$all = [
@@ -197,7 +202,7 @@ public static function assertQueryTrue( ...$prop ) {
* @param int $id Expected ID.
*/
public static function assertQueriedObjectId( int $id ): void {
- PHPUnit::assertSame( $id, get_queried_object_id() );
+ PHPUnit::assertSame( $id, get_queried_object_id(), 'Queried object ID is not the same.' );
}
/**
@@ -206,44 +211,32 @@ public static function assertQueriedObjectId( int $id ): void {
* @param int $id Expected ID.
*/
public static function assertNotQueriedObjectId( int $id ): void {
- PHPUnit::assertNotSame( $id, get_queried_object_id() );
+ PHPUnit::assertNotSame( $id, get_queried_object_id(), 'Queried object ID is the same.' );
}
/**
* Assert that a given object is equivalent to the global queried object.
*
- * @param Object $object Expected object.
+ * @param mixed $object Expected object.
+ * @param bool $strict Whether to assert the same object type or just the same identifying data.
*/
- public static function assertQueriedObject( mixed $object ): void {
+ public static function assertQueriedObject( mixed $object, bool $strict = false ): void {
$queried_object = get_queried_object();
- // First, assert the same object types.
- PHPUnit::assertInstanceOf( get_class( $object ), $queried_object );
+ // Assert the same object types if strict mode.
+ if ( $strict ) {
+ PHPUnit::assertInstanceOf( get_class( $object ), $queried_object );
+ }
// Next, assert identifying data about the object.
- switch ( true ) {
- case $object instanceof Post:
- case $object instanceof User:
- PHPUnit::assertSame( $object->id(), $queried_object->ID );
- break;
-
- case $object instanceof Term:
- PHPUnit::assertSame( $object->id(), $queried_object->term_id );
- break;
-
- case $object instanceof \WP_Post:
- case $object instanceof \WP_User:
- PHPUnit::assertSame( $object->ID, $queried_object->ID );
- break;
-
- case $object instanceof \WP_Term:
- PHPUnit::assertSame( $object->term_id, $queried_object->term_id );
- break;
-
- case $object instanceof \WP_Post_Type:
- PHPUnit::assertSame( $object->name, $queried_object->name );
- break;
- }
+ match ( true ) {
+ $object instanceof Post || $object instanceof User => PHPUnit::assertSame( $object->id(), $queried_object->ID, 'Queried object ID is not the same.' ),
+ $object instanceof Term => PHPUnit::assertSame( $object->id(), $queried_object->term_id, 'Queried object ID is not the same.' ),
+ $object instanceof \WP_Post || $object instanceof \WP_User => PHPUnit::assertSame( $object->ID, $queried_object->ID, 'Queried object ID is not the same.' ),
+ $object instanceof \WP_Term => PHPUnit::assertSame( $object->term_id, $queried_object->term_id, 'Queried object ID is not the same.' ),
+ $object instanceof \WP_Post_Type => PHPUnit::assertSame( $object->name, $queried_object->name, 'Queried object name is not the same.' ),
+ default => PHPUnit::fail( 'Unknown object type.' ),
+ };
}
/**
@@ -254,36 +247,21 @@ public static function assertQueriedObject( mixed $object ): void {
public static function assertNotQueriedObject( mixed $object ): void {
$queried_object = get_queried_object();
- switch ( true ) {
- case $object instanceof Post:
- case $object instanceof User:
- PHPUnit::assertNotSame( $object->id(), $queried_object->ID );
- break;
-
- case $object instanceof Term:
- PHPUnit::assertNotSame( $object->id(), $queried_object->term_id );
- break;
-
- case $object instanceof \WP_Post:
- case $object instanceof \WP_User:
- PHPUnit::assertNotSame( $object->ID, $queried_object->ID );
- break;
-
- case $object instanceof \WP_Term:
- PHPUnit::assertNotSame( $object->term_id, $queried_object->term_id );
- break;
-
- case $object instanceof \WP_Post_Type:
- PHPUnit::assertNotSame( $object->name, $queried_object->name );
- break;
- }
+ match ( true ) {
+ $object instanceof Post || $object instanceof User => PHPUnit::assertNotSame( $object->id(), $queried_object->ID, 'Queried object ID is the same.' ),
+ $object instanceof Term => PHPUnit::assertNotSame( $object->id(), $queried_object->term_id, 'Queried object ID is the same.' ),
+ $object instanceof \WP_Post || $object instanceof \WP_User => PHPUnit::assertNotSame( $object->ID, $queried_object->ID, 'Queried object ID is the same.' ),
+ $object instanceof \WP_Term => PHPUnit::assertNotSame( $object->term_id, $queried_object->term_id, 'Queried object ID is the same.' ),
+ $object instanceof \WP_Post_Type => PHPUnit::assertNotSame( $object->name, $queried_object->name, 'Queried object name is the same.' ),
+ default => PHPUnit::fail( 'Unknown object type.' ),
+ };
}
/**
* Assert that the queried object is null.
*/
public static function assertQueriedObjectNull(): void {
- PHPUnit::assertNull( get_queried_object(), 'Expected queried object to be null.' );
+ PHPUnit::assertNull( get_queried_object(), 'Queried object is not null.' );
}
/**
@@ -291,18 +269,19 @@ public static function assertQueriedObjectNull(): void {
*
* @param array $arguments Arguments to query against.
*/
- public function assertPostExists( array $arguments ) {
- $posts = \get_posts(
- array_merge(
- [
- 'fields' => 'ids',
- 'posts_per_page' => 1,
- ],
- $arguments
- )
+ public function assertPostExists( array $arguments ): void {
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'posts_per_page' => 1,
+ ],
+ $arguments
);
- PHPUnit::assertNotEmpty( $posts );
+ PHPUnit::assertNotEmpty(
+ \get_posts( $arguments ),
+ "Post not found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -310,18 +289,19 @@ public function assertPostExists( array $arguments ) {
*
* @param array $arguments Arguments to query against.
*/
- public function assertPostDoesNotExists( array $arguments ) {
- $posts = \get_posts(
- array_merge(
- [
- 'fields' => 'ids',
- 'posts_per_page' => 1,
- ],
- $arguments
- )
+ public function assertPostDoesNotExists( array $arguments ): void {
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'posts_per_page' => 1,
+ ],
+ $arguments
);
- PHPUnit::assertEmpty( $posts );
+ PHPUnit::assertEmpty(
+ \get_posts( $arguments ),
+ "Post found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -329,19 +309,20 @@ public function assertPostDoesNotExists( array $arguments ) {
*
* @param array $arguments Arguments to query against.
*/
- public function assertTermExists( array $arguments ) {
- $terms = \get_terms(
- array_merge(
- [
- 'fields' => 'ids',
- 'count' => 1,
- 'hide_empty' => false,
- ],
- $arguments
- )
+ public function assertTermExists( array $arguments ): void {
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'count' => 1,
+ 'hide_empty' => false,
+ ],
+ $arguments
);
- PHPUnit::assertNotEmpty( $terms );
+ PHPUnit::assertNotEmpty(
+ \get_terms( $arguments ),
+ "Term not found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -349,19 +330,20 @@ public function assertTermExists( array $arguments ) {
*
* @param array $arguments Arguments to query against.
*/
- public function assertTermDoesNotExists( array $arguments ) {
- $terms = \get_terms(
- array_merge(
- [
- 'fields' => 'ids',
- 'count' => 1,
- 'hide_empty' => false,
- ],
- $arguments
- )
+ public function assertTermDoesNotExists( array $arguments ): void {
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'count' => 1,
+ 'hide_empty' => false,
+ ],
+ $arguments
);
- PHPUnit::assertEmpty( $terms );
+ PHPUnit::assertEmpty(
+ \get_terms( $arguments ),
+ "Term found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -370,17 +352,18 @@ public function assertTermDoesNotExists( array $arguments ) {
* @param array $arguments Arguments to query against.
*/
public function assertUserExists( array $arguments ) {
- $users = \get_users(
- array_merge(
- [
- 'fields' => 'ids',
- 'count' => 1,
- ],
- $arguments
- )
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'count' => 1,
+ ],
+ $arguments
);
- PHPUnit::assertNotEmpty( $users );
+ PHPUnit::assertNotEmpty(
+ \get_users( $arguments ),
+ "User not found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -388,18 +371,19 @@ public function assertUserExists( array $arguments ) {
*
* @param array $arguments Arguments to query against.
*/
- public function assertUserDoesNotExists( array $arguments ) {
- $users = \get_users(
- array_merge(
- [
- 'fields' => 'ids',
- 'count' => 1,
- ],
- $arguments
- )
+ public function assertUserDoesNotExists( array $arguments ): void {
+ $arguments = array_merge(
+ [
+ 'fields' => 'ids',
+ 'count' => 1,
+ ],
+ $arguments
);
- PHPUnit::assertEmpty( $users );
+ PHPUnit::assertEmpty(
+ \get_users( $arguments ),
+ "User found with arguments: \n" . print_r( $arguments, true ),
+ );
}
/**
@@ -433,7 +417,7 @@ protected function get_term_from_argument( $argument ): ?WP_Term {
* @param Term|\WP_Term|int $term Term to check.
* @return void
*/
- public function assertPostHasTerm( $post, $term ) {
+ public function assertPostHasTerm( Post|WP_Post|int $post, Term|WP_Term|int $term ): void {
if ( $post instanceof Post ) {
$post = $post->id();
}
@@ -441,7 +425,7 @@ public function assertPostHasTerm( $post, $term ) {
$term = $this->get_term_from_argument( $term );
PHPUnit::assertInstanceOf( \WP_Term::class, $term, 'Term not found to assert against' );
- PHPUnit::assertTrue( \has_term( $term->term_id, $term->taxonomy, $post ) );
+ PHPUnit::assertTrue( \has_term( $term->term_id, $term->taxonomy, $post ), 'Term not found on post' );
}
/**
@@ -453,7 +437,7 @@ public function assertPostHasTerm( $post, $term ) {
* @param Term|\WP_Term|int $term Term to check.
* @return void
*/
- public function assertPostNotHasTerm( $post, $term ) {
+ public function assertPostNotHasTerm( Post|WP_Post|int $post, Term|WP_Term|int $term ): void {
if ( $post instanceof Post ) {
$post = $post->id();
}
@@ -461,7 +445,7 @@ public function assertPostNotHasTerm( $post, $term ) {
$term = $this->get_term_from_argument( $term );
if ( $term ) {
- PHPUnit::assertFalse( \has_term( $term->term_id, $term->taxonomy, $post ) );
+ PHPUnit::assertFalse( \has_term( $term->term_id, $term->taxonomy, $post ), 'Term found on post' );
}
}
@@ -471,7 +455,7 @@ public function assertPostNotHasTerm( $post, $term ) {
* @param Post|\WP_Post|int $post Post to check.
* @param Term|\WP_Term|int $term Term to check.
*/
- public function assertPostsDoesNotHaveTerm( $post, $term ) {
+ public function assertPostsDoesNotHaveTerm( Post|WP_Post|int $post, Term|WP_Term|int $term ): void {
$this->assertPostNotHasTerm( $post, $term );
}
}
diff --git a/src/mantle/testing/concerns/trait-block-assertions.php b/src/mantle/testing/concerns/trait-block-assertions.php
new file mode 100644
index 00000000..62644f92
--- /dev/null
+++ b/src/mantle/testing/concerns/trait-block-assertions.php
@@ -0,0 +1,156 @@
+assertNotEmpty(
+ match_blocks( $string, $args ),
+ 'Failed asserting that string matches block with arguments ' . print_r( $args, true ),
+ );
+ }
+
+ /**
+ * Assert that a string does not match a block.
+ *
+ * The arguments are passed directly to `match_block()`.
+ *
+ * @see \Alley\WP\match_block()
+ *
+ * @param string $string The string to check.
+ * @param array $args The arguments to pass to `match_block()`.
+ */
+ public function assertStringNotMatchesBlock( string $string, array $args ): void {
+ $this->assertEmpty(
+ match_blocks( $string, $args ),
+ 'Failed asserting that string does not match block with arguments ' . print_r( $args, true ),
+ );
+ }
+
+ /**
+ * Assert that a string has a block.
+ *
+ * @param string $string The string to check.
+ * @param string|string[] $block_name The block name(s) to check for. Will attempt to match any of the names.
+ * @param array $attrs Optional. Attributes to check for.
+ */
+ public function assertStringHasBlock( string $string, string|array $block_name, array $attrs = [] ): void {
+ $this->assertNotEmpty(
+ match_blocks(
+ $string,
+ [
+ 'attrs' => $this->convert_arguments_for_matching( $attrs ),
+ 'name' => $block_name,
+ ],
+ ),
+ ! empty( $attrs )
+ ? "Failed asserting that string has block [{$block_name}] with attributes " . print_r( $attrs, true )
+ : "Failed asserting that string has block [{$block_name}]."
+ );
+ }
+
+ /**
+ * Assert that a string does not have a block.
+ *
+ * @param string $string The string to check.
+ * @param string|string[] $block_name The block name(s) to check for. Will attempt to match any of the names.
+ * @param array $attrs Optional. Attributes to check for.
+ */
+ public function assertStringNotHasBlock( string $string, string|array $block_name, array $attrs = [] ): void {
+ $this->assertEmpty(
+ match_blocks(
+ $string,
+ [
+ 'attrs' => $this->convert_arguments_for_matching( $attrs ),
+ 'name' => $block_name,
+ ],
+ ),
+ ! empty( $attrs )
+ ? "Failed asserting that string does not have block [{$block_name}] with attributes " . print_r( $attrs, true )
+ : "Failed asserting that string does not have block [{$block_name}]",
+ );
+ }
+
+ /**
+ * Assert that a post has a block in its content.
+ *
+ * @param Post|WP_Post $post The post to check.
+ * @param string|string[] $block_name The block name(s) to check for. Will attempt to match any of the names.
+ * @param array $attrs Optional. Attributes to check for.
+ */
+ public function assertPostHasBlock( Post|WP_Post $post, string|array $block_name, array $attrs = [] ): void {
+ $this->assertStringHasBlock( $post->post_content, $block_name, $attrs );
+ }
+
+ /**
+ * Assert that a post does not have a block in its content.
+ *
+ * @param Post|WP_Post $post The post to check.
+ * @param string|string[] $block_name The block name(s) to check for. Will attempt to match any of the names.
+ * @param array $attrs Optional. Attributes to check for.
+ */
+ public function assertPostNotHasBlock( Post|WP_Post $post, string|array $block_name, array $attrs = [] ): void {
+ $this->assertStringNotHasBlock( $post->post_content, $block_name, $attrs );
+ }
+
+ /**
+ * Convert the key/value arguments to an array that can be passed to
+ * `match_block()`.
+ *
+ * @param array $args The arguments to convert.
+ * @return array
+ */
+ protected function convert_arguments_for_matching( array $args ): array {
+ // PHPCS is crashing on these lines for some reason. Disabling it for now
+ // until we've upgrading to WPCS 3.0.
+
+ /* phpcs:disable */
+ return collect( $args )->reduce(
+ function ( array $carry, $value, $key ) {
+ // Allow for passing an argument pair directly.
+ if ( is_array( $value ) && isset( $value['key'], $value['value'] ) ) {
+ $carry[] = $value;
+
+ return $carry;
+ }
+
+ $carry[] = [
+ $key => $value,
+ 'value' => $value,
+ ];
+
+ return $carry;
+ },
+ []
+ );
+ /* phpcs:enable */
+ }
+}
diff --git a/src/mantle/testing/concerns/trait-interacts-with-cron.php b/src/mantle/testing/concerns/trait-interacts-with-cron.php
index fa8e0720..314a4ddc 100644
--- a/src/mantle/testing/concerns/trait-interacts-with-cron.php
+++ b/src/mantle/testing/concerns/trait-interacts-with-cron.php
@@ -38,7 +38,7 @@ public function assertInCronQueue( string $action, array $args = [] ): void {
}
/**
- * Assert tha an action is not in a cron queue.
+ * Assert that an action is not in a cron queue.
*
* @param string $action Action hook of the event.
* @param array $args Arguments for the cron queue event.
diff --git a/src/mantle/testing/concerns/trait-interacts-with-hooks.php b/src/mantle/testing/concerns/trait-interacts-with-hooks.php
index f847fac6..c7ef4750 100644
--- a/src/mantle/testing/concerns/trait-interacts-with-hooks.php
+++ b/src/mantle/testing/concerns/trait-interacts-with-hooks.php
@@ -20,9 +20,9 @@ trait Interacts_With_Hooks {
/**
* Storage of the hooks that have been fired.
*
- * @var array
+ * @var array
*/
- protected $hooks_fired = [];
+ protected array $hooks_fired = [];
/**
* Expectation Container
@@ -49,6 +49,7 @@ function( $value ) {
}
$this->hooks_fired[ $filter ]++;
+
return $value;
}
);
@@ -76,14 +77,25 @@ public function interacts_with_hooks_tear_down(): void {
* @return void
*/
public function assertHookApplied( string $hook, int $count = null ): void {
- PHPUnit::assertTrue( ! empty( $this->hooks_fired[ $hook ] ) );
+ PHPUnit::assertNotEmpty(
+ $this->hooks_fired[ $hook ] ?? [],
+ "Asserted that [{$hook}] was not fired."
+ );
- if ( null !== $count ) {
+ if ( $count ) {
$times_fired = $this->hooks_fired[ $hook ] ?? 0;
+
PHPUnit::assertEquals(
$count,
- $this->hooks_fired[ $hook ],
- "Asserted that [{$hook}] was fired {$count} times when only fired {$times_fired} times."
+ $times_fired,
+ sprintf(
+ 'Asserted that [%s] was applied %d %s when only applied %d %s.',
+ $hook,
+ $count,
+ 1 === $count ? 'time' : 'times',
+ $times_fired,
+ 1 === $times_fired ? 'time' : 'times',
+ ),
);
}
}
@@ -95,7 +107,11 @@ public function assertHookApplied( string $hook, int $count = null ): void {
* @return void
*/
public function assertHookNotApplied( string $hook ): void {
- PHPUnit::assertTrue( empty( $this->hooks_fired[ $hook ] ) );
+ PHPUnit::assertEquals(
+ 0,
+ $this->hooks_fired[ $hook ] ?? 0,
+ "Asserted that [{$hook}] was fired."
+ );
}
/**
diff --git a/src/mantle/testing/concerns/trait-interacts-with-requests.php b/src/mantle/testing/concerns/trait-interacts-with-requests.php
index 382f1467..6a4a2718 100644
--- a/src/mantle/testing/concerns/trait-interacts-with-requests.php
+++ b/src/mantle/testing/concerns/trait-interacts-with-requests.php
@@ -2,7 +2,7 @@
/**
* Interacts_With_Requests trait file.
*
- * @phpcs:disable WordPress.NamingConventions.ValidFunctionName
+ * @phpcs:disable WordPress.NamingConventions.ValidFunctionName, Squiz.Commenting.FunctionComment.SpacingAfterParamType
*
* @package Mantle
*/
@@ -10,6 +10,7 @@
namespace Mantle\Testing\Concerns;
use Closure;
+use Mantle\Contracts\Support\Arrayable;
use Mantle\Http_Client\Request;
use Mantle\Http_Client\Response;
use Mantle\Support\Collection;
@@ -26,19 +27,21 @@
/**
* Allow Mock HTTP Requests
+ *
+ * @mixin \PHPUnit\Framework\TestCase
*/
trait Interacts_With_Requests {
/**
* Storage of the callbacks to mock the requests.
*
- * @var Collection
+ * @var Collection
*/
protected Collection $stub_callbacks;
/**
* Storage of request URLs.
*
- * @var Collection
+ * @var Collection
*/
protected Collection $recorded_requests;
@@ -46,21 +49,21 @@ trait Interacts_With_Requests {
* Flag to prevent external requests from being made. By default, this is
* false.
*
- * @var Mock_Http_Response|Closure|bool
+ * @var Mock_Http_Response|callable|bool
*/
protected mixed $preventing_stray_requests = false;
/**
* Recorded actual HTTP requests made during the test.
*
- * @var Collection
+ * @var Collection
*/
protected Collection $recorded_actual_requests;
/**
* Setup the trait.
*/
- public function interacts_with_requests_set_up() {
+ public function interacts_with_requests_set_up(): void {
$this->stub_callbacks = collect();
$this->recorded_requests = collect();
$this->recorded_actual_requests = collect();
@@ -71,7 +74,7 @@ public function interacts_with_requests_set_up() {
/**
* Remove the filter to intercept the request.
*/
- public function interacts_with_requests_tear_down() {
+ public function interacts_with_requests_tear_down(): void {
\remove_filter( 'pre_http_request', [ $this, 'pre_http_request' ], PHP_INT_MAX );
$this->report_stray_requests();
@@ -82,14 +85,14 @@ public function interacts_with_requests_tear_down() {
*
* @param Mock_Http_Response|\Closure|bool $response A default response or callback to use, boolean otherwise.
*/
- public function prevent_stray_requests( $response = true ) {
+ public function prevent_stray_requests( Mock_Http_Response|Closure|bool $response = true ): void {
$this->preventing_stray_requests = $response;
}
/**
* Allow stray external requests.
*/
- public function allow_stray_requests() {
+ public function allow_stray_requests(): void {
$this->preventing_stray_requests = false;
}
@@ -102,21 +105,30 @@ public function allow_stray_requests() {
* information on how this is used, see the `create_stub_request_callback()` method below and the
* relevant test for the trait (Mantle\Tests\Testing\Concerns\Test_Interacts_With_Requests).
*
+ * Example:
+ *
+ * $this->fake_request();
+ * $this->fake_request( 'https://testing.com/*' );
+ * $this->fake_request( 'https://testing.com/*' )->with_response_code( 404 )->with_body( 'test body' );
+ * $this->fake_request( fn () => Mock_Http_Response::create()->with_body( 'test body' ) );
+ *
+ * @link https://mantle.alley.com/docs/testing/remote-requests#faking-requests Documentation
+ *
* @throws \InvalidArgumentException Thrown on invalid argument.
*
- * @param Closure|string|array $url_or_callback URL to fake, array of URL and response pairs, or a closure
- * that will return a faked response.
- * @param Mock_Http_Response|Mock_Http_Sequence $response Optional response object, defaults to creating a 200 response.
+ * @template TCallableReturn of Mock_Http_Sequence|Mock_Http_Response|Arrayable
+ *
+ * @param (callable(string, array): TCallableReturn)|Mock_Http_Response|string|array $url_or_callback URL to fake, array of URL and response pairs, or a closure
+ * that will return a faked response.
+ * @param Mock_Http_Response|callable $response Optional response object, defaults to a 200 response with no body.
* @return static|Mock_Http_Response
*/
- public function fake_request( Mock_Http_Response|Mock_Http_Sequence|Closure|string|array|null $url_or_callback = null, Mock_Http_Response|Mock_Http_Sequence|Closure $response = null ) {
+ public function fake_request( Mock_Http_Response|callable|string|array|null $url_or_callback = null, Mock_Http_Response|callable $response = null ): static|Mock_Http_Response {
if ( is_array( $url_or_callback ) ) {
$this->stub_callbacks = $this->stub_callbacks->merge(
collect( $url_or_callback )
->map(
- function( $response, $url_or_callback ) {
- return $this->create_stub_request_callback( $url_or_callback, $response );
- }
+ fn ( $response, $url_or_callback ) => $this->create_stub_request_callback( $url_or_callback, $response ),
)
);
@@ -126,6 +138,7 @@ function( $response, $url_or_callback ) {
// Allow a callback to be passed instead.
if ( is_callable( $url_or_callback ) ) {
$this->stub_callbacks->push( $url_or_callback );
+
return $this;
}
@@ -152,18 +165,6 @@ function( $response, $url_or_callback ) {
return $response;
}
- /**
- * Alias for fake_request().
- *
- * @param Closure|string|array $url_or_callback URL to fake, array of URL and response pairs, or a closure
- * that will return a faked response.
- * @param Mock_Http_Response|Mock_Http_Sequence $response Optional response object, defaults to creating a 200 response.
- * @return static|Mock_Http_Response
- */
- public function fake( Mock_Http_Response|Mock_Http_Sequence|Closure|string|array|null $url_or_callback = null, Mock_Http_Response|Mock_Http_Sequence|Closure $response = null ) {
- return $this->fake_request( $url_or_callback, $response );
- }
-
/**
* Filters pre_http_request to intercept the request, mock a response, and
* return it. If the response has already been preempted, the preempt will
@@ -209,16 +210,31 @@ public function pre_http_request( $preempt, $request_args, $url ) {
*
* @param string $url Request URL.
* @param array $request_args Request arguments.
- * @return array|null
+ * @return array|WP_Error|null
*/
protected function get_stub_response( $url, $request_args ): array|WP_Error|null {
if ( ! $this->stub_callbacks->is_empty() ) {
foreach ( $this->stub_callbacks as $callback ) {
$response = $callback( $url, $request_args );
- if ( $response instanceof Mock_Http_Response ) {
+
+ if ( $response instanceof Mock_Http_Response || $response instanceof Arrayable ) {
return $response->to_array();
}
+ // Throw an error when an unknown response type is returned from the callback.
+ if ( $response && ! is_array( $response ) && ! is_wp_error( $response ) ) {
+ throw new RuntimeException(
+ sprintf(
+ 'Unknown response type returned for faked request to [%s]. Expected a (%s|%s|%s|array), got %s.',
+ $url,
+ Mock_Http_Response::class,
+ Arrayable::class,
+ WP_Error::class,
+ gettype( $response )
+ ),
+ );
+ }
+
if ( ! is_null( $response ) ) {
return $response;
}
@@ -228,7 +244,7 @@ protected function get_stub_response( $url, $request_args ): array|WP_Error|null
if ( false !== $this->preventing_stray_requests ) {
$prevent = value( $this->preventing_stray_requests );
- if ( $prevent instanceof Mock_Http_Response ) {
+ if ( $prevent instanceof Mock_Http_Response || $prevent instanceof Arrayable ) {
return $prevent->to_array();
}
@@ -277,17 +293,17 @@ protected function store_streamed_response( string $url, array $response, array
/**
* Retrieve a callback for the stubbed response.
*
- * @param string $url URL to stub.
- * @param Closure|Mock_Http_Response|Mock_Http_Sequence $response Response to send.
- * @return Closure
+ * @param string $url URL to stub.
+ * @param callable|Mock_Http_Response $response Response to send.
+ * @return callable
*/
- protected function create_stub_request_callback( string $url, $response ): Closure {
+ protected function create_stub_request_callback( string $url, Mock_Http_Response|callable $response ): callable {
return function( string $request_url, array $request_args ) use ( $url, $response ) {
if ( ! Str::is( Str::start( $url, '*' ), $request_url ) ) {
return;
}
- return $response instanceof Closure || $response instanceof Mock_Http_Sequence
+ return is_callable( $response )
? $response( $request_url, $request_args )
: $response;
};
@@ -333,9 +349,10 @@ protected function report_stray_requests(): void {
* @param int $expected_times Number of times the request should have been
* sent, optional.
*/
- public function assertRequestSent( $url_or_callback = null, int $expected_times = null ) {
+ public function assertRequestSent( string|callable|null $url_or_callback = null, int $expected_times = null ): void {
if ( is_null( $url_or_callback ) ) {
PHPUnit::assertTrue( $this->recorded_requests->is_not_empty(), 'A request was made.' );
+
return;
}
@@ -360,7 +377,7 @@ public function assertRequestSent( $url_or_callback = null, int $expected_times
*
* @param string|callable $url_or_callback URL to check against or callback.
*/
- public function assertRequestNotSent( $url_or_callback = null ) {
+ public function assertRequestNotSent( string|callable|null $url_or_callback = null ): void {
if ( is_string( $url_or_callback ) ) {
$url_or_callback = fn ( $request ) => Str::is( $url_or_callback, $request->url() );
}
@@ -377,7 +394,7 @@ public function assertRequestNotSent( $url_or_callback = null ) {
*
* @return void
*/
- public function assertNoRequestSent() {
+ public function assertNoRequestSent(): void {
PHPUnit::assertEmpty(
$this->recorded_requests,
'Requests were recorded',
@@ -390,7 +407,7 @@ public function assertNoRequestSent() {
* @param int $count Request count.
* @return void
*/
- public function assertRequestCount( int $count ) {
+ public function assertRequestCount( int $count ): void {
PHPUnit::assertCount( $count, $this->recorded_requests );
}
}
diff --git a/src/mantle/testing/concerns/trait-prevent-remote-requests.php b/src/mantle/testing/concerns/trait-prevent-remote-requests.php
new file mode 100644
index 00000000..a925977a
--- /dev/null
+++ b/src/mantle/testing/concerns/trait-prevent-remote-requests.php
@@ -0,0 +1,27 @@
+prevent_remote_requests ) {
+ $this->prevent_stray_requests( new Mock_Http_Response() );
+ }
+ }
+}
diff --git a/src/mantle/testing/concerns/trait-rsync-installation.php b/src/mantle/testing/concerns/trait-rsync-installation.php
index ca5589d2..dc1b2a9e 100644
--- a/src/mantle/testing/concerns/trait-rsync-installation.php
+++ b/src/mantle/testing/concerns/trait-rsync-installation.php
@@ -136,11 +136,15 @@ public function maybe_rsync( string $to = null, string $from = null ): static {
* Maybe rsync the codebase to the wp-content within WordPress.
*
* Will attempt to locate the wp-content directory relative to the current
- * directory. As a fallback, it will assumme it is being called from either
+ * directory. As a fallback, it will assume it is being called from either
* /wp-content/plugin/:plugin/tests OR /wp-content/themes/:theme/tests. Will
* rsync the codebase from the wp-content level to the root of the WordPress
* installation. Also will attempt to locate the wp-content directory relative
* to the current directory.
+ *
+ * This isn't a perfect function and can sometimes fail to locate the proper
+ * `wp-content` directory. If it does fail to work, manually call
+ * `maybe_rsync()` yourself with the proper paths.
*/
public function maybe_rsync_wp_content(): static {
// Attempt to locate wp-content relative to the current directory.
@@ -349,7 +353,7 @@ protected function perform_rsync_testsuite() {
// Install WordPress at the base installation if it doesn't exist yet.
if ( ! is_dir( $base_install_path ) || ! is_file( "{$base_install_path}/wp-load.php" ) ) {
Utils::info(
- "Installating WordPress at {$base_install_path} ...",
+ "Installing WordPress at {$base_install_path} ...",
'Install Rsync'
);
@@ -484,7 +488,7 @@ protected function get_phpunit_command(): string {
// Use the first argument and translate it to the rsync-ed path.
$executable = $this->translate_location( $args[0] );
- // Attempt to fallback to the phpunit binrary reference in PHP_SELF. This
+ // Attempt to fallback to the phpunit binary reference in PHP_SELF. This
// would be the one used to invoke the current script. With that, we can
// translate it to the new location in the rsync-ed WordPress
// installation.
diff --git a/src/mantle/testing/concerns/trait-snapshot-testing.php b/src/mantle/testing/concerns/trait-snapshot-testing.php
index e0da6083..3104bb33 100644
--- a/src/mantle/testing/concerns/trait-snapshot-testing.php
+++ b/src/mantle/testing/concerns/trait-snapshot-testing.php
@@ -3,14 +3,17 @@
* Snapshot_Testing trait file
*
* phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
+ * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
*
* @package Mantle
*/
namespace Mantle\Testing\Concerns;
+use DOMDocument;
use Mantle\Support\Arr;
use Mantle\Support\Str;
+use Mantle\Testing\Snapshots\HTML_Driver;
use function Mantle\Support\Helpers\collect;
use function Mantle\Support\Helpers\data_get;
@@ -28,10 +31,11 @@ trait Snapshot_Testing {
*
* Alias to `assertMatchesSnapshotContent()`.
*
+ * @param mixed ...$args Optional. Additional arguments to pass to the snapshot assertion.
* @return static
*/
- public function assertMatchesSnapshot(): static {
- return $this->assertMatchesSnapshotContent();
+ public function assertMatchesSnapshot( ...$args ): static {
+ return $this->assertMatchesSnapshotContent( ...$args );
}
/**
@@ -40,14 +44,17 @@ public function assertMatchesSnapshot(): static {
* Checks the response content-type to use the proper driver to make the
* assertion against.
*
+ * @param mixed ...$args Optional. Additional arguments to pass to the snapshot assertion.
* @return static
*/
- public function assertMatchesSnapshotContent(): static {
+ public function assertMatchesSnapshotContent( ...$args ): static {
if ( $this->test_case ) {
$content_type = $this->get_header( 'content-type' );
if ( Str::contains( $content_type, 'application/json', true ) ) {
- return $this->assertMatchesSnapshotJson();
+ return $this->assertMatchesSnapshotJson( ...$args );
+ } elseif ( Str::contains( $content_type, 'text/html', true ) ) {
+ return $this->assertMatchesSnapshotHtml( ...$args );
} else {
$this->test_case->assertMatchesSnapshot( $this->get_content() );
}
@@ -59,13 +66,50 @@ public function assertMatchesSnapshotContent(): static {
/**
* Assert that the response's HTML content matches a stored snapshot.
*
+ * @param array|string|null $selectors Optional. The XPath selectors to include in the snapshot, or null to include the entire content. Defaults to the entire content.
* @return static
*/
- public function assertMatchesSnapshotHtml(): static {
- if ( $this->test_case ) {
- $this->test_case->assertMatchesHtmlSnapshot( $this->get_content() );
+ public function assertMatchesSnapshotHtml( array|string $selectors = null ): static {
+ if ( ! $this->test_case ) {
+ return $this;
}
+ if ( empty( $selectors ) ) {
+ $this->test_case->assertMatchesSnapshot( $this->get_content(), new HTML_Driver() );
+
+ return $this;
+ }
+
+ if ( ! is_array( $selectors ) ) {
+ $selectors = [ $selectors ];
+ }
+
+ // Extract from the content by the XPath selectors.
+ libxml_use_internal_errors( true );
+
+ $document = new DOMDocument( '1.0' );
+
+ // Mirror the internal HtmlDriver of the snapshot assertions package.
+ $document->preserveWhiteSpace = false;
+ $document->formatOutput = true;
+
+ // To ignore HTML5 errors.
+ @$document->loadHTML( $this->get_content(), LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+
+ $nodes = ( new \DOMXPath( $document ) )->query( implode( '|', $selectors ) );
+
+ if ( 0 === count( $nodes ) ) {
+ $this->test_case->fail( 'No nodes found for the given XPath selector(s): ' . print_r( $selectors, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
+ }
+
+ $results = [];
+
+ foreach ( $nodes as $node ) {
+ $results[] = $document->saveHTML( $node );
+ }
+
+ $this->test_case->assertMatchesSnapshot( implode( "\n", $results ), new HTML_Driver() );
+
return $this;
}
diff --git a/src/mantle/testing/concerns/trait-with-faker.php b/src/mantle/testing/concerns/trait-with-faker.php
index b90f7d78..33ad6e73 100644
--- a/src/mantle/testing/concerns/trait-with-faker.php
+++ b/src/mantle/testing/concerns/trait-with-faker.php
@@ -9,6 +9,7 @@
use Faker\Generator;
use Faker\Factory;
+use Mantle\Faker\Faker_Provider;
/**
* This trait sets up a faker instance for use in tests.
@@ -44,6 +45,10 @@ protected function make_faker(): Generator {
return $this->app->make( Generator::class, [ 'locale' => $locale ] );
}
- return Factory::create( $locale );
+ $generator = Factory::create( $locale );
+
+ $generator->addProvider( new Faker_Provider( $generator ) );
+
+ return $generator;
}
}
diff --git a/src/mantle/testing/concerns/trait-wordpress-state.php b/src/mantle/testing/concerns/trait-wordpress-state.php
index bdb34666..4b38229a 100644
--- a/src/mantle/testing/concerns/trait-wordpress-state.php
+++ b/src/mantle/testing/concerns/trait-wordpress-state.php
@@ -7,7 +7,11 @@
namespace Mantle\Testing\Concerns;
+use Carbon\Carbon;
+use DateTimeInterface;
+use Mantle\Database\Model\Post;
use Mantle\Testing\Utils;
+use WP_Post;
/**
* This trait includes functionality for controlling WordPress state during
@@ -159,22 +163,27 @@ public function set_permalink_structure( $structure = '' ) {
*
* @global \wpdb $wpdb WordPress database abstraction object.
*
- * @param int $post_id Post ID.
- * @param string $date Post date, in the format YYYY-MM-DD HH:MM:SS.
+ * @param WP_Post|Post|int $post Post ID or post object.
+ * @param DateTimeInterface|string $date Date object or string to update the
+ * post with. If a string is passed it
+ * is assumed to be local timezone.
* @return int|false 1 on success, or false on error.
*/
- protected function update_post_modified( $post_id, $date ) {
+ protected function update_post_modified( WP_Post|Post|int $post, DateTimeInterface|string $date ) {
global $wpdb;
+ $post = is_object( $post ) ? $post->ID : $post;
+ $date = $date instanceof DateTimeInterface ? Carbon::instance( $date ) : Carbon::parse( $date, wp_timezone() );
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$update = $wpdb->update(
$wpdb->posts,
[
- 'post_modified' => $date,
- 'post_modified_gmt' => $date,
+ 'post_modified' => $date->setTimezone( wp_timezone() )->format( 'Y-m-d H:i:s' ),
+ 'post_modified_gmt' => $date->setTimezone( new \DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' ),
],
[
- 'ID' => $post_id,
+ 'ID' => $post,
],
[
'%s',
@@ -185,7 +194,7 @@ protected function update_post_modified( $post_id, $date ) {
]
);
- clean_post_cache( $post_id );
+ clean_post_cache( $post );
return $update;
}
diff --git a/src/mantle/testing/snapshots/class-html-driver.php b/src/mantle/testing/snapshots/class-html-driver.php
new file mode 100644
index 00000000..72dc8378
--- /dev/null
+++ b/src/mantle/testing/snapshots/class-html-driver.php
@@ -0,0 +1,52 @@
+preserveWhiteSpace = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ $document->formatOutput = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ @$document->loadHTML( $data, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+
+ $value = $document->saveHTML();
+
+ // Normalize line endings for cross-platform tests.
+ if ( PHP_OS_FAMILY === 'Windows' ) {
+ $value = implode( "\n", explode( "\r\n", $value ) );
+ }
+
+ return $value;
+ }
+}
diff --git a/src/mantle/testing/wordpress-bootstrap.php b/src/mantle/testing/wordpress-bootstrap.php
index d46f894d..2d3b751d 100644
--- a/src/mantle/testing/wordpress-bootstrap.php
+++ b/src/mantle/testing/wordpress-bootstrap.php
@@ -11,6 +11,8 @@
use function Mantle\Testing\tests_add_filter;
+defined( 'MANTLE_IS_TESTING' ) || define( 'MANTLE_IS_TESTING', true );
+
require_once __DIR__ . '/class-utils.php';
require_once __DIR__ . '/class-wp-die.php';
@@ -156,6 +158,11 @@
}
}
+// Ensure that the shutdown function is registered when installing WordPress.
+if ( $installing_wp ) {
+ Utils::register_shutdown_function();
+}
+
if ( $multisite && ! $installing_wp ) {
Utils::info( 'Running as multisite...' );
defined( 'MULTISITE' ) or define( 'MULTISITE', true );
@@ -174,6 +181,12 @@
// Use the Spy REST Server instead of default.
tests_add_filter( 'wp_rest_server_class', [ Utils::class, 'wp_rest_server_class_filter' ], PHP_INT_MAX );
+// Prevent updating translations asynchronously.
+tests_add_filter( 'async_update_translation', '__return_false' );
+
+// Disable background updates.
+tests_add_filter( 'automatic_updater_disabled', '__return_true' );
+
// Load WordPress.
require_once ABSPATH . '/wp-settings.php';
diff --git a/src/mantle/testkit/composer.json b/src/mantle/testkit/composer.json
index 8a87bf62..5049aa74 100644
--- a/src/mantle/testkit/composer.json
+++ b/src/mantle/testkit/composer.json
@@ -2,7 +2,7 @@
"name": "mantle-framework/testkit",
"description": "The Mantle Framework Teskit Package",
"keywords": ["testing", "mantle"],
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/src/mantle/view/composer.json b/src/mantle/view/composer.json
index 1a7ddb8f..2d53342e 100644
--- a/src/mantle/view/composer.json
+++ b/src/mantle/view/composer.json
@@ -1,7 +1,7 @@
{
"name": "mantle-framework/view",
"description": "The Mantle Framework View Package",
- "type": "project",
+ "type": "library",
"require": {
"php": "^8.0",
"alleyinteractive/composer-wordpress-autoloader": "^1.0",
diff --git a/tests/assets/test-asset-loader.php b/tests/assets/test-asset-loader.php
index 8b2e5ac1..988286c2 100644
--- a/tests/assets/test-asset-loader.php
+++ b/tests/assets/test-asset-loader.php
@@ -84,7 +84,7 @@ public function test_asset_enqueue_from_asset_path() {
$head = $this->get_wp_head();
$this->assertStringContainsString(
- "",
+ '',
$head,
);
diff --git a/tests/assets/test-asset-manager.php b/tests/assets/test-asset-manager.php
index f2d64db8..cd96f32f 100644
--- a/tests/assets/test-asset-manager.php
+++ b/tests/assets/test-asset-manager.php
@@ -12,17 +12,17 @@ public function test_register_script() {
$manager = new Asset_Manager();
$manager
->script(
- 'script-handle',
- 'https://example.org/script.js',
+ "script-handle",
+ "https://example.org/script.js",
[
- 'jquery',
+ "jquery",
],
- 'global',
- 'sync',
+ "global",
+ "sync",
);
$this->assertStringContainsString(
- "",
+ "",
$this->get_wp_head(),
);
}
@@ -31,8 +31,8 @@ public function test_register_style() {
$manager = new Asset_Manager();
$manager
->style(
- 'style-handle',
- 'https://example.org/style.css',
+ "style-handle",
+ "https://example.org/style.css",
[]
);
@@ -46,14 +46,14 @@ public function test_async_script() {
$manager = new Asset_Manager();
$manager
->script(
- 'testsync-script-handle',
- 'https://example.org/example-script.js',
+ "testsync-script-handle",
+ "https://example.org/example-script.js",
);
- $manager->async( 'testsync-script-handle' );
+ $manager->async( "testsync-script-handle" );
$this->assertStringContainsString(
- "",
+ "",
$this->get_wp_head(),
);
}
@@ -61,24 +61,24 @@ public function test_async_script() {
public function test_fluent_script() {
$manager = new Asset_Manager();
$manager
- ->script( 'example-fluent' )
- ->src( 'https://example.org/example-fluent.js' )
+ ->script( "example-fluent" )
+ ->src( "https://example.org/example-fluent.js" )
->async();
$this->assertStringContainsString(
- "",
+ "",
$this->get_wp_head(),
);
}
public function test_fluent_script_helper() {
asset()
- ->script( 'example-helper' )
- ->src( 'https://example.org/example-helper.js' )
+ ->script( "example-helper" )
+ ->src( "https://example.org/example-helper.js" )
->async();
$this->assertStringContainsString(
- "",
+ "",
$this->get_wp_head(),
);
}
@@ -87,21 +87,21 @@ public function test_core_dependency() {
global $wp_scripts;
// Prevent a failing test if this is removed in the future.
- if ( ! isset( $wp_scripts->registered['swfobject'] ) ) {
- $this->markTestSkipped( 'swfobject is not registered in core, should change the dependency tested against' );
+ if ( ! isset( $wp_scripts->registered["swfobject"] ) ) {
+ $this->markTestSkipped( "swfobject is not registered in core, should change the dependency tested against" );
return;
}
- $version = $wp_scripts->registered['swfobject']->ver;
+ $version = $wp_scripts->registered["swfobject"]->ver;
// Get the core version of the asset.
( new Asset_Manager() )
- ->script( 'swfobject' )
+ ->script( "swfobject" )
->version( null )
->async();
$this->assertStringContainsString(
- "",
+ "",
$this->get_wp_head(),
);
}
diff --git a/tests/database/factory/test-factory.php b/tests/database/factory/test-factory.php
index 4d983b6b..67cd6992 100644
--- a/tests/database/factory/test-factory.php
+++ b/tests/database/factory/test-factory.php
@@ -6,11 +6,13 @@
use Mantle\Database\Model;
use Mantle\Testing\Framework_Test_Case;
+/**
+ * @group factory
+ */
class Test_Factory extends Framework_Test_Case {
public function test_create_basic_model() {
$factory = Testable_Post::factory();
- $this->assertInstanceOf( Factory\Factory::class, $factory );
$this->assertInstanceOf( Factory\Post_Factory::class, $factory );
$post = $factory->create_and_get();
@@ -154,7 +156,7 @@ class Testable_Post extends Model\Post {
}
/**
- * @method static Testable_Post_Factory factory()
+ * @method static Testable_Post_Factory factory()
*/
class Testable_Post_With_Factory extends Model\Post {
public static $object_name = 'post';
diff --git a/tests/database/factory/test-unit-testing-factory.php b/tests/database/factory/test-unit-testing-factory.php
index 7c0c3c3a..54cf3b81 100644
--- a/tests/database/factory/test-unit-testing-factory.php
+++ b/tests/database/factory/test-unit-testing-factory.php
@@ -14,6 +14,8 @@
* Test case with the focus of testing the unit testing factory that mirrors
* WordPress core's factories. The factories here should be drop-in replacements
* for core's factories with some sugar on top.
+ *
+ * @group factory
*/
class Test_Unit_Testing_Factory extends Framework_Test_Case {
use With_Faker;
@@ -21,7 +23,7 @@ class Test_Unit_Testing_Factory extends Framework_Test_Case {
public function test_post_factory() {
$this->assertInstanceOf( \WP_Post::class, static::factory()->post->create_and_get() );
- $posts = static::factory()->post->create_many(
+ $post_ids = static::factory()->post->create_many(
10,
[
'post_type' => 'post',
@@ -29,12 +31,12 @@ public function test_post_factory() {
]
);
- $this->assertCount( 10, $posts );
- foreach ( $posts as $post_id ) {
+ $this->assertCount( 10, $post_ids );
+ foreach ( $post_ids as $post_id ) {
$this->assertIsInt( $post_id );
}
- $this->assertEquals( 'draft', get_post_status( array_shift( $posts ) ) );
+ $this->assertEquals( 'draft', get_post_status( array_shift( $post_ids ) ) );
}
public function test_post_create_with_thumbnail() {
@@ -93,6 +95,7 @@ public function test_attachment_factory() {
$this->shim_test( \WP_Post::class, 'attachment' );
$attachment = static::factory()->attachment->create_and_get();
+
$this->assertEquals( 'attachment', get_post_type( $attachment ) );
}
@@ -104,7 +107,6 @@ public function test_term_factory() {
public function test_blog_factory() {
if ( ! is_multisite() ) {
$this->markTestSkipped( 'This test requires multisite.' );
- return;
}
$this->shim_test( \WP_Site::class, 'blog' );
@@ -113,7 +115,6 @@ public function test_blog_factory() {
public function test_network_factory() {
if ( ! is_multisite() ) {
$this->markTestSkipped( 'This test requires multisite.' );
- return;
}
$this->shim_test( \WP_Network::class, 'network' );
@@ -148,7 +149,7 @@ public function test_comment_factory() {
public function test_as_models() {
$post = static::factory()->post->as_models()->create_and_get();
- $term = static::factory()->term->as_models()->with_model( Testable_Post_Tag::class )->create_and_get();
+ $term = static::factory()->term->with_model( Testable_Post_Tag::class )->as_models()->create_and_get();
$this->assertInstanceOf( Post::class, $post );
$this->assertInstanceOf( Testable_Post_Tag::class, $term );
@@ -271,6 +272,23 @@ protected function shim_test( string $class_name, string $property ) {
$this->assertCount( 10, $object_ids );
}
+
+ /**
+ * @dataProvider dataprovider_factory
+ */
+ public function test_dataprovider_factory( $post ) {
+ $this->assertInstanceOf( \WP_Post::class, $post );
+ $this->assertStringContainsString(
+ '