diff --git a/CHANGELOG.md b/CHANGELOG.md index c5aeb79e..baba7c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Post factories that are passed a term slug to `with_terms()` will create the + term if it doesn't exist by default. This can be disabled by calling the + `create_terms()` method on the factory or replacing the with terms method call + with `with_terms_only_existing()`. + ### Fixed - Ensure that the `delete()` method of the HTTP Client doesn't set a body by default. diff --git a/src/mantle/database/factory/class-post-factory.php b/src/mantle/database/factory/class-post-factory.php index 072b89c8..0c700f2c 100644 --- a/src/mantle/database/factory/class-post-factory.php +++ b/src/mantle/database/factory/class-post-factory.php @@ -36,6 +36,11 @@ class Post_Factory extends Factory { */ protected string $model = Post::class; + /** + * Flag to create terms by default. + */ + protected bool $create_terms = true; + /** * Constructor. * @@ -46,9 +51,21 @@ public function __construct( Generator $faker, public string $post_type = 'post' parent::__construct( $faker ); } + /** + * Change the default creation of terms with the post factory. + * + * @param bool $value Value to set. + */ + public function create_terms( bool $value = true ): void { + $this->create_terms = $value; + } + /** * Create a new factory instance to create posts with a set of terms. * + * Any slugs passed that are not found will be created. If you want to + * only use existing terms, use `with_terms_only_existing()`. + * * @param array>|\WP_Term|int|string ...$terms Terms to assign to the post. */ public function with_terms( ...$terms ): static { @@ -60,7 +77,26 @@ public function with_terms( ...$terms ): static { $terms = collect( $terms )->all(); return $this->with_middleware( - fn ( array $args, Closure $next ) => $next( $args )->set_terms( $terms ), + fn ( array $args, Closure $next ) => $next( $args )->set_terms( $terms, append: true, create: $this->create_terms ), + ); + } + + /** + * Create a new factory instance to create posts with a set of terms without creating + * any unknown terms. + * + * @param array>|\WP_Term|int|string ...$terms Terms to assign to the post. + */ + public function with_terms_only_existing( ...$terms ): static { + // Handle an array in the first argument. + if ( 1 === count( $terms ) && isset( $terms[0] ) && is_array( $terms[0] ) ) { + $terms = $terms[0]; + } + + $terms = collect( $terms )->all(); + + return $this->with_middleware( + fn ( array $args, Closure $next ) => $next( $args )->set_terms( $terms, append: true, create: false ), ); } diff --git a/src/mantle/database/model/term/trait-model-term.php b/src/mantle/database/model/term/trait-model-term.php index e86a294c..d4ca11d0 100644 --- a/src/mantle/database/model/term/trait-model-term.php +++ b/src/mantle/database/model/term/trait-model-term.php @@ -12,6 +12,7 @@ use Mantle\Database\Model\Term; use Mantle\Support\Arr; use Mantle\Support\Collection; +use Mantle\Support\Str; use WP_Term; use function Mantle\Support\Helpers\collect; @@ -120,18 +121,19 @@ public function get_terms( string $taxonomy ): array { * @param mixed $terms Accepts an array of or a single instance of terms. * @param string $taxonomy Taxonomy name, optional. * @param bool $append Append to the object's terms, defaults to false. + * @param bool $create Create the term if it does not exist, defaults to false. * @return static * * @throws Model_Exception Thrown if the $taxonomy cannot be inferred from $terms. * @throws Model_Exception Thrown if error saving the post's terms. */ - public function set_terms( $terms, ?string $taxonomy = null, bool $append = false ) { + public function set_terms( $terms, ?string $taxonomy = null, bool $append = false, bool $create = false ) { $terms = collect( Arr::wrap( $terms ) ); // If taxonomy is not specified, chunk the terms into taxonomy groups. if ( ! $taxonomy ) { $terms = $terms->reduce( - function ( array $carry, $term, $index ): array { + function ( array $carry, $term, $index ) use ( $create ): array { if ( $term instanceof WP_Term ) { $carry[ $term->taxonomy ][] = $term; @@ -177,10 +179,21 @@ function ( array $carry, $term, $index ): array { continue; } - $item = get_term_object_by( 'slug', $item, $taxonomy ); + $term = get_term_object_by( 'slug', $item, $taxonomy ); - if ( $item ) { - $carry[ $taxonomy ][] = $item; + // Optionally create the term if it does not exist. + if ( ! $term && $create ) { + $term = wp_insert_term( Str::headline( $item ), $taxonomy, [ 'slug' => $item ] ); + + if ( is_wp_error( $term ) ) { + throw new Model_Exception( "Error creating term: [{$term->get_error_message()}]" ); + } + + $term = get_term( $term['term_id'], $taxonomy ); + } + + if ( $term instanceof WP_Term ) { + $carry[ $taxonomy ][] = $term; } } diff --git a/tests/Database/Factory/UnitTestingFactoryTest.php b/tests/Database/Factory/UnitTestingFactoryTest.php index d09d9a9b..efc67c79 100644 --- a/tests/Database/Factory/UnitTestingFactoryTest.php +++ b/tests/Database/Factory/UnitTestingFactoryTest.php @@ -280,8 +280,8 @@ public function test_posts_with_multiple_terms_spread_array_argument() { ); } - #[Group( 'with_terms' )] #[DataProvider( 'slug_id_dataprovider' )] + #[Group( 'with_terms' )] public function test_posts_with_multiple_terms_single_array( string $field ) { $tags = collect( static::factory()->tag->create_many( 5 ) ) ->map( fn ( $term_id ) => get_term( $term_id ) ) @@ -300,7 +300,14 @@ public function test_posts_with_multiple_terms_single_array( string $field ) { #[Group( 'with_terms' )] public function test_posts_with_terms_create_unknown_term() { - $this->markTestIncomplete( 'This test is incomplete.' ); + $post = static::factory()->post->with_terms( [ + 'post_tag' => [ 'unknown-term' ], + ] )->create_and_get(); + + $post_tags = get_the_terms( $post, 'post_tag' ); + + $this->assertCount( 1, $post_tags ); + $this->assertEquals( 'unknown-term', $post_tags[0]->slug ); } public function test_terms_with_posts() {