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
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
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 @@
+
+ This is an example Inertia page component.
+
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']);
+ });
+ }
+}