├── .github ├── FUNDING.yml └── workflows │ └── run-tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Readme.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── InertiaResponseRenderer.php └── InertiaResponseRendererServiceProvider.php └── tests ├── Fixtures ├── InertiaStep.php └── InertiaWizard.php ├── InertiaResponseRendererTest.php └── TestCase.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ksassnowski 2 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | php: ["8.0", "8.1"] 11 | laravel: [8.*, 9.*] 12 | dependency-version: [prefer-lowest, prefer-stable] 13 | include: 14 | - laravel: 8.* 15 | testbench: ^6.17 16 | larastan: ^1.0 17 | - laravel: 9.* 18 | testbench: ^7.6 19 | larastan: ^2.0 20 | os: [ubuntu-latest] 21 | 22 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 23 | 24 | steps: 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php }} 29 | extensions: pdo, sqlite, pdo_sqlite 30 | coverage: none 31 | 32 | - name: Checkout code 33 | uses: actions/checkout@v2 34 | 35 | - name: Cache dependencies 36 | uses: actions/cache@v1 37 | with: 38 | path: ~/.composer/cache/files 39 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 40 | 41 | - name: Install dependencies 42 | run: | 43 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nunomaduro/larastan:${{ matrix.larastan }}" --no-interaction --no-update 44 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 45 | 46 | - name: Execute tests 47 | run: vendor/bin/phpunit 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .phpunit.cache/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.6.0] — 2022-07-14 9 | 10 | ### Added 11 | 12 | - Added support for Laravel 9 13 | 14 | ### Changed 15 | 16 | - Bump Arcanist dependency to `0.7.0` 17 | 18 | ## [0.5.0] — 2021-08-04 19 | 20 | ### Changed 21 | 22 | - Bump Arcanist dependency to `^0.5.0` 23 | 24 | ## [0.4.0] — 2021-07-20 25 | 26 | ### Changed 27 | 28 | - Bump Arcanist dependency to `^0.4.0` 29 | - Change return type declarations to match `ResponseRenderer` interface 30 | 31 | ## [0.3.0] — 2021-06-29 32 | 33 | ### Changed 34 | 35 | - Bump Arcanist dependency to 0.3.0 36 | 37 | ## [0.2.0] — 2021-05-07 38 | 39 | ### Changed 40 | 41 | - Bump Arcanist dependency to 0.2.0 42 | 43 | ## [0.1.0] — 2021-05-04 44 | 45 | Initial release 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright <2021> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Inertia Response Renderer 2 | 3 | This package provides an Inertia.js response renderer for Arcanist. 4 | 5 | > The real docs are still being written, but here’s a really quick 6 | > introduction. 7 | 8 | ## Installation 9 | 10 | Install the package through composer (you still need the main Arcanist package 11 | installed). 12 | 13 | ``` 14 | composer require laravel-arcanist/inertia-response-renderer 15 | ``` 16 | 17 | Inside `config/arcanist.php`, change the `renderers.renderer` key to 18 | `Arcanist\InertiaResponseRenderer::class`. 19 | 20 | That’s it. 21 | 22 | ## How it works 23 | 24 | The reponse renderer will try and resolve step templates via the following 25 | convention: 26 | 27 | ``` 28 | resources/js/Pages/Wizards/{wizard-slug}/{step-slug}.vue 29 | ``` 30 | 31 | You can configure the `Wizard` path prefix by changing the 32 | `renderers.inertia.component_base_path` setting in the config. 33 | 34 | ## View Data 35 | 36 | Arcanist passes a `step` and `wizard` prop to all views. These can be accessed 37 | in the usual Inertia way. 38 | 39 | ```javascript 40 | this.$page.props.arcanist.wizard 41 | 42 | this.$page.props.arcanist.step 43 | ``` 44 | 45 | Please refer to the [main documentation](https://laravel-arcanist.com/getting-started#accessing-data-in-a-view) for a more detailed explanation of 46 | these variables. 47 | 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-arcanist/inertia-response-renderer", 3 | "description": "Inertia response renderer for Arcanist", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Kai Sassnowski", 9 | "email": "me@kai-sassnowski.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.0", 14 | "inertiajs/inertia-laravel": "^0.5.2", 15 | "laravel-arcanist/arcanist": "^0.7.0" 16 | }, 17 | "require-dev": { 18 | "ergebnis/composer-normalize": "^2.13", 19 | "mockery/mockery": "^1.4", 20 | "orchestra/testbench": "^6.17 || ^7.6", 21 | "phpunit/phpunit": "^9.0", 22 | "roave/security-advisories": "dev-master" 23 | }, 24 | "minimum-stability": "dev", 25 | "prefer-stable": true, 26 | "autoload": { 27 | "psr-4": { 28 | "Arcanist\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Arcanist\\Tests\\": "tests/" 34 | } 35 | }, 36 | "config": { 37 | "allow-plugins": { 38 | "ergebnis/composer-normalize": true 39 | } 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Arcanist\\InertiaResponseRendererServiceProvider" 45 | ] 46 | } 47 | }, 48 | "scripts": { 49 | "post-install-cmd": [ 50 | "composer normalize" 51 | ], 52 | "post-update-cmd": [ 53 | "composer normalize" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | tests 11 | 12 | 13 | 14 | 16 | 17 | src 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/InertiaResponseRenderer.php: -------------------------------------------------------------------------------- 1 | componentBasePath . '/' . Str::studly($wizard::$slug) . '/' . Str::studly($step->slug); 24 | 25 | $viewData = [ 26 | 'arcanist' => array_filter([ 27 | 'wizard' => $wizard->summary(), 28 | 'step' => $data, 29 | ]) 30 | ]; 31 | 32 | return Inertia::render($component, $viewData); 33 | } 34 | 35 | public function redirect(WizardStep $step, AbstractWizard $wizard): Response | Responsable | Renderable 36 | { 37 | if (!$wizard->exists()) { 38 | return redirect()->route('wizard.' . $wizard::$slug . '.create'); 39 | } 40 | 41 | return redirect()->route( 42 | 'wizard.' . $wizard::$slug . '.show', 43 | [$wizard->getId(), $step->slug] 44 | ); 45 | } 46 | 47 | public function redirectWithError( 48 | WizardStep $step, 49 | AbstractWizard $wizard, 50 | ?string $error = null 51 | ): Response | Responsable | Renderable { 52 | return redirect()->route( 53 | 'wizard.' . $wizard::$slug . '.show', 54 | [$wizard->getId(), $step->slug] 55 | )->withErrors(['wizard' => $error]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/InertiaResponseRendererServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(InertiaResponseRenderer::class, function () { 12 | return new InertiaResponseRenderer( 13 | config('arcanist.renderers.inertia.component_base_path', 'Wizards') 14 | ); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Fixtures/InertiaStep.php: -------------------------------------------------------------------------------- 1 | wizard = m::mock(InertiaWizard::class); 32 | $this->wizard::$slug = 'inertia-wizard'; 33 | $this->step = m::mock(InertiaStep::class); 34 | $this->step->slug = 'inertia-step'; 35 | } 36 | 37 | public function throws_an_exception_if_the_template_does_not_exist(): void 38 | { 39 | $this->markTestSkipped('Not possible to test if template exists from Laravel'); 40 | } 41 | 42 | /** @test */ 43 | public function it_renders_the_correct_template_for_a_step(): void 44 | { 45 | File::shouldReceive('exists')->andReturnTrue(); 46 | $this->wizard->allows('summary')->andReturns([]); 47 | 48 | Inertia::shouldReceive('render') 49 | ->once() 50 | ->with('Wizards/InertiaWizard/InertiaStep', m::any()) 51 | ->andReturn($this->createMock(Response::class)); 52 | 53 | $this->makeRenderer()->renderStep($this->step, $this->wizard); 54 | } 55 | 56 | /** @test */ 57 | public function it_always_passes_the_wizard_summary_to_the_view(): void 58 | { 59 | File::shouldReceive('exists')->andReturnTrue(); 60 | $this->wizard->allows('summary')->andReturns(['::summary::']); 61 | 62 | Inertia::shouldReceive('render') 63 | ->once() 64 | ->with(m::any(), [ 65 | 'arcanist' => [ 66 | 'wizard' => ['::summary::'], 67 | ], 68 | ]) 69 | ->andReturn($this->createMock(Response::class)); 70 | 71 | $this->makeRenderer()->renderStep($this->step, $this->wizard); 72 | } 73 | 74 | /** @test */ 75 | public function it_passes_the_view_data_to_the_view(): void 76 | { 77 | File::shouldReceive('exists')->andReturnTrue(); 78 | $this->wizard->allows('summary')->andReturns(['::summary::']); 79 | 80 | Inertia::shouldReceive('render') 81 | ->once() 82 | ->with(m::any(), [ 83 | 'arcanist' => [ 84 | 'wizard' => ['::summary::'], 85 | 'step' => ['::key::' => '::value::'], 86 | ], 87 | ]) 88 | ->andReturn($this->createMock(Response::class)); 89 | 90 | $this->makeRenderer()->renderStep($this->step, $this->wizard, ['::key::' => '::value::']); 91 | } 92 | 93 | /** 94 | * @test 95 | * @dataProvider redirectToStepProvider 96 | */ 97 | public function it_redirects_to_a_steps_view(callable $callRenderer): void 98 | { 99 | Route::get('/wizard/inertia-wizard/{id}/{slug?}', fn () => 'ok')->name('wizard.inertia-wizard.show'); 100 | $this->wizard->allows('exists')->andReturnTrue(); 101 | $this->wizard->allows('getId')->andReturn('1'); 102 | 103 | $response = new TestResponse($callRenderer($this->makeRenderer(), $this->wizard, $this->step)); 104 | 105 | $response->assertRedirect(route('wizard.inertia-wizard.show', [ 106 | 'id' => '1', 107 | 'slug' => 'inertia-step' 108 | ])); 109 | } 110 | 111 | /** @test */ 112 | public function it_redirects_to_the_first_step_if_the_wizard_does_not_exist_yet(): void 113 | { 114 | Route::get('/wizard/inertia-wizard', fn () => 'ok')->name('wizard.inertia-wizard.create'); 115 | 116 | $this->wizard->allows('exists')->andReturnFalse(); 117 | $response = new TestResponse($this->makeRenderer()->redirect($this->step, $this->wizard)); 118 | 119 | $response->assertRedirect(route('wizard.inertia-wizard.create')); 120 | } 121 | 122 | public function redirectToStepProvider(): Generator 123 | { 124 | yield from [ 125 | 'redirect' => [ 126 | function (InertiaResponseRenderer $renderer, AbstractWizard $wizard, WizardStep $step) { 127 | return $renderer->redirect($step, $wizard); 128 | }, 129 | ], 130 | 'redirectWithError' => [ 131 | function (InertiaResponseRenderer $renderer, AbstractWizard $wizard, WizardStep $step) { 132 | return $renderer->redirectWithError($step, $wizard); 133 | }, 134 | ] 135 | ]; 136 | } 137 | 138 | /** @test */ 139 | public function it_redirects_with_an_error(): void 140 | { 141 | Route::get('/wizard/inertia-wizard/{id}/{slug?}', fn () => 'ok')->name('wizard.inertia-wizard.show'); 142 | 143 | $this->wizard->allows('getId')->andReturn('1'); 144 | 145 | $response = new TestResponse( 146 | $this->makeRenderer()->redirectWithError($this->step, $this->wizard, '::message::') 147 | ); 148 | 149 | $response->assertSessionHasErrors('wizard'); 150 | } 151 | 152 | protected function makeRenderer(): ResponseRenderer 153 | { 154 | return new InertiaResponseRenderer('Wizards'); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('arcanist.route_prefix', '/wizard'); 19 | } 20 | } 21 | --------------------------------------------------------------------------------