diff --git a/README.md b/README.md index 3097669..5a93489 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ return new InertiaConfig( /** * Version resolver, if you use vite for example you probably want to use the default here, * or you can add a custom one to maybe get from enviroment variables etc. + * + * default path: public/build/manifest.json */ - versionResolverClass: ManifestVersionResolver::class, + versionResolver: new ManifestVersionResolver(), /** * Props that should be included in "all" requests, the default is errors and the authenticated user */ diff --git a/composer.lock b/composer.lock index 2f82667..26f4466 100644 --- a/composer.lock +++ b/composer.lock @@ -314,7 +314,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.x" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -397,45 +397,37 @@ }, { "name": "giggsey/libphonenumber-for-php", - "version": "dev-master", + "version": "8.13.55", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "18f14d9488f6b9d700f9056c2552cdacd04ff21c" + "reference": "6e28b3d53cf96d7f41c83d9b80b6021ecbd00537" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/18f14d9488f6b9d700f9056c2552cdacd04ff21c", - "reference": "18f14d9488f6b9d700f9056c2552cdacd04ff21c", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/6e28b3d53cf96d7f41c83d9b80b6021ecbd00537", + "reference": "6e28b3d53cf96d7f41c83d9b80b6021ecbd00537", "shasum": "" }, "require": { - "giggsey/locale": "^2.7", - "php": "^8.1", - "symfony/polyfill-mbstring": "^1.31" + "giggsey/locale": "^2.0", + "php": "^7.4|^8.0", + "symfony/polyfill-mbstring": "^1.17" }, "replace": { "giggsey/libphonenumber-for-php-lite": "self.version" }, "require-dev": { - "ext-dom": "*", - "friendsofphp/php-cs-fixer": "^3.71", - "infection/infection": "^0.28.0", - "pear/pear-core-minimal": "^1.10.16", - "pear/pear_exception": "^1.0.2", + "friendsofphp/php-cs-fixer": "^3.64", + "pear/pear-core-minimal": "^1.10", + "pear/pear_exception": "^1.0", "pear/versioncontrol_git": "^0.7", - "phing/phing": "^3.0.1", - "php-coveralls/php-coveralls": "^2.7", - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.7", - "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpstan/phpstan-phpunit": "^2.0.4", - "phpstan/phpstan-strict-rules": "^2.0.3", - "phpunit/phpunit": "^10.5.45", - "symfony/console": "^6.4.17", - "symfony/var-exporter": "^6.4.19" + "phing/phing": "^3.0", + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/console": "^v5.2", + "symfony/var-exporter": "^5.2" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -464,7 +456,7 @@ "homepage": "https://giggsey.com/" } ], - "description": "A library for parsing, formatting, storing and validating international phone numbers, a PHP Port of Google's libphonenumber.", + "description": "PHP Port of Google's libphonenumber", "homepage": "https://github.com/giggsey/libphonenumber-for-php", "keywords": [ "geocoding", @@ -478,39 +470,39 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "source": "https://github.com/giggsey/libphonenumber-for-php" }, - "time": "2025-03-13T17:46:18+00:00" + "time": "2025-02-14T08:14:08+00:00" }, { "name": "giggsey/locale", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/giggsey/Locale.git", - "reference": "a5c65ea3c2630f27ccb78977990eefbee6dd8f97" + "reference": "1cd8b3ad2d43e04f4c2c6a240495af44780f809b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/Locale/zipball/a5c65ea3c2630f27ccb78977990eefbee6dd8f97", - "reference": "a5c65ea3c2630f27ccb78977990eefbee6dd8f97", + "url": "https://api.github.com/repos/giggsey/Locale/zipball/1cd8b3ad2d43e04f4c2c6a240495af44780f809b", + "reference": "1cd8b3ad2d43e04f4c2c6a240495af44780f809b", "shasum": "" }, "require": { - "php": "^7.4|^8.0" + "php": "^8.1" }, "require-dev": { "ext-json": "*", - "friendsofphp/php-cs-fixer": "^3.64", - "pear/pear-core-minimal": "^1.9", + "friendsofphp/php-cs-fixer": "^3.66", + "pear/pear-core-minimal": "^1.10", "pear/pear_exception": "^1.0", "pear/versioncontrol_git": "^0.5", - "phing/phing": "^2.7", - "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "^8.5|^9.5", - "symfony/console": "^5.0|^6.0", - "symfony/filesystem": "^5.0|^6.0", - "symfony/finder": "^5.0|^6.0", - "symfony/process": "^5.0|^6.0", - "symfony/var-exporter": "^5.2|^6.0" + "phing/phing": "^2.17.4", + "php-coveralls/php-coveralls": "^2.7", + "phpunit/phpunit": "^10.5.45", + "symfony/console": "^6.4", + "symfony/filesystem": "6.4", + "symfony/finder": "^6.4", + "symfony/process": "^6.4", + "symfony/var-exporter": "^6.4" }, "type": "library", "autoload": { @@ -532,9 +524,9 @@ "description": "Locale functions required by libphonenumber-for-php", "support": { "issues": "https://github.com/giggsey/Locale/issues", - "source": "https://github.com/giggsey/Locale/tree/2.7.0" + "source": "https://github.com/giggsey/Locale/tree/2.8.0" }, - "time": "2024-11-04T11:18:07+00:00" + "time": "2025-03-20T14:25:27+00:00" }, { "name": "graham-campbell/result-type", @@ -1022,12 +1014,12 @@ "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "467e764fb6ebf5c931ba3edffda938ff755cad19" + "reference": "2b8777dfb48a01321fa1532254f7603a6d4ceebe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/467e764fb6ebf5c931ba3edffda938ff755cad19", - "reference": "467e764fb6ebf5c931ba3edffda938ff755cad19", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/2b8777dfb48a01321fa1532254f7603a6d4ceebe", + "reference": "2b8777dfb48a01321fa1532254f7603a6d4ceebe", "shasum": "" }, "require": { @@ -1118,7 +1110,7 @@ "type": "tidelift" } ], - "time": "2025-03-16T13:04:36+00:00" + "time": "2025-03-20T09:27:50+00:00" }, { "name": "nette/php-generator", @@ -3166,12 +3158,12 @@ "source": { "type": "git", "url": "https://github.com/tempestphp/tempest-framework.git", - "reference": "f5e38d062aa18a0df77874bec914c13b44dfb33e" + "reference": "0b2597511b02a5804a30bc5bb10a231a92f4fb72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tempestphp/tempest-framework/zipball/f5e38d062aa18a0df77874bec914c13b44dfb33e", - "reference": "f5e38d062aa18a0df77874bec914c13b44dfb33e", + "url": "https://api.github.com/repos/tempestphp/tempest-framework/zipball/0b2597511b02a5804a30bc5bb10a231a92f4fb72", + "reference": "0b2597511b02a5804a30bc5bb10a231a92f4fb72", "shasum": "" }, "require": { @@ -3206,7 +3198,8 @@ "symfony/var-dumper": "^7.1", "symfony/var-exporter": "^7.1", "tempest/highlight": "^2.11.2", - "vlucas/phpdotenv": "^5.6" + "vlucas/phpdotenv": "^5.6", + "voku/portable-ascii": "^2.0.3" }, "replace": { "tempest/auth": "self.version", @@ -3324,7 +3317,7 @@ "type": "github" } ], - "time": "2025-03-18T12:06:45+00:00" + "time": "2025-03-20T21:18:59+00:00" }, { "name": "tempest/highlight", @@ -3463,6 +3456,80 @@ } ], "time": "2024-07-20T21:52:34+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" } ], "packages-dev": [ @@ -3975,12 +4042,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3bbb8d54b3a6718e51fd48cd478079f5f49b82bd" + "reference": "9d6046153c2893b521784069e6b5249ce7d2acae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3bbb8d54b3a6718e51fd48cd478079f5f49b82bd", - "reference": "3bbb8d54b3a6718e51fd48cd478079f5f49b82bd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9d6046153c2893b521784069e6b5249ce7d2acae", + "reference": "9d6046153c2893b521784069e6b5249ce7d2acae", "shasum": "" }, "require": { @@ -4000,7 +4067,7 @@ "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.2", + "sebastian/code-unit": "^3.0.3", "sebastian/comparator": "^6.3.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", @@ -4052,7 +4119,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5" }, "funding": [ { @@ -4068,7 +4135,7 @@ "type": "tidelift" } ], - "time": "2025-03-18T13:41:57+00:00" + "time": "2025-03-19T13:45:48+00:00" }, { "name": "sebastian/cli-parser", @@ -4133,12 +4200,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1f56f951e080c0353ee13ed8e5a8e900b9e7a6b7" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1f56f951e080c0353ee13ed8e5a8e900b9e7a6b7", - "reference": "1f56f951e080c0353ee13ed8e5a8e900b9e7a6b7", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { @@ -4175,7 +4242,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/main" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -4183,7 +4250,7 @@ "type": "github" } ], - "time": "2025-01-01T09:30:51+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", diff --git a/src/Contracts/InertiaVersionResolver.php b/src/Contracts/InertiaVersionResolver.php index 335fafe..fcca9b3 100644 --- a/src/Contracts/InertiaVersionResolver.php +++ b/src/Contracts/InertiaVersionResolver.php @@ -4,7 +4,9 @@ namespace NeoIsRecursive\Inertia\Contracts; +use Tempest\Container\Container; + interface InertiaVersionResolver { - public function resolve(): string; + public function resolve(Container $container): string; } diff --git a/src/Http/InertiaResponse.php b/src/Http/InertiaResponse.php index 47dfb13..08fa929 100644 --- a/src/Http/InertiaResponse.php +++ b/src/Http/InertiaResponse.php @@ -5,10 +5,10 @@ use Closure; use NeoIsRecursive\Inertia\Contracts\MergeableProp; use NeoIsRecursive\Inertia\Props\AlwaysProp; +use NeoIsRecursive\Inertia\Props\DeferProp; use NeoIsRecursive\Inertia\Props\LazyProp; use NeoIsRecursive\Inertia\Support\Header; use NeoIsRecursive\Inertia\Views\InertiaBaseView; -use Tempest\Http\Status; use Tempest\Router\IsResponse; use Tempest\Router\Request; use Tempest\Router\Response; @@ -22,132 +22,173 @@ final class InertiaResponse implements Response use IsResponse; public function __construct( - Request $request, - string $page, - array $props, - string $rootView, - string $version, + readonly Request $request, + readonly string $page, + readonly array $props, + readonly string $rootView, + readonly string $version, ) { - $alwaysProps = $this->resolveAlwaysProps(props: $props); - $partialProps = $this->resolvePartialProps(request: $request, component: $page, props: $props); - $mergeProps = $this->resolveMergeProps($props, $request); - - $props = $this->evaluateProps( - props: array_merge( - $alwaysProps, - $partialProps, - $mergeProps - ), + $deferredProps = self::resolvePropKeysThatShouldDefer( + props: $props, request: $request, - unpackDotProps: true + component: $page ); - $page = [ - 'component' => $page, - 'props' => $props, - 'url' => $request->uri, - 'version' => $version, - 'mergeProps' => array_keys($mergeProps), - ]; + $mergeProps = self::resolvePropKeysThatShouldMerge( + props: $props, + request: $request + ); + // Build page data immutably + $pageData = array_merge( + [ + 'component' => $page, + 'props' => self::composeProps( + props: $this->props, + request: $this->request, + component: $page + ), + 'url' => $request->uri, + 'version' => $version, + ], + count($deferredProps) ? ['deferredProps' => $deferredProps] : [], + count($mergeProps) ? ['mergeProps' => $mergeProps] : [] + ); - if ($request->headers->offsetExists(Header::INERTIA) && $request->headers[Header::INERTIA] == 'true') { - $this->status = Status::OK; + $isInertia = ($request->headers->offsetExists(Header::INERTIA) + && $request->headers[Header::INERTIA] === 'true'); - $this->body = $page; + $this->body = $isInertia + ? (function () use ($pageData) { + // side effect to set Inertia header + $this->headers[Header::INERTIA] = 'true'; - $this->addHeader(Header::INERTIA, 'true'); - return; - } + return $pageData; + })() + : new InertiaBaseView( + view: $rootView, + pageData: $pageData + ); + } - $this->body = new InertiaBaseView( - view: $rootView, - pageData: $page, - ); + public static function isPartial(Request $request, string $component): bool + { + return ($request->headers[Header::PARTIAL_COMPONENT] ?? null) === $component; } - private function resolveAlwaysProps(array $props): array + /** + * @pure + * Composes the various prop transformations into one functional pipeline. + */ + private static function composeProps(array $props, Request $request, string $component): array { - $always = array_filter($props, static function ($prop) { - return $prop instanceof AlwaysProp; - }); + $always = static::resolveAlwaysProps($props); + $partial = static::resolvePartialProps($request, $component, $props); - return $always; + return static::evaluateProps( + array_merge($always, $partial), + $request, + true + ); } - private function resolvePartialProps(Request $request, string $component, array $props): array + /** + * @pure + * function to extract AlwaysProp instances. + */ + private static function resolveAlwaysProps(array $props): array { - $headers = $request->headers; - - $partialHeader = $headers[Header::PARTIAL_COMPONENT] ?? null; + return array_filter($props, static fn($prop) => $prop instanceof AlwaysProp); + } - $isPartial = $partialHeader === $component; + /** + * @pure + * function to extract Partial props based on request headers. + */ + private static function resolvePartialProps(Request $request, string $component, array $props): array + { + $headers = $request->headers; - if (! $isPartial) { - return array_filter($props, static function ($prop) { - return ! ($prop instanceof LazyProp); - }); + if (!static::isPartial($request, $component)) { + return array_filter($props, static fn($prop) => !($prop instanceof LazyProp || $prop instanceof DeferProp)); } $only = array_filter(explode(',', $headers[Header::PARTIAL_ONLY] ?? '')); $except = array_filter(explode(',', $headers[Header::PARTIAL_EXCEPT] ?? '')); - $props = $only ? array_intersect_key($props, array_flip($only)) : $props; + $filtered = $only + ? array_intersect_key($props, array_flip($only)) + : $props; + + return array_filter( + $filtered, + static fn($key) => !in_array($key, $except, true), + ARRAY_FILTER_USE_KEY + ); + } + + private static function resolvePropKeysThatShouldDefer(array $props, Request $request, string $component): array + { - if (count($except) > 0) { - foreach ($except as $key) { - unset($props[$key]); - } + if (static::isPartial($request, $component)) { + return []; } - return $props; + return arr($props) + ->filter(function ($prop) { + return $prop instanceof DeferProp; + }) + ->map(fn(DeferProp $prop, string $key) => [ + 'group' => $prop->group, + 'key' => $key, + ]) + ->groupBy(fn(array $prop) => $prop['group']) + ->map(fn(array $group) => arr($group)->pluck('key')->toArray()) + ->toArray(); } - public static function resolveMergeProps(array $props, Request $request): array + private static function resolvePropKeysThatShouldMerge(array $props, Request $request): array { $resetProps = arr(explode(',', $request->headers[Header::RESET] ?? '')); - $mergeProps = arr($props) + return arr($props) ->filter(fn($prop) => $prop instanceof MergeableProp && $prop->shouldMerge) - ->filter( - fn($_, $key) => ! $resetProps->contains($key) - ) - ->keys(); - - return $mergeProps->toArray(); + ->filter(fn($_, $key) => !$resetProps->contains($key)) + ->keys() + ->toArray(); } - public static function evaluateProps(array $props, Request $request, bool $unpackDotProps = true): array + /** + * @pure + * Evaluates props recursively. + */ + private static function evaluateProps(array $props, Request $request, bool $unpackDotProps = true): array { - foreach ($props as $key => $value) { - if ($value instanceof Closure) { - $value = invoke($value); - } - - if ($value instanceof LazyProp) { - $value = $value(); - } - - if ($value instanceof AlwaysProp) { - $value = $value(); - } - - if ($value instanceof ImmutableArray) { - $value = $value->toArray(); - } - - if (is_array($value)) { - $value = self::evaluateProps($value, $request, false); - } - - if ($unpackDotProps && str_contains($key, '.')) { - $props = arr($props)->set($key, $value)->toArray(); - unset($props[$key]); - } else { - $props[$key] = $value; - } - } - - return $props; + return arr($props) + ->map(function ($value, string|int $key) use ($request) { + $evaluated = $value instanceof Closure ? invoke($value) : $value; + $evaluated = ($evaluated instanceof LazyProp || $evaluated instanceof AlwaysProp) + ? $evaluated() + : $evaluated; + $evaluated = $evaluated instanceof ImmutableArray + ? $evaluated->toArray() + : $evaluated; + $evaluated = is_array($evaluated) + ? self::evaluateProps($evaluated, $request, false) + : $evaluated; + + return [$key, $evaluated]; + }) + ->reduce( + function (array $acc, array $item) use ($unpackDotProps) { + [$key, $value] = $item; + if ($unpackDotProps && str_contains($key, '.')) { + return arr($acc)->set($key, $value)->toArray(); + } + $acc[$key] = $value; + return $acc; + }, + [] + ); } } diff --git a/src/Inertia.php b/src/Inertia.php index aff2324..f09a1a3 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -33,7 +33,7 @@ public function flushShared(): void } public string $version { - get => $this->container->get($this->config->versionResolverClass)->resolve(); + get => $this->container->invoke($this->config->versionResolver->resolve(...)); } diff --git a/src/InertiaConfig.php b/src/InertiaConfig.php index df83fd1..01ba351 100644 --- a/src/InertiaConfig.php +++ b/src/InertiaConfig.php @@ -3,6 +3,7 @@ namespace NeoIsRecursive\Inertia; use Closure; +use NeoIsRecursive\Inertia\Contracts\InertiaVersionResolver; use NeoIsRecursive\Inertia\Props\AlwaysProp; use NeoIsRecursive\Inertia\Props\LazyProp; @@ -12,7 +13,7 @@ final class InertiaConfig public function __construct( readonly public string $rootView, /** @var class-string */ - readonly public string $versionResolverClass = ManifestVersionResolver::class, + readonly public InertiaVersionResolver $versionResolver = new ManifestVersionResolver(), /** @var array */ public private(set) array $sharedProps = [], ) {} diff --git a/src/ManifestVersionResolver.php b/src/ManifestVersionResolver.php index 5a2dd5f..8db2ab2 100644 --- a/src/ManifestVersionResolver.php +++ b/src/ManifestVersionResolver.php @@ -3,6 +3,7 @@ namespace NeoIsRecursive\Inertia; use NeoIsRecursive\Inertia\Contracts\InertiaVersionResolver; +use Tempest\Container\Container; use function Tempest\root_path; @@ -10,7 +11,7 @@ { public function __construct(public ?string $manifestPath = null) {} - public function resolve(): string + public function resolve(Container $container): string { $manifestPath = $this->manifestPath ?? root_path('/public/build/manifest.json'); diff --git a/src/Props/DeferredProp.php b/src/Props/DeferProp.php similarity index 86% rename from src/Props/DeferredProp.php rename to src/Props/DeferProp.php index aec12bd..1f32e5b 100644 --- a/src/Props/DeferredProp.php +++ b/src/Props/DeferProp.php @@ -10,12 +10,13 @@ use function Tempest\invoke; -final class DeferredProp implements MergeableProp +final class DeferProp implements MergeableProp { use IsMergeableProp; public function __construct( public MethodReflector|FunctionReflector|string|array|Closure $callback, + public string $group = 'default', public private(set) bool $shouldMerge = false ) {} diff --git a/tests/Fixtures/TestController.php b/tests/Fixtures/TestController.php index 28b39a1..2a8eaec 100644 --- a/tests/Fixtures/TestController.php +++ b/tests/Fixtures/TestController.php @@ -5,6 +5,7 @@ namespace NeoIsRecursive\Inertia\Tests\Fixtures; use NeoIsRecursive\Inertia\Inertia; +use NeoIsRecursive\Inertia\Props\AlwaysProp; use Tempest\Router\Get; use function NeoIsRecursive\Inertia\inertia; @@ -22,6 +23,20 @@ public function testCanSharePropsFromAnyWhere(Inertia $inertia) { $inertia->share('foo', 'bar'); + $inertia->share([ + 'baz' => 'qux', + ]); + return inertia('User/Edit'); } + + #[Get(uri: '/all-sorts-of-props')] + public function testAllSortsOfProps(Inertia $inertia) + { + $inertia->share('foo', 'bar'); + + return inertia('User/Edit', [ + new AlwaysProp(fn() => 'baz'), + ]); + } } diff --git a/tests/Fixtures/public/.vite/manifest.json b/tests/Fixtures/public/.vite/manifest.json new file mode 100644 index 0000000..e008195 --- /dev/null +++ b/tests/Fixtures/public/.vite/manifest.json @@ -0,0 +1,8 @@ +{ + "src/main.tsx": { + "file": "assets/main-CMDOL_K2.tsx", + "name": "main", + "src": "src/main.tsx", + "isEntry": true + } +} diff --git a/tests/Integration/AlwaysPropTest.php b/tests/Integration/AlwaysPropTest.php index 1b5f29a..dd48809 100644 --- a/tests/Integration/AlwaysPropTest.php +++ b/tests/Integration/AlwaysPropTest.php @@ -6,7 +6,7 @@ use NeoIsRecursive\Inertia\Tests\TestCase; use Tempest\Router\Request; -class AlwaysPropTest extends TestCase +final class AlwaysPropTest extends TestCase { public function test_can_invoke(): void { diff --git a/tests/Integration/DeferPropsTest.php b/tests/Integration/DeferPropsTest.php new file mode 100644 index 0000000..c8a7730 --- /dev/null +++ b/tests/Integration/DeferPropsTest.php @@ -0,0 +1,35 @@ +assertSame('A lazy value', $deferProp()); + } + + public function test_can_accept_scalar_values(): void + { + $deferProp = new DeferProp(fn() => 'A lazy value'); + + $this->assertSame('A lazy value', $deferProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $deferProp = new DeferProp(function (Request $request) { + return $request; + }); + + $this->assertInstanceOf(Request::class, $deferProp()); + } +} diff --git a/tests/Integration/HelperTest.php b/tests/Integration/HelperTest.php index 00bdf73..2105250 100644 --- a/tests/Integration/HelperTest.php +++ b/tests/Integration/HelperTest.php @@ -8,7 +8,7 @@ use function NeoIsRecursive\Inertia\inertia; -class HelperTest extends TestCase +final class HelperTest extends TestCase { public function test_the_helper_function_returns_an_instance_of_the_response_factory(): void { diff --git a/tests/Integration/InertiaTest.php b/tests/Integration/InertiaTest.php index cef819a..fd837f3 100644 --- a/tests/Integration/InertiaTest.php +++ b/tests/Integration/InertiaTest.php @@ -16,7 +16,7 @@ use function Tempest\get; use function Tempest\uri; -class InertiaTest extends TestCase +final class InertiaTest extends TestCase { private function createFactory(): Inertia { @@ -105,11 +105,11 @@ public function test_shared_data_can_be_shared_from_anywhere(): void 'props' => [ 'user' => null, 'foo' => 'bar', + 'baz' => 'qux', 'errors' => [], ], 'url' => uri([TestController::class, 'testCanSharePropsFromAnyWhere']), 'version' => $version, - 'mergeProps' => [], ], $response->body); } diff --git a/tests/Integration/LazyPropTest.php b/tests/Integration/LazyPropTest.php index 6358e4a..da7d4bc 100644 --- a/tests/Integration/LazyPropTest.php +++ b/tests/Integration/LazyPropTest.php @@ -6,7 +6,7 @@ use NeoIsRecursive\Inertia\Tests\TestCase; use Tempest\Router\Request; -class LazyPropTest extends TestCase +final class LazyPropTest extends TestCase { public function test_can_invoke(): void { diff --git a/tests/Integration/MergePropTest.php b/tests/Integration/MergePropTest.php index 578c8bc..7025d40 100644 --- a/tests/Integration/MergePropTest.php +++ b/tests/Integration/MergePropTest.php @@ -6,7 +6,7 @@ use NeoIsRecursive\Inertia\Tests\TestCase; use Tempest\Router\Request; -class MergePropTest extends TestCase +final class MergePropTest extends TestCase { public function test_can_invoke_with_a_callback(): void { diff --git a/tests/Integration/ResponseTest.php b/tests/Integration/ResponseTest.php index 7216c97..ef2bb8a 100644 --- a/tests/Integration/ResponseTest.php +++ b/tests/Integration/ResponseTest.php @@ -4,6 +4,7 @@ use NeoIsRecursive\Inertia\Http\InertiaResponse; use NeoIsRecursive\Inertia\Props\AlwaysProp; +use NeoIsRecursive\Inertia\Props\DeferProp; use NeoIsRecursive\Inertia\Props\LazyProp; use NeoIsRecursive\Inertia\Support\Header; use NeoIsRecursive\Inertia\Tests\TestCase; @@ -16,7 +17,7 @@ use function Tempest\get; -class ResponseTest extends TestCase +final class ResponseTest extends TestCase { public function test_server_response(): void { @@ -34,7 +35,7 @@ public function test_server_response(): void $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); - $this->assertSame('
', get(ViewRenderer::class)->render($view)); + $this->assertSame('
', get(ViewRenderer::class)->render($view)); } public function test_xhr_response(): void @@ -518,4 +519,76 @@ public function test_dot_notation_props_are_merged_with_other_dot_notation_props ], ], $page['props']); } + + public function test_server_response_with_deferred_prop(): void + { + $request = $this->createInertiaRequest(Method::GET, '/user/123'); + + $user = ['name' => 'Jonathan']; + $response = new InertiaResponse( + request: $request, + page: 'User/Edit', + props: [ + 'user' => $user, + 'foo' => new DeferProp(function () { + return 'bar'; + }, group: 'default'), + ], + rootView: 'app', + version: '123', + ); + + $pageData = $response->body; + + $this->assertIsArray($pageData); + + $this->assertSame('User/Edit', $pageData['component']); + $this->assertSame('Jonathan', $pageData['props']['user']['name']); + $this->assertSame('/user/123', $pageData['url']); + $this->assertSame('123', $pageData['version']); + $this->assertSame([ + 'default' => ['foo'], + ], $pageData['deferredProps']); + // $this->assertFalse($pageData['clearHistory']); + // $this->assertFalse($pageData['encryptHistory']); + } + + public function test_server_response_with_deferred_prop_and_multiple_groups(): void + { + $request = $this->createInertiaRequest(Method::GET, '/user/123'); + + $user = ['name' => 'Jonathan']; + $response = new InertiaResponse( + request: $request, + page: 'User/Edit', + props: [ + 'user' => $user, + 'foo' => new DeferProp(function () { + return 'foo value'; + }, 'default'), + 'bar' => new DeferProp(function () { + return 'bar value'; + }, 'default'), + 'baz' => new DeferProp(function () { + return 'baz value'; + }, 'custom'), + ], + rootView: 'app', + version: '123' + ); + + $page = $response->body; + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertSame([ + 'default' => ['foo', 'bar'], + 'custom' => ['baz'], + ], $page['deferredProps']); + // $this->assertFalse($page['clearHistory']); + // $this->assertFalse($page['encryptHistory']); + // $this->assertSame('
', $view->render()); + } } diff --git a/tests/Integration/VersionResolverTest.php b/tests/Integration/VersionResolverTest.php new file mode 100644 index 0000000..33fc706 --- /dev/null +++ b/tests/Integration/VersionResolverTest.php @@ -0,0 +1,22 @@ +assertEquals('0fd434e97fe8f9df7938687c77e09a8a', $resolver->resolve(new GenericContainer())); + } + + public function test_version_resolver_returns_empty_string_when_manifest_file_does_not_exist(): void + { + $resolver = new ManifestVersionResolver(__DIR__ . '/../Fixtures/public/.vite/missing-manifest.json'); + + $this->assertEquals('', $resolver->resolve(new GenericContainer())); + } +}