diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b26a9a99 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at https://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..13f3a20e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.github export-ignore +/.php_cs.dist export-ignore +/.styleci.yml export-ignore +/.scrutinizer.yml export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore +/CHANGELOG.md export-ignore +/CONTRIBUTING.md export-ignore +/README.md export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..418be7da --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,42 @@ +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + runs-on: ubuntu-20.04 + strategy: + fail-fast: true + matrix: + php: + - 7.2 + - 7.3 + - 7.4 + - 8.0 + composerFlags: + - '--prefer-lowest' + - '' + + name: PHP ${{ matrix.php }} w/ Composer ${{ matrix.composerFlags }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + composer update --prefer-dist --no-interaction --no-progress ${{ matrix.composerFlags }} + + - name: Execute tests + run: vendor/bin/phpunit --verbose diff --git a/.gitignore b/.gitignore index c4886e2e..7c46776a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/vendor -.DS_Store +.env .phpunit.result.cache composer.lock +phpunit.xml +/vendor diff --git a/_ide_helpers.php b/_ide_helpers.php new file mode 100644 index 00000000..c3b8f827 --- /dev/null +++ b/_ide_helpers.php @@ -0,0 +1,31 @@ + [ + + 'ensure_pages_exist' => true, + + 'page_paths' => [ + + resource_path('js/Pages'), + + ], + + 'page_extensions' => [ + + 'js', + 'svelte', + 'ts', + 'vue', + + ], + + ], + +]; diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index c042b781..00000000 --- a/phpunit.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - tests - - - - - src - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..9e13df70 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + tests + + + + + src/ + + + + diff --git a/readme.md b/readme.md index 6a761416..bb79dcfb 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,18 @@ # Inertia.js Laravel Adapter +

+ + Latest Version + + + Build Status + + StyleCI + + Total Downloads + +

+ +--- + Visit [inertiajs.com](https://inertiajs.com/) to learn more. diff --git a/src/Inertia.php b/src/Inertia.php index 9ba04694..5df60f02 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -10,9 +10,9 @@ * @method static array getShared($key = null) * @method static void version($version) * @method static int|string getVersion() - * @method static \Inertia\Response render($component, $props = []) + * @method static Response render($component, $props = []) * @method static \Illuminate\Http\Response location($url) - * @method static \Inertia\LazyProp lazy(callable $callback) + * @method static LazyProp lazy(callable $callback) * * @see \Inertia\ResponseFactory */ diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 6e8423ec..3ef01c7e 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,16 +2,34 @@ namespace Inertia; +use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Http\Request; use Illuminate\Routing\Router; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider as BaseServiceProvider; +use Illuminate\Testing\TestResponse; +use Illuminate\View\FileViewFinder; +use Inertia\Testing\TestResponseMacros; +use LogicException; class ServiceProvider extends BaseServiceProvider { public function register() { $this->app->singleton(ResponseFactory::class); + + $this->mergeConfigFrom( + __DIR__.'/../config/inertia.php', + 'inertia' + ); + + $this->app->bind('inertia.testing.view-finder', function ($app) { + return new FileViewFinder( + $app['files'], + $app['config']->get('inertia.testing.page_paths'), + $app['config']->get('inertia.testing.page_extensions') + ); + }); } public function boot() @@ -20,6 +38,11 @@ public function boot() $this->registerConsoleCommands(); $this->registerRequestMacro(); $this->registerRouterMacro(); + $this->registerTestingMacros(); + + $this->publishes([ + __DIR__.'/../config/inertia.php' => config_path('inertia.php'), + ]); } protected function registerBladeDirective() @@ -55,4 +78,22 @@ protected function registerRouterMacro() ->defaults('props', $props); }); } + + protected function registerTestingMacros() + { + if (class_exists(TestResponse::class)) { + TestResponse::mixin(new TestResponseMacros()); + + return; + } + + // Laravel <= 6.0 + if (class_exists(LegacyTestResponse::class)) { + LegacyTestResponse::mixin(new TestResponseMacros()); + + return; + } + + throw new LogicException('Could not detect TestResponse class.'); + } } diff --git a/src/Testing/Assert.php b/src/Testing/Assert.php new file mode 100644 index 00000000..ffafcdd1 --- /dev/null +++ b/src/Testing/Assert.php @@ -0,0 +1,85 @@ +path = $path; + + $this->component = $component; + $this->props = $props; + $this->url = $url; + $this->version = $version; + } + + protected function dotPath($key): string + { + if (is_null($this->path)) { + return $key; + } + + return implode('.', [$this->path, $key]); + } + + protected function scope($key, Closure $callback): self + { + $props = $this->prop($key); + $path = $this->dotPath($key); + + PHPUnit::assertIsArray($props, sprintf('Inertia property [%s] is not scopeable.', $path)); + + $scope = new self($this->component, $props, $this->url, $this->version, $path); + $callback($scope); + $scope->interacted(); + + return $this; + } + + public static function fromTestResponse($response): self + { + try { + $response->assertViewHas('page'); + $page = json_decode(json_encode($response->viewData('page')), true); + + PHPUnit::assertIsArray($page); + PHPUnit::assertArrayHasKey('component', $page); + PHPUnit::assertArrayHasKey('props', $page); + PHPUnit::assertArrayHasKey('url', $page); + PHPUnit::assertArrayHasKey('version', $page); + } catch (AssertionFailedError $e) { + PHPUnit::fail('Not a valid Inertia response.'); + } + + return new self($page['component'], $page['props'], $page['url'], $page['version']); + } +} diff --git a/src/Testing/Concerns/Debugging.php b/src/Testing/Concerns/Debugging.php new file mode 100644 index 00000000..612374de --- /dev/null +++ b/src/Testing/Concerns/Debugging.php @@ -0,0 +1,20 @@ +prop($prop)); + + return $this; + } + + public function dd(string $prop = null): void + { + dd($this->prop($prop)); + } + + abstract protected function prop(string $key = null); +} diff --git a/src/Testing/Concerns/Has.php b/src/Testing/Concerns/Has.php new file mode 100644 index 00000000..7c3867e4 --- /dev/null +++ b/src/Testing/Concerns/Has.php @@ -0,0 +1,113 @@ +prop($key), + sprintf('Inertia property [%s] does not have the expected size.', $this->dotPath($key)) + ); + + return $this; + } + + public function hasAll($key): self + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $prop => $count) { + if (is_int($prop)) { + $this->has($count); + } else { + $this->has($prop, $count); + } + } + + return $this; + } + + public function has(string $key, $value = null, Closure $scope = null): self + { + PHPUnit::assertTrue( + Arr::has($this->prop(), $key), + sprintf('Inertia property [%s] does not exist.', $this->dotPath($key)) + ); + + $this->interactsWith($key); + + if (is_int($value) && ! is_null($scope)) { + $path = $this->dotPath($key); + + $prop = $this->prop($key); + if ($prop instanceof Collection) { + $prop = $prop->all(); + } + + PHPUnit::assertTrue($value > 0, sprintf('Cannot scope directly onto the first entry of property [%s] when asserting that it has a size of 0.', $path)); + PHPUnit::assertIsArray($prop, sprintf('Direct scoping is currently unsupported for non-array like properties such as [%s].', $path)); + $this->count($key, $value); + + return $this->scope($key.'.'.array_keys($prop)[0], $scope); + } + + if (is_callable($value)) { + $this->scope($key, $value); + } elseif (! is_null($value)) { + $this->count($key, $value); + } + + return $this; + } + + public function missingAll($key): self + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $prop) { + $this->misses($prop); + } + + return $this; + } + + public function missing(string $key): self + { + $this->interactsWith($key); + + PHPUnit::assertNotTrue( + Arr::has($this->prop(), $key), + sprintf('Inertia property [%s] was found while it was expected to be missing.', $this->dotPath($key)) + ); + + return $this; + } + + public function missesAll($key): self + { + return $this->missingAll( + is_array($key) ? $key : func_get_args() + ); + } + + public function misses(string $key): self + { + return $this->missing($key); + } + + abstract protected function prop(string $key = null); + + abstract protected function dotPath($key): string; + + abstract protected function interactsWith(string $key): void; + + abstract protected function scope($key, Closure $callback); +} diff --git a/src/Testing/Concerns/Interaction.php b/src/Testing/Concerns/Interaction.php new file mode 100644 index 00000000..506d64d2 --- /dev/null +++ b/src/Testing/Concerns/Interaction.php @@ -0,0 +1,41 @@ +interacted, true)) { + $this->interacted[] = $prop; + } + } + + public function interacted(): void + { + PHPUnit::assertSame( + [], + array_diff(array_keys($this->prop()), $this->interacted), + $this->path + ? sprintf('Unexpected Inertia properties were found in scope [%s].', $this->path) + : 'Unexpected Inertia properties were found on the root level.' + ); + } + + public function etc(): self + { + $this->interacted = array_keys($this->prop()); + + return $this; + } + + abstract protected function prop(string $key = null); +} diff --git a/src/Testing/Concerns/Matching.php b/src/Testing/Concerns/Matching.php new file mode 100644 index 00000000..8e77cccf --- /dev/null +++ b/src/Testing/Concerns/Matching.php @@ -0,0 +1,73 @@ + $value) { + $this->where($key, $value); + } + + return $this; + } + + public function where($key, $expected): self + { + $this->has($key); + + $actual = $this->prop($key); + + if ($expected instanceof Closure) { + PHPUnit::assertTrue( + $expected(is_array($actual) ? Collection::make($actual) : $actual), + sprintf('Inertia property [%s] was marked as invalid using a closure.', $this->dotPath($key)) + ); + + return $this; + } + + if ($expected instanceof Arrayable) { + $expected = $expected->toArray(); + } elseif ($expected instanceof Responsable) { + $expected = json_decode(json_encode($expected->toResponse(request())->getData()), true); + } + + $this->ensureSorted($expected); + $this->ensureSorted($actual); + + PHPUnit::assertSame( + $expected, + $actual, + sprintf('Inertia property [%s] does not match the expected value.', $this->dotPath($key)) + ); + + return $this; + } + + protected function ensureSorted(&$value): void + { + if (! is_array($value)) { + return; + } + + foreach ($value as &$arg) { + $this->ensureSorted($arg); + } + + ksort($value); + } + + abstract protected function dotPath($key): string; + + abstract protected function prop(string $key = null); + + abstract public function has(string $key, $value = null, Closure $scope = null); +} diff --git a/src/Testing/Concerns/PageObject.php b/src/Testing/Concerns/PageObject.php new file mode 100644 index 00000000..efcdf766 --- /dev/null +++ b/src/Testing/Concerns/PageObject.php @@ -0,0 +1,54 @@ +component, 'Unexpected Inertia page component.'); + + if ($shouldExist || (is_null($shouldExist) && config('inertia.testing.ensure_pages_exist', true))) { + try { + app('inertia.testing.view-finder')->find($value); + } catch (InvalidArgumentException $exception) { + PHPUnit::fail(sprintf('Inertia page component file [%s] does not exist.', $value)); + } + } + + return $this; + } + + protected function prop(string $key = null) + { + return Arr::get($this->props, $key); + } + + public function url(string $value): self + { + PHPUnit::assertSame($value, $this->url, 'Unexpected Inertia page url.'); + + return $this; + } + + public function version($value): self + { + PHPUnit::assertSame($value, $this->version, 'Unexpected Inertia asset version.'); + + return $this; + } + + public function toArray(): array + { + return [ + 'component' => $this->component, + 'props' => $this->props, + 'url' => $this->url, + 'version' => $this->version, + ]; + } +} diff --git a/src/Testing/TestResponseMacros.php b/src/Testing/TestResponseMacros.php new file mode 100644 index 00000000..59064d90 --- /dev/null +++ b/src/Testing/TestResponseMacros.php @@ -0,0 +1,30 @@ +toArray(); + }; + } +} diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 62b8a751..951d920d 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -134,9 +134,13 @@ public function test_lazy_resource_response() $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('User/Index', $page->component); - $this->assertSame(json_encode($expected), json_encode($page->props->users)); $this->assertSame('/users?page=1', $page->url); $this->assertSame('123', $page->version); + tap($page->props->users, function ($users) use ($expected) { + $this->assertSame(json_encode($expected['data']), json_encode($users->data)); + $this->assertSame(json_encode($expected['links']), json_encode($users->links)); + $this->assertSame('/', $users->meta->path); + }); } public function test_arrayable_prop_response() diff --git a/tests/Stubs/ExamplePage.vue b/tests/Stubs/ExamplePage.vue new file mode 100644 index 00000000..27492271 --- /dev/null +++ b/tests/Stubs/ExamplePage.vue @@ -0,0 +1,3 @@ + diff --git a/tests/TestCase.php b/tests/TestCase.php index aa4dccc7..4679888e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,20 +2,59 @@ namespace Inertia\Tests; +use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Support\Facades\View; +use Illuminate\Testing\TestResponse; +use Inertia\Inertia; +use Inertia\ServiceProvider; +use LogicException; use Orchestra\Testbench\TestCase as Orchestra; abstract class TestCase extends Orchestra { protected function getPackageProviders($app) { - return ['Inertia\ServiceProvider']; + return [ + ServiceProvider::class, + ]; } public function setUp(): void { parent::setUp(); - View::addLocation(__DIR__.'/stubs'); + View::addLocation(__DIR__.'/Stubs'); + + Inertia::setRootView('welcome'); + config()->set('inertia.testing.ensure_pages_exist', false); + config()->set('inertia.testing.page_paths', [realpath(__DIR__)]); + } + + /** + * @return string + * @throws LogicException + */ + protected function getTestResponseClass(): string + { + // Laravel >= 7.0 + if (class_exists(TestResponse::class)) { + return TestResponse::class; + } + + // Laravel <= 6.0 + if (class_exists(LegacyTestResponse::class)) { + return LegacyTestResponse::class; + } + + throw new LogicException('Could not detect TestResponse class.'); + } + + protected function makeMockRequest($view) + { + app('router')->get('/example-url', function () use ($view) { + return $view; + }); + + return $this->get('/example-url'); } } diff --git a/tests/Testing/AssertTest.php b/tests/Testing/AssertTest.php new file mode 100644 index 00000000..745b4bfd --- /dev/null +++ b/tests/Testing/AssertTest.php @@ -0,0 +1,1281 @@ +makeMockRequest( + Inertia::render('foo') + ); + + $response->assertInertia(); + } + + /** @test */ + public function the_view_is_not_served_by_inertia(): void + { + $response = $this->makeMockRequest(view('welcome')); + $response->assertOk(); // Make sure we can render the built-in Orchestra 'welcome' view.. + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Not a valid Inertia response.'); + + $response->assertInertia(); + } + + /** @test */ + public function the_component_matches(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('foo'); + }); + } + + /** @test */ + public function the_component_does_not_match(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia page component.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('bar'); + }); + } + + /** @test */ + public function the_component_exists_on_the_filesystem(): void + { + $response = $this->makeMockRequest( + Inertia::render('Stubs/ExamplePage') + ); + + config()->set('inertia.testing.ensure_pages_exist', true); + $response->assertInertia(function (Assert $inertia) { + $inertia->component('Stubs/ExamplePage'); + }); + } + + /** @test */ + public function the_component_does_not_exist_on_the_filesystem(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + config()->set('inertia.testing.ensure_pages_exist', true); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia page component file [foo] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('foo'); + }); + } + + /** @test */ + public function it_can_force_enable_the_component_file_existence(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + config()->set('inertia.testing.ensure_pages_exist', false); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia page component file [foo] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('foo', true); + }); + } + + /** @test */ + public function it_can_force_disable_the_component_file_existence_check(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + config()->set('inertia.testing.ensure_pages_exist', true); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('foo', false); + }); + } + + /** @test */ + public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_exist_relative_to_any_of_the_given_paths(): void + { + $response = $this->makeMockRequest( + Inertia::render('fixtures/ExamplePage') + ); + + config()->set('inertia.testing.ensure_pages_exist', true); + config()->set('inertia.testing.page_paths', [realpath(__DIR__)]); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia page component file [fixtures/ExamplePage] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('fixtures/ExamplePage'); + }); + } + + /** @test */ + public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_have_one_of_the_configured_extensions(): void + { + $response = $this->makeMockRequest( + Inertia::render('fixtures/ExamplePage') + ); + + config()->set('inertia.testing.ensure_pages_exist', true); + config()->set('inertia.testing.page_extensions', ['bin', 'exe', 'svg']); + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia page component file [fixtures/ExamplePage] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->component('fixtures/ExamplePage'); + }); + } + + /** @test */ + public function it_has_a_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'prop' => 'value', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('prop'); + }); + } + + /** @test */ + public function it_does_not_have_a_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'value', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [prop] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('prop'); + }); + } + + /** @test */ + public function it_has_a_nested_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'example' => [ + 'nested' => 'nested-value', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('example.nested'); + }); + } + + /** @test */ + public function it_does_not_have_a_nested_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'example' => [ + 'nested' => 'nested-value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [example.another] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('example.another'); + }); + } + + /** @test */ + public function it_can_count_the_amount_of_items_in_a_given_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 2); + }); + } + + /** @test */ + public function it_fails_counting_when_the_amount_of_items_in_a_given_prop_does_not_match(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not have the expected size.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 1); + }); + } + + /** @test */ + public function it_cannot_count_the_amount_of_items_in_a_given_prop_when_the_prop_does_not_exist(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('baz', 2); + }); + } + + /** @test */ + public function it_fails_when_the_second_argument_of_the_has_assertion_is_an_unsupported_type(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'baz', + ]) + ); + + $this->expectException(TypeError::class); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 'invalid'); + }); + } + + /** @test */ + public function it_asserts_that_a_prop_is_missing(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'bar' => true, + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->missing('foo.baz'); + }); + } + + /** @test */ + public function it_asserts_that_a_prop_is_missing_using_the_misses_method(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'bar' => true, + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->misses('foo.baz'); + }); + } + + /** @test */ + public function it_fails_asserting_that_a_prop_is_missing_when_it_exists_using_the_misses_method(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'prop' => 'value', + 'foo' => [ + 'bar' => true, + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [foo.bar] was found while it was expected to be missing.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('prop') + ->misses('foo.bar'); + }); + } + + /** @test */ + public function it_fails_asserting_that_a_prop_is_missing_when_it_exists(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'prop' => 'value', + 'foo' => [ + 'bar' => true, + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [foo.bar] was found while it was expected to be missing.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('prop') + ->missing('foo.bar'); + }); + } + + /** @test */ + public function it_can_assert_that_multiple_props_are_missing(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'baz' => 'foo', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('baz') + ->missingAll([ + 'foo', + 'bar', + ]); + }); + } + + /** @test */ + public function it_cannot_assert_that_multiple_props_are_missing_when_at_least_one_exists(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'baz' => 'example', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] was found while it was expected to be missing.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('foo') + ->missingAll([ + 'bar', + 'baz', + ]); + }); + } + + /** @test */ + public function it_can_use_arguments_instead_of_an_array_to_assert_that_it_is_missing_multiple_props(): void + { + $this->makeMockRequest( + Inertia::render('foo', [ + 'baz' => 'foo', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->has('baz')->missingAll('foo', 'bar'); + }); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] was found while it was expected to be missing.'); + + $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'baz' => 'example', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->has('foo')->missingAll('bar', 'baz'); + }); + } + + /** @test */ + public function it_can_assert_that_multiple_props_are_missing_using_the_misses_all_method(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'baz' => 'foo', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('baz') + ->missesAll([ + 'foo', + 'bar', + ]); + }); + } + + /** @test */ + public function it_cannot_assert_that_multiple_props_are_missing_when_at_least_one_exists_using_the_misses_all_method(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'baz' => 'example', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] was found while it was expected to be missing.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('foo') + ->missesAll([ + 'bar', + 'baz', + ]); + }); + } + + /** @test */ + public function it_can_use_arguments_instead_of_an_array_to_assert_that_it_is_missing_multiple_props_using_the_misses_all_method(): void + { + $this->makeMockRequest( + Inertia::render('foo', [ + 'baz' => 'foo', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->has('baz')->missesAll('foo', 'bar'); + }); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] was found while it was expected to be missing.'); + + $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'baz' => 'example', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->has('foo')->missesAll('bar', 'baz'); + }); + } + + /** @test */ + public function the_prop_matches_a_value(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'value', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', 'value'); + }); + } + + /** @test */ + public function the_prop_does_not_match_a_value(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'value', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not match the expected value.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', 'invalid'); + }); + } + + /** @test */ + public function the_prop_does_not_match_a_value_when_it_does_not_exist(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'value', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('baz', null); + }); + } + + /** @test */ + public function the_prop_does_not_match_loosely(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 1, + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not match the expected value.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', true); + }); + } + + /** @test */ + public function the_prop_matches_a_value_using_a_closure(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'baz', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', function ($value) { + return $value === 'baz'; + }); + }); + } + + /** @test */ + public function the_prop_does_not_match_a_value_using_a_closure(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'baz', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] was marked as invalid using a closure.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', function ($value) { + return $value === 'invalid'; + }); + }); + } + + /** @test */ + public function array_props_will_be_automatically_cast_to_collections_when_using_a_closure(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'foo', + 'example' => 'value', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('bar', function ($value) { + $this->assertInstanceOf(Collection::class, $value); + + return $value->count() === 2; + }); + }); + } + + /** @test */ + public function the_prop_matches_a_value_using_an_arrayable(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => $user, + ]) + ); + + $response->assertInertia(function (Assert $inertia) use ($user) { + $inertia->where('bar', $user); + }); + } + + /** @test */ + public function the_prop_does_not_match_a_value_using_an_arrayable(): void + { + Model::unguard(); + $userA = User::make(['name' => 'Example']); + $userB = User::make(['name' => 'Another']); + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => $userA, + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not match the expected value.'); + + $response->assertInertia(function (Assert $inertia) use ($userB) { + $inertia->where('bar', $userB); + }); + } + + /** @test */ + public function the_prop_matches_a_value_using_an_arrayable_even_when_they_are_sorted_differently(): void + { + // https://github.com/claudiodekker/inertia-laravel-testing/issues/30 + Model::unguard(); + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => User::make(['id' => 1, 'name' => 'Example']), + 'baz' => [ + 'id' => 1, + 'name' => 'Nayeli Hermiston', + 'email' => 'vroberts@example.org', + 'email_verified_at' => '2021-01-22T10:34:42.000000Z', + 'created_at' => '2021-01-22T10:34:42.000000Z', + 'updated_at' => '2021-01-22T10:34:42.000000Z', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->where('bar', User::make(['name' => 'Example', 'id' => 1])) + ->where('baz', [ + 'name' => 'Nayeli Hermiston', + 'email' => 'vroberts@example.org', + 'id' => 1, + 'email_verified_at' => '2021-01-22T10:34:42.000000Z', + 'updated_at' => '2021-01-22T10:34:42.000000Z', + 'created_at' => '2021-01-22T10:34:42.000000Z', + ]); + }); + } + + /** @test */ + public function the_prop_matches_a_value_using_a_responsable(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $resource = JsonResource::collection(new Collection([$user, $user])); + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => $resource, + ]) + ); + + $response->assertInertia(function (Assert $inertia) use ($resource) { + $inertia->where('bar', $resource); + }); + } + + /** @test */ + public function the_prop_does_not_match_a_value_using_a_responsable(): void + { + Model::unguard(); + $resourceA = JsonResource::make(User::make(['name' => 'Another'])); + $resourceB = JsonResource::make(User::make(['name' => 'Example'])); + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => $resourceA, + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not match the expected value.'); + + $response->assertInertia(function (Assert $inertia) use ($resourceB) { + $inertia->where('bar', $resourceB); + }); + } + + /** @test */ + public function the_nested_prop_matches_a_value(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'example' => [ + 'nested' => 'nested-value', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('example.nested', 'nested-value'); + }); + } + + /** @test */ + public function the_nested_prop_does_not_match_a_value(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'example' => [ + 'nested' => 'nested-value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [example.nested] does not match the expected value.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->where('example.nested', 'another-value'); + }); + } + + /** @test */ + public function it_can_scope_the_assertion_query(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $called = false; + $response->assertInertia(function (Assert $inertia) use (&$called) { + $inertia->has('bar', function (Assert $inertia) use (&$called) { + $called = true; + $inertia + ->where('baz', 'example') + ->where('prop', 'value'); + }); + }); + + $this->assertTrue($called, 'The scoped query was never actually called.'); + } + + /** @test */ + public function it_cannot_scope_the_assertion_query_when_the_scoped_prop_does_not_exist(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('baz', function (Assert $inertia) { + $inertia->where('baz', 'example'); + }); + }); + } + + /** @test */ + public function it_cannot_scope_the_assertion_query_when_the_scoped_prop_is_a_single_value(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => 'value', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] is not scopeable.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', function (Assert $inertia) { + // + }); + }); + } + + /** @test */ + public function it_can_scope_on_complex_objects_responsable(): void + { + Model::unguard(); + $userA = User::make(['name' => 'Example']); + $userB = User::make(['name' => 'Another']); + + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'example' => JsonResource::make([$userA, $userB]), + ]) + ); + + $called = false; + $response->assertInertia(function (Assert $inertia) use (&$called) { + return $inertia->has('example', function (Assert $inertia) use (&$called) { + $inertia->has('data', 2); + + $called = true; + }); + }); + + $this->assertTrue($called, 'The scoped query was never actually called.'); + } + + /** @test */ + public function it_can_directly_scope_onto_the_first_item_when_asserting_that_a_prop_has_a_length_greater_than_zero(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + ['key' => 'first'], + ['key' => 'second'], + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 2, function (Assert $inertia) { + $inertia->where('key', 'first'); + }); + }); + } + + /** @test */ + public function it_cannot_directly_scope_onto_the_first_item_when_asserting_that_a_prop_has_a_length_of_zero(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + ['key' => 'first'], + ['key' => 'second'], + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Cannot scope directly onto the first entry of property [bar] when asserting that it has a size of 0.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 0, function (Assert $inertia) { + $inertia->where('key', 'first'); + }); + }); + } + + /** @test */ + public function it_cannot_directly_scope_onto_the_first_item_when_it_does_not_match_the_expected_size(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + ['key' => 'first'], + ['key' => 'second'], + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [bar] does not have the expected size.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', 1, function (Assert $inertia) { + $inertia->where('key', 'first'); + }); + }); + } + + /** @test */ + public function it_fails_when_it_does_not_interact_with_all_props_in_the_scope_at_least_once(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'baz' => 'example', + 'prop' => 'value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia properties were found in scope [bar].'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('bar', function (Assert $inertia) { + $inertia->where('baz', 'example'); + }); + }); + } + + /** @test */ + public function it_can_disable_the_interaction_check_for_the_current_scope(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => true, + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->etc(); + }); + } + + /** @test */ + public function it_cannot_disable_the_interaction_check_for_any_other_scopes(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => true, + 'baz' => [ + 'foo' => 'bar', + 'example' => 'value', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia properties were found in scope [baz].'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->etc() + ->has('baz', function (Assert $inertia) { + $inertia->where('foo', 'bar'); + }); + }); + } + + /** @test */ + public function it_does_not_fail_when_not_interacting_with_every_top_level_prop(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'bar' => 'baz', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->has('foo'); + }); + } + + /** @test */ + public function it_fails_when_not_interacting_with_every_top_level_prop_while_the_interacted_flag_is_set(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'bar' => 'baz', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia properties were found on the root level.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia + ->has('foo') + ->interacted(); + }); + } + + /** @test */ + public function it_can_assert_that_multiple_props_match_their_expected_values_at_once(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $resource = JsonResource::make(User::make(['name' => 'Another'])); + + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'user' => $user, + 'resource' => $resource, + ], + 'bar' => 'baz', + ]) + ); + + $response->assertInertia(function (Assert $inertia) use ($user, $resource) { + $inertia->whereAll([ + 'foo.user' => $user, + 'foo.resource' => $resource, + 'bar' => function ($value) { + return $value === 'baz'; + }, + ]); + }); + } + + /** @test */ + public function it_cannot_assert_that_multiple_props_match_their_expected_values_when_at_least_one_does_not(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => 'bar', + 'baz' => 'example', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] was marked as invalid using a closure.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->whereAll([ + 'foo' => 'bar', + 'baz' => function ($value) { + return $value === 'foo'; + }, + ]); + }); + } + + /** @test */ + public function it_can_assert_that_it_has_multiple_props(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $resource = JsonResource::make(User::make(['name' => 'Another'])); + + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'user' => $user, + 'resource' => $resource, + ], + 'bar' => 'baz', + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->hasAll([ + 'foo.user', + 'foo.resource', + 'bar', + ]); + }); + } + + /** @test */ + public function it_cannot_assert_that_it_has_multiple_props_when_at_least_one_is_missing(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $resource = JsonResource::make(User::make(['name' => 'Another'])); + + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'user' => $user, + 'resource' => $resource, + ], + 'bar' => 'baz', + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->hasAll([ + 'foo.user', + 'baz', + ]); + }); + } + + /** @test */ + public function it_can_use_arguments_instead_of_an_array_to_assert_that_it_has_multiple_props(): void + { + Model::unguard(); + $user = User::make(['name' => 'Example']); + $resource = JsonResource::make(User::make(['name' => 'Another'])); + + $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'user' => $user, + 'resource' => $resource, + ], + 'bar' => 'baz', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->hasAll('foo.user', 'foo.resource', 'bar'); + }); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $this->makeMockRequest( + Inertia::render('foo', [ + 'foo' => [ + 'user' => $user, + 'resource' => $resource, + ], + 'bar' => 'baz', + ]) + )->assertInertia(function (Assert $inertia) { + $inertia->hasAll('foo.user', 'baz'); + }); + } + + /** @test */ + public function it_can_count_multiple_props_at_once(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'key' => 'value', + 'prop' => 'example', + ], + 'baz' => [ + 'another' => 'value', + ], + ]) + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->hasAll([ + 'bar' => 2, + 'baz' => 1, + ]); + }); + } + + /** @test */ + public function it_cannot_count_multiple_props_at_once_when_at_least_one_is_missing(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', [ + 'bar' => [ + 'key' => 'value', + 'prop' => 'example', + ], + ]) + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Inertia property [baz] does not exist.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->hasAll([ + 'bar' => 2, + 'baz' => 1, + ]); + }); + } + + /** @test */ + public function the_page_url_matches(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->url('/example-url'); + }); + } + + /** @test */ + public function the_page_url_does_not_match(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia page url.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->url('/invalid-page'); + }); + } + + /** @test */ + public function the_asset_version_matches(): void + { + Inertia::version('example-version'); + + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $response->assertInertia(function (Assert $inertia) { + $inertia->version('example-version'); + }); + } + + /** @test */ + public function the_asset_version_does_not_match(): void + { + Inertia::version('example-version'); + + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected Inertia asset version.'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->version('different-version'); + }); + } + + /** @test */ + public function it_is_macroable(): void + { + Assert::macro('myCustomMacro', function () { + throw new Exception('My Custom Macro was called!'); + }); + + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('My Custom Macro was called!'); + + $response->assertInertia(function (Assert $inertia) { + $inertia->myCustomMacro(); + }); + } +} diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php new file mode 100644 index 00000000..dc44f18b --- /dev/null +++ b/tests/Testing/TestResponseMacrosTest.php @@ -0,0 +1,54 @@ +makeMockRequest( + Inertia::render('foo') + ); + + $success = false; + $response->assertInertia(function ($page) use (&$success) { + $this->assertInstanceOf(Assert::class, $page); + $success = true; + }); + + $this->assertTrue($success); + } + + /** @test */ + public function it_preserves_the_ability_to_continue_chaining_laravel_test_response_calls(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo') + ); + + $this->assertInstanceOf( + $this->getTestResponseClass(), + $response->assertInertia() + ); + } + + /** @test */ + public function it_can_retrieve_the_inertia_page(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', ['bar' => 'baz']) + ); + + tap($response->inertiaPage(), function (array $page) { + $this->assertSame('foo', $page['component']); + $this->assertSame(['bar' => 'baz'], $page['props']); + $this->assertSame('/example-url', $page['url']); + $this->assertSame('', $page['version']); + }); + } +}