Skip to content

Commit 3463db7

Browse files
authored
Inertia page generator (#734)
1 parent d7a8e20 commit 3463db7

File tree

9 files changed

+314
-0
lines changed

9 files changed

+314
-0
lines changed

config/blueprint.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
'notification' => \Blueprint\Generators\Statements\NotificationGenerator::class,
171171
'resource' => \Blueprint\Generators\Statements\ResourceGenerator::class,
172172
'view' => \Blueprint\Generators\Statements\ViewGenerator::class,
173+
'inertia_page' => \Blueprint\Generators\Statements\InertiaPageGenerator::class,
173174
'policy' => \Blueprint\Generators\PolicyGenerator::class,
174175
],
175176

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Blueprint\Generators\Statements;
4+
5+
use Blueprint\Contracts\Generator;
6+
use Blueprint\Generators\StatementGenerator;
7+
use Blueprint\Models\Statements\InertiaStatement;
8+
use Blueprint\Tree;
9+
use Illuminate\Support\Str;
10+
11+
class InertiaPageGenerator extends StatementGenerator implements Generator
12+
{
13+
protected array $types = [];
14+
15+
protected array $adapters = [
16+
'vue3' => ['framework' => 'vue', 'extension' => '.vue'],
17+
'react' => ['framework' => 'react', 'extension' => '.jsx'],
18+
'svelte' => ['framework' => 'svelte', 'extension' => '.svelte'],
19+
];
20+
21+
protected ?array $adapter = null;
22+
23+
public function output(Tree $tree): array
24+
{
25+
$this->adapter = $this->getAdapter();
26+
27+
if (!$this->adapter) {
28+
return $this->output;
29+
}
30+
31+
$stub = $this->filesystem->stub('inertia.' . $this->adapter['framework'] . '.stub');
32+
33+
/**
34+
* @var \Blueprint\Models\Controller $controller
35+
*/
36+
foreach ($tree->controllers() as $controller) {
37+
foreach ($controller->methods() as $statements) {
38+
foreach ($statements as $statement) {
39+
if (!$statement instanceof InertiaStatement) {
40+
continue;
41+
}
42+
43+
$path = $this->getStatementPath($statement->view());
44+
45+
if ($this->filesystem->exists($path)) {
46+
$this->output['skipped'][] = $path;
47+
continue;
48+
}
49+
50+
$this->create($path, $this->populateStub($stub, $statement));
51+
}
52+
}
53+
}
54+
55+
return $this->output;
56+
}
57+
58+
protected function getAdapter(): ?array
59+
{
60+
$packagePath = base_path('package.json');
61+
62+
if (!$this->filesystem->exists($packagePath)) {
63+
return null;
64+
}
65+
66+
$contents = $this->filesystem->get($packagePath);
67+
68+
if (preg_match('/@inertiajs\/(vue3|react|svelte)/i', $contents, $matches)) {
69+
$adapterKey = strtolower($matches[1]);
70+
71+
return $this->adapters[$adapterKey] ?? null;
72+
}
73+
74+
return null;
75+
}
76+
77+
protected function getStatementPath(string $view): string
78+
{
79+
return 'resources/js/Pages/' . str_replace('.', '/', $view) . $this->adapter['extension'];
80+
}
81+
82+
protected function populateStub(string $stub, InertiaStatement $inertiaStatement): string
83+
{
84+
$data = $inertiaStatement->data();
85+
$props = $this->adapter['framework'] === 'vue' ? json_encode($data) : '{ ' . implode(', ', $data) . ' }';
86+
$componentName = $this->adapter['framework'] === 'react' ? Str::afterLast($inertiaStatement->view(), '/') : null;
87+
88+
return str_replace([
89+
'{{ componentName }}',
90+
'{{ props }}',
91+
'{{ view }}',
92+
], [
93+
$componentName,
94+
$props,
95+
str_replace('/', ' ', $inertiaStatement->view()),
96+
], $stub);
97+
}
98+
}

stubs/inertia.react.stub

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Head } from '@inertiajs/react'
2+
3+
export default function {{ componentName }}({{ props }}) {
4+
return (
5+
<div>
6+
<Head title="{{ view }}" />
7+
<h1>{{ view }}</h1>
8+
</div>
9+
)
10+
}

stubs/inertia.svelte.stub

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let {{ props }} = $props()
3+
</script>
4+
5+
<svelte:head>
6+
<title>{{ view }}</title>
7+
</svelte:head>
8+
9+
<div>
10+
<h1>{{ view }}</h1>
11+
</div>

stubs/inertia.vue.stub

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup>
2+
import { Head } from '@inertiajs/vue3'
3+
4+
defineProps({{ props }})
5+
</script>
6+
7+
<template>
8+
<Head title="{{ view }}" />
9+
<h1>{{ view }}</h1>
10+
</template>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Tests\Feature\Generators\Statements;
4+
5+
use Blueprint\Blueprint;
6+
use Blueprint\Generators\Statements\InertiaPageGenerator;
7+
use Blueprint\Lexers\StatementLexer;
8+
use Blueprint\Tree;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\Attributes\Test;
11+
use Tests\TestCase;
12+
13+
/**
14+
* @see InertiaPageGenerator
15+
*/
16+
final class InertiaPageGeneratorTest extends TestCase
17+
{
18+
private $blueprint;
19+
20+
protected $files;
21+
22+
/** @var InertiaPageGenerator */
23+
private $subject;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
29+
$this->subject = new InertiaPageGenerator($this->files);
30+
31+
$this->blueprint = new Blueprint;
32+
$this->blueprint->registerLexer(new \Blueprint\Lexers\ControllerLexer(new StatementLexer));
33+
$this->blueprint->registerGenerator($this->subject);
34+
}
35+
36+
#[Test]
37+
public function output_writes_nothing_for_empty_tree(): void
38+
{
39+
$this->filesystem->shouldNotHaveReceived('put');
40+
41+
$this->assertEquals([], $this->subject->output(new Tree(['controllers' => []])));
42+
}
43+
44+
#[Test]
45+
public function output_writes_nothing_without_inertia_statements(): void
46+
{
47+
$this->filesystem->shouldNotHaveReceived('put');
48+
49+
$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
50+
$tree = $this->blueprint->analyze($tokens);
51+
52+
$this->assertEquals([], $this->subject->output($tree));
53+
}
54+
55+
#[Test]
56+
public function output_writes_nothing_when_package_json_is_missing(): void
57+
{
58+
$this->filesystem->expects('exists')
59+
->with(base_path('package.json'))
60+
->andReturnFalse();
61+
$this->filesystem->shouldNotHaveReceived('put');
62+
63+
$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
64+
$tree = $this->blueprint->analyze($tokens);
65+
66+
$this->assertEquals([], $this->subject->output($tree));
67+
}
68+
69+
#[Test]
70+
public function output_writes_nothing_when_adapter_is_not_found(): void
71+
{
72+
$this->filesystem->expects('exists')
73+
->with(base_path('package.json'))
74+
->andReturnTrue();
75+
$this->filesystem->expects('get')
76+
->with(base_path('package.json'))
77+
->andReturn('');
78+
$this->filesystem->shouldNotHaveReceived('put');
79+
80+
$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
81+
$tree = $this->blueprint->analyze($tokens);
82+
83+
$this->assertEquals([], $this->subject->output($tree));
84+
}
85+
86+
#[Test]
87+
#[DataProvider('inertiaAdaptersDataProvider')]
88+
public function output_writes_pages_for_inertia_statements($framework, $dependencies, $path, $extension): void
89+
{
90+
$this->filesystem->expects('exists')
91+
->with(base_path('package.json'))
92+
->andReturnTrue();
93+
$this->filesystem->expects('get')
94+
->with(base_path('package.json'))
95+
->andReturn($dependencies);
96+
$this->filesystem->expects('stub')
97+
->with("inertia.$framework.stub")
98+
->andReturn($this->stub("inertia.$framework.stub"));
99+
$this->filesystem->expects('exists')
100+
->with($path)
101+
->andReturnFalse();
102+
$this->filesystem->expects('put')
103+
->with($path, $this->fixture('inertia-pages/customer-show' . $extension));
104+
105+
$tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
106+
$tree = $this->blueprint->analyze($tokens);
107+
$output = $this->subject->output($tree);
108+
109+
$this->assertContains(
110+
$path,
111+
$output['created'],
112+
);
113+
}
114+
115+
#[Test]
116+
#[DataProvider('inertiaAdaptersDataProvider')]
117+
public function it_outputs_skipped_pages($framework, $dependencies, $path): void
118+
{
119+
$this->filesystem->expects('exists')
120+
->with(base_path('package.json'))
121+
->andReturnTrue();
122+
$this->filesystem->expects('get')
123+
->with(base_path('package.json'))
124+
->andReturn($dependencies);
125+
$this->filesystem->expects('stub')
126+
->with("inertia.$framework.stub")
127+
->andReturn($this->stub("inertia.$framework.stub"));
128+
$this->filesystem->expects('exists')
129+
->with($path)
130+
->andReturnTrue();
131+
$this->filesystem->expects('put')
132+
->never();
133+
134+
$tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
135+
$tree = $this->blueprint->analyze($tokens);
136+
$ouput = $this->subject->output($tree);
137+
138+
$this->assertEquals([
139+
'skipped' => [
140+
$path,
141+
],
142+
], $ouput);
143+
}
144+
145+
public static function inertiaAdaptersDataProvider(): array
146+
{
147+
return [
148+
['vue', '"@inertiajs/vue3": "^2.0.0"', 'resources/js/Pages/Customer/Show.vue', '.vue'],
149+
['react', '"@inertiajs/react": "^2.0.0"', 'resources/js/Pages/Customer/Show.jsx', '.jsx'],
150+
['svelte', '"@inertiajs/svelte": "^2.0.0"', 'resources/js/Pages/Customer/Show.svelte', '.svelte'],
151+
];
152+
}
153+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Head } from '@inertiajs/react'
2+
3+
export default function Show({ customer, customers }) {
4+
return (
5+
<div>
6+
<Head title="Customer Show" />
7+
<h1>Customer Show</h1>
8+
</div>
9+
)
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let { customer, customers } = $props()
3+
</script>
4+
5+
<svelte:head>
6+
<title>Customer Show</title>
7+
</svelte:head>
8+
9+
<div>
10+
<h1>Customer Show</h1>
11+
</div>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup>
2+
import { Head } from '@inertiajs/vue3'
3+
4+
defineProps(["customer","customers"])
5+
</script>
6+
7+
<template>
8+
<Head title="Customer Show" />
9+
<h1>Customer Show</h1>
10+
</template>

0 commit comments

Comments
 (0)