diff --git a/composer.json b/composer.json index afd0e62..43bf524 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ ], "require": { "php": "^8.0", - "laravel/framework": "^11.0|^10.0|^9.0|^8.0|^12.0" + "laravel/framework": "^11.0|^12.0|^13.0" }, "autoload-dev": { "psr-4": { @@ -23,8 +23,8 @@ } }, "require-dev": { - "orchestra/testbench": "^9.0|^8.0|^10.0", - "phpunit/phpunit": "^10.0|^11.5.3" + "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", + "phpunit/phpunit": "^10.0|^11.5.3|^12.0" }, "extra": { "laravel": { diff --git a/tests/Feature/FilterableByMultipleFieldInRelationTest.php b/tests/Feature/FilterableByMultipleFieldInRelationTest.php index f5eb4b5..040fa85 100644 --- a/tests/Feature/FilterableByMultipleFieldInRelationTest.php +++ b/tests/Feature/FilterableByMultipleFieldInRelationTest.php @@ -2,6 +2,7 @@ namespace Refinephp\LaravelRefine\Tests\Feature; +use PHPUnit\Framework\Attributes\Test; use Refinephp\LaravelRefine\Tests\App\Models\Author; use Refinephp\LaravelRefine\Tests\TestCase; @@ -67,8 +68,7 @@ public function setUp(): void 'description' => 'The Silmarillion is a collection of mythopoeic works by English writer J. R. R. Tolkien.', ]); } - - /** @test */ + #[Test] public function it_can_filter_by_multiple_fields_in_relation(): void { $originalSilentMode = $this->app['config']->get('laravel-refine.silent'); diff --git a/tests/Feature/FilterableTest.php b/tests/Feature/FilterableTest.php index 6fe0ab2..8f11de0 100644 --- a/tests/Feature/FilterableTest.php +++ b/tests/Feature/FilterableTest.php @@ -4,6 +4,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\Product; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertEquals; @@ -25,8 +26,7 @@ public function setUp(): void 'title' => 'laravel refine is the best', ]); } - - /** @test */ + #[Test] public function it_can_process_a_basic_request_without_any_filter(): void { $response = $this->getJson('/posts'); @@ -34,8 +34,7 @@ public function it_can_process_a_basic_request_without_any_filter(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_process_a_request_without_any_matches(): void { $response = $this->getJson('/posts?filters[title][$eq]=no matches'); @@ -43,8 +42,7 @@ public function it_can_process_a_request_without_any_matches(): void $response->assertOk(); $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_with_a_basic_eq_operator(): void { $response = $this->getJson('/posts?filters[title][$eq]=laravel refine is the best'); @@ -52,8 +50,7 @@ public function it_can_filter_with_a_basic_eq_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_process_with_grouped_filters() { $post = Post::create(['title' => 'title']) @@ -65,8 +62,7 @@ public function it_can_process_with_grouped_filters() assertEquals('title', $response->json()[0]['title']); } - - /** @test */ + #[Test] public function it_can_filter_by_decimal_values_with_results() { $product = Product::factory()->create([ @@ -79,8 +75,7 @@ public function it_can_filter_by_decimal_values_with_results() assertEquals($product->id, $response->json()[0]['id']); } - - /** @test */ + #[Test] public function it_can_filter_by_decimal_values_without_matches() { $product = Product::factory()->create([ @@ -91,8 +86,7 @@ public function it_can_filter_by_decimal_values_without_matches() ->assertOk() ->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_by_float_values_with_results() { $product = Product::factory()->create([ @@ -105,8 +99,7 @@ public function it_can_filter_by_float_values_with_results() assertEquals($product->id, $response->json()[0]['id']); } - - /** @test */ + #[Test] public function it_can_filter_by_float_values_without_matches() { $product = Product::factory()->create([ @@ -117,8 +110,7 @@ public function it_can_filter_by_float_values_without_matches() ->assertOk() ->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_by_boolean_values_with_results() { $product = Product::factory()->create([ @@ -131,8 +123,7 @@ public function it_can_filter_by_boolean_values_with_results() assertEquals($product->id, $response->json()[0]['id']); } - - /** @test */ + #[Test] public function it_can_filter_by_timestamp_values_without_matches() { $product = Product::factory()->create(); @@ -143,8 +134,7 @@ public function it_can_filter_by_timestamp_values_without_matches() ->assertOk() ->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_by_timestamp_values_with_results() { $product = Product::factory()->create(); @@ -157,8 +147,7 @@ public function it_can_filter_by_timestamp_values_with_results() assertEquals($product->id, $response->json()[0]['id']); } - - /** @test */ + #[Test] public function it_can_filter_by_boolean_values_without_matches() { $product = Product::factory()->create([ @@ -169,8 +158,7 @@ public function it_can_filter_by_boolean_values_without_matches() ->assertOk() ->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_with_eqc_operator(): void { $response = $this->getJson('/posts?filters[title][$eqc]=LARAVEL refine is the best'); @@ -178,8 +166,7 @@ public function it_can_filter_with_eqc_operator(): void $response->assertOk(); $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_with_ne_operator(): void { $response = $this->getJson('/posts?filters[title][$ne]=no matches'); @@ -187,8 +174,7 @@ public function it_can_filter_with_ne_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_lt_operator(): void { $product = Product::factory()->create([ @@ -199,8 +185,7 @@ public function it_can_filter_with_lt_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_lte_operator(): void { $product = Product::factory()->create([ @@ -211,8 +196,7 @@ public function it_can_filter_with_lte_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_gt_operator(): void { $product = Product::factory()->create([ @@ -223,8 +207,7 @@ public function it_can_filter_with_gt_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_gte_operator(): void { $product = Product::factory()->create([ @@ -235,8 +218,7 @@ public function it_can_filter_with_gte_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_in_operator(): void { Product::factory()->create([ @@ -255,8 +237,7 @@ public function it_can_filter_with_in_operator(): void ->assertOk() ->assertJsonCount(2); } - - /** @test */ + #[Test] public function it_can_filter_with_notIn_operator(): void { $product = Product::factory()->create([ @@ -267,8 +248,7 @@ public function it_can_filter_with_notIn_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_contains_operator(): void { $response = $this->getJson('/posts?filters[title][$contains]=laravel'); @@ -276,8 +256,7 @@ public function it_can_filter_with_contains_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_notContains_operator(): void { $response = $this->getJson('/posts?filters[title][$notContains]=complexity'); @@ -285,16 +264,14 @@ public function it_can_filter_with_notContains_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_containsc_operator(): void { $response = $this->getJson('/posts?filters[title][$containsc]=laravel'); $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_notContainsc_operator(): void { $response = $this->getJson('/posts?filters[title][$notContainsc]=complexity'); @@ -302,8 +279,7 @@ public function it_can_filter_with_notContainsc_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_null_operator(): void { $product = Product::factory()->create([ @@ -314,8 +290,7 @@ public function it_can_filter_with_null_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_notNull_operator(): void { $product = Product::factory()->create([ @@ -326,8 +301,7 @@ public function it_can_filter_with_notNull_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_between_operator(): void { $product = Product::factory()->create([ @@ -338,8 +312,7 @@ public function it_can_filter_with_between_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_notBetween_operator(): void { $product = Product::factory()->create([ @@ -350,8 +323,7 @@ public function it_can_filter_with_notBetween_operator(): void ->assertOk() ->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_startsWith_operator(): void { $response = $this->getJson('/posts?filters[title][$startsWith]=laravel'); @@ -359,8 +331,7 @@ public function it_can_filter_with_startsWith_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_startsWithc_operator(): void { $response = $this->getJson('/posts?filters[title][$startsWithc]=laravel'); @@ -368,8 +339,7 @@ public function it_can_filter_with_startsWithc_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_endsWith_operator(): void { $response = $this->getJson('/posts?filters[title][$endsWith]=best'); @@ -377,8 +347,7 @@ public function it_can_filter_with_endsWith_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_endsWithc_operator(): void { $response = $this->getJson('/posts?filters[title][$endsWithc]=best'); @@ -386,8 +355,7 @@ public function it_can_filter_with_endsWithc_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_with_or_operator(): void { $post1 = Post::create(['title' => 'laravel refine']); @@ -399,8 +367,7 @@ public function it_can_filter_with_or_operator(): void ->assertOk() ->assertJsonCount(2); } - - /** @test */ + #[Test] public function it_can_filter_with_and_operator(): void { $post = Product::factory()->create([ diff --git a/tests/Feature/FilterableWithCustomResolverTest.php b/tests/Feature/FilterableWithCustomResolverTest.php index 3fdb938..fa372be 100644 --- a/tests/Feature/FilterableWithCustomResolverTest.php +++ b/tests/Feature/FilterableWithCustomResolverTest.php @@ -4,6 +4,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\Tag; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertEquals; @@ -34,8 +35,7 @@ public function setUp(): void 'name' => 'refine_tag', ]); } - - /** @test */ + #[Test] public function it_can_process_a_basic_request_without_any_filter(): void { $response = $this->getJson('/tags'); @@ -43,8 +43,7 @@ public function it_can_process_a_basic_request_without_any_filter(): void $response->assertOk(); $response->assertJsonCount(3); } - - /** @test */ + #[Test] public function it_can_process_a_request_without_any_matches(): void { $response = $this->getJson('/tags?filters[name][$eq]=nothing'); @@ -52,8 +51,7 @@ public function it_can_process_a_request_without_any_matches(): void $response->assertOk(); $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_filter_with_a_basic_eq_operator(): void { $response = $this->getJson('/tags?filters[name][$eq]=laravel'); @@ -61,8 +59,7 @@ public function it_can_filter_with_a_basic_eq_operator(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_process_custom_operator1(): void { $response = $this->getJson('/tags?filters[name][$refine]=true'); @@ -70,8 +67,7 @@ public function it_can_process_custom_operator1(): void $response->assertOk(); $response->assertJsonCount(1); // since we have 1 tag with name 'refine_tag' } - - /** @test */ + #[Test] public function it_can_process_with_grouped_filters() { $post = Post::query()->create(['title' => 'title']); diff --git a/tests/Feature/IncludeableTest.php b/tests/Feature/IncludeableTest.php index bfc7960..6742143 100644 --- a/tests/Feature/IncludeableTest.php +++ b/tests/Feature/IncludeableTest.php @@ -3,6 +3,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\Post; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertTrue; class IncludeableTest extends TestCase @@ -31,8 +32,7 @@ public function setUp(): void return Post::WithIncludes()->get(); }); } - - /** @test */ + #[Test] public function it_includes_specified_relationships(): void { $response = $this->getJson("/posts?includes=comment"); diff --git a/tests/Feature/RelationFilterTest.php b/tests/Feature/RelationFilterTest.php index fc810da..485856a 100644 --- a/tests/Feature/RelationFilterTest.php +++ b/tests/Feature/RelationFilterTest.php @@ -5,6 +5,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\User; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; class RelationFilterTest extends TestCase { @@ -24,8 +25,7 @@ public function setUp(): void 'title' => 'laravel refine is the best', ]); } - - /** @test */ + #[Test] public function it_can_filter_by_has_many_relation(): void { $post = Post::first(); @@ -43,8 +43,7 @@ public function it_can_filter_by_has_many_relation(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_by_belongs_to_relation(): void { $user = User::create([ @@ -61,8 +60,7 @@ public function it_can_filter_by_belongs_to_relation(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_by_belongs_to_many_relation(): void { $post = Post::first(); @@ -76,8 +74,7 @@ public function it_can_filter_by_belongs_to_many_relation(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_filter_by_has_one_relation(): void { $product = Product::factory([ diff --git a/tests/Feature/RenameFilterableFieldsTest.php b/tests/Feature/RenameFilterableFieldsTest.php index 6a1bfc5..e0bebd0 100644 --- a/tests/Feature/RenameFilterableFieldsTest.php +++ b/tests/Feature/RenameFilterableFieldsTest.php @@ -4,18 +4,19 @@ use Refinephp\LaravelRefine\Tests\App\Models\Post; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertEquals; class RenameFilterableFieldsTest extends TestCase { + #[Test] public function it_can_return_relevant_table_column(): void { $post = new Post(); assertEquals('title', $post->getField('title')); } - - /** @test */ + #[Test] public function it_can_return_relevant_table_column_after_renamed(): void { $post = new Post(); @@ -23,8 +24,7 @@ public function it_can_return_relevant_table_column_after_renamed(): void assertEquals('title', $post->getField('post_title')); } - - /** @test */ + #[Test] public function it_can_process_without_renamed_fields(): void { Post::create([ @@ -41,8 +41,7 @@ public function it_can_process_without_renamed_fields(): void // It returns o records as builder level filters take priority $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_process_with_renamed_fields(): void { $post = new Post(); @@ -65,8 +64,7 @@ public function it_can_process_with_renamed_fields(): void // It returns o records as builder level filters take priority $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function it_can_overwrite_rename_filter_fields_in_eloquent_builder(): void { $post = new Post(); @@ -90,8 +88,7 @@ public function it_can_overwrite_rename_filter_fields_in_eloquent_builder(): voi // It returns o records as builder level filters take priority $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function available_filter_fields_work_with_renamed_filter_fields(): void { $book = new Book(); @@ -116,8 +113,7 @@ public function available_filter_fields_work_with_renamed_filter_fields(): void $response->assertOk(); $response->assertJsonCount(1); } - - /** @test */ + #[Test] public function rename_filter_fields_works_when_filter_fields_not_set(): void { $post = new Post(); diff --git a/tests/Feature/RestrictFilterableFieldsTest.php b/tests/Feature/RestrictFilterableFieldsTest.php index d39af17..a966b55 100644 --- a/tests/Feature/RestrictFilterableFieldsTest.php +++ b/tests/Feature/RestrictFilterableFieldsTest.php @@ -3,6 +3,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\Post; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertEquals; use function PHPUnit\Framework\assertStringContainsString; @@ -10,7 +11,7 @@ class RestrictFilterableFieldsTest extends TestCase { - /** @test */ + #[Test] public function it_return_all_available_fields_when_filter_fields_not_defined(): void { $post = new Post(); @@ -22,8 +23,7 @@ public function it_return_all_available_fields_when_filter_fields_not_defined(): assertTrue(in_array($field, $availableFields)); } } - - /** @test */ + #[Test] public function it_return_all_available_fields_when_filter_fields_defined(): void { $post = new Post(); @@ -43,8 +43,7 @@ public function it_return_all_available_fields_when_filter_fields_defined(): voi 'rank', ], $availableFields); } - - /** @test */ + #[Test] public function it_can_return_restricted_filters_when_defined_inside_filter_fields() { $post = new Post(); @@ -63,8 +62,7 @@ public function it_can_return_restricted_filters_when_defined_inside_filter_fiel 'rank' => ['$eq'], ], $restrictedFilters); } - - /** @test */ + #[Test] public function it_gives_priority_to_restricted_filters_property_when_defined_to_return_restricted_filters() { $post = new Post(); @@ -90,8 +88,7 @@ public function it_gives_priority_to_restricted_filters_property_when_defined_to 'rank' => ['$eq', 'lt'], ], $restrictedFilters); } - - /** @test */ + #[Test] public function it_return_empty_array_when_no_restricted_property_is_defined() { $post = new Post(); @@ -106,8 +103,7 @@ public function it_return_empty_array_when_no_restricted_property_is_defined() assertEquals([], $restrictedFilters); } - - /** @test */ + #[Test] public function it_can_process_a_request_without_restricted_filters(): void { Route::get('/posts', function () { @@ -123,8 +119,7 @@ public function it_can_process_a_request_without_restricted_filters(): void $response->assertOk(); $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_process_when_a_valid_filter_is_applied(): void { $post = new Post(); @@ -143,8 +138,7 @@ public function it_can_process_when_a_valid_filter_is_applied(): void $response->assertOk(); $response->assertJsonCount(0); } - - /** @test */ + #[Test] public function it_can_avoid_when_invalid_filter_is_applied_in_silent_mode(): void { $originalSilentMode = $this->app['config']->get('laravel-refine.silent'); @@ -169,8 +163,7 @@ public function it_can_avoid_when_invalid_filter_is_applied_in_silent_mode(): vo $this->app['config']->set('laravel-refine.silent', $originalSilentMode); } - - /** @test */ + #[Test] public function it_throws_error_when_invalid_filter_is_applied_in_non_silent_mode(): void { $originalSilentMode = $this->app['config']->get('laravel-refine.silent'); @@ -198,8 +191,7 @@ public function it_throws_error_when_invalid_filter_is_applied_in_non_silent_mod $this->app['config']->set('laravel-refine.silent', $originalSilentMode); } - - /** @test */ + #[Test] public function it_can_set_restricted_filters_on_eloquent_builder(): void { $post = new Post(); diff --git a/tests/Feature/SortableTest.php b/tests/Feature/SortableTest.php index 5c1c9ed..b1d8882 100644 --- a/tests/Feature/SortableTest.php +++ b/tests/Feature/SortableTest.php @@ -7,6 +7,7 @@ use Refinephp\LaravelRefine\Tests\App\Models\User; use Refinephp\LaravelRefine\Tests\TestCase; use Illuminate\Support\Facades\Route; +use PHPUnit\Framework\Attributes\Test; use function PHPUnit\Framework\assertEquals; use function PHPUnit\Framework\assertTrue; @@ -28,8 +29,7 @@ public function setUp(): void $this->post = $post; } - - /** @test */ + #[Test] public function it_can_sort(): void { $response = $this->getJson('/posts?sort=title:asc'); @@ -37,8 +37,7 @@ public function it_can_sort(): void $response->assertOk(); } - - /** @test */ + #[Test] public function it_can_sort_limit_to_certain_fields(): void { $this->post->sortFields = ['title']; @@ -49,8 +48,7 @@ public function it_can_sort_limit_to_certain_fields(): void $response->assertOk(); } - - /** @test */ + #[Test] public function it_can_sort_with_renamed_fields(): void { $this->post->sortFields = ['title' => 'post_title']; @@ -61,12 +59,8 @@ public function it_can_sort_with_renamed_fields(): void $response->assertOk(); } - - /** - *@test - * - *@dataProvider directionProvider - */ + #[Test] + #[PHPUnit\Framework\Attributes\DataProvider('directionProvider')] public function it_can_sort_null_values_last($direction): void { config(['laravel-refine.null_last' => true]); @@ -87,12 +81,8 @@ public function it_can_sort_null_values_last($direction): void assertEquals(['b', 'a', null, null], $response->collect()->pluck('title')->all()); } } - - /** - *@test - * - *@dataProvider directionProvider - */ + #[Test] + #[PHPUnit\Framework\Attributes\DataProvider('directionProvider')] public function it_can_sort_by_belongs_to_relationship(string $direction) { $this->truncateAll(); @@ -124,12 +114,8 @@ public function it_can_sort_by_belongs_to_relationship(string $direction) assertEquals(['post3', 'post2', 'post1'], $response->collect()->pluck('title')->all()); } } - - /** - *@test - * - *@dataProvider directionProvider - */ + #[Test] + #[PHPUnit\Framework\Attributes\DataProvider('directionProvider')] public function it_can_sort_by_has_one_relationship(string $direction) { $this->truncateAll(); @@ -161,12 +147,8 @@ public function it_can_sort_by_has_one_relationship(string $direction) assertEquals(['user3', 'user2', 'user1'], $response->collect()->pluck('name')->all()); } } - - /** - *@test - * - *@dataProvider directionProvider - */ + #[Test] + #[PHPUnit\Framework\Attributes\DataProvider('directionProvider')] public function it_can_sort_by_has_many_relationship(string $direction) { $this->truncateAll(); @@ -198,12 +180,8 @@ public function it_can_sort_by_has_many_relationship(string $direction) assertEquals(['user3', 'user2', 'user1'], $response->collect()->pluck('name')->all()); } } - - /** - *@test - * - *@dataProvider directionProvider - */ + #[Test] + #[PHPUnit\Framework\Attributes\DataProvider('directionProvider')] public function it_can_sort_by_belongs_to_many_relationship(string $direction) { $this->truncateAll();