Skip to content

Commit

Permalink
Inertia page generator (#734)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicodevs authored Jan 8, 2025
1 parent d7a8e20 commit 3463db7
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
'notification' => \Blueprint\Generators\Statements\NotificationGenerator::class,
'resource' => \Blueprint\Generators\Statements\ResourceGenerator::class,
'view' => \Blueprint\Generators\Statements\ViewGenerator::class,
'inertia_page' => \Blueprint\Generators\Statements\InertiaPageGenerator::class,
'policy' => \Blueprint\Generators\PolicyGenerator::class,
],

Expand Down
98 changes: 98 additions & 0 deletions src/Generators/Statements/InertiaPageGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace Blueprint\Generators\Statements;

use Blueprint\Contracts\Generator;
use Blueprint\Generators\StatementGenerator;
use Blueprint\Models\Statements\InertiaStatement;
use Blueprint\Tree;
use Illuminate\Support\Str;

class InertiaPageGenerator extends StatementGenerator implements Generator
{
protected array $types = [];

protected array $adapters = [
'vue3' => ['framework' => 'vue', 'extension' => '.vue'],
'react' => ['framework' => 'react', 'extension' => '.jsx'],
'svelte' => ['framework' => 'svelte', 'extension' => '.svelte'],
];

protected ?array $adapter = null;

public function output(Tree $tree): array
{
$this->adapter = $this->getAdapter();

if (!$this->adapter) {
return $this->output;
}

$stub = $this->filesystem->stub('inertia.' . $this->adapter['framework'] . '.stub');

/**
* @var \Blueprint\Models\Controller $controller
*/
foreach ($tree->controllers() as $controller) {
foreach ($controller->methods() as $statements) {
foreach ($statements as $statement) {
if (!$statement instanceof InertiaStatement) {
continue;
}

$path = $this->getStatementPath($statement->view());

if ($this->filesystem->exists($path)) {
$this->output['skipped'][] = $path;
continue;
}

$this->create($path, $this->populateStub($stub, $statement));
}
}
}

return $this->output;
}

protected function getAdapter(): ?array
{
$packagePath = base_path('package.json');

if (!$this->filesystem->exists($packagePath)) {
return null;
}

$contents = $this->filesystem->get($packagePath);

if (preg_match('/@inertiajs\/(vue3|react|svelte)/i', $contents, $matches)) {
$adapterKey = strtolower($matches[1]);

return $this->adapters[$adapterKey] ?? null;
}

return null;
}

protected function getStatementPath(string $view): string
{
return 'resources/js/Pages/' . str_replace('.', '/', $view) . $this->adapter['extension'];
}

protected function populateStub(string $stub, InertiaStatement $inertiaStatement): string
{
$data = $inertiaStatement->data();
$props = $this->adapter['framework'] === 'vue' ? json_encode($data) : '{ ' . implode(', ', $data) . ' }';
$componentName = $this->adapter['framework'] === 'react' ? Str::afterLast($inertiaStatement->view(), '/') : null;

return str_replace([
'{{ componentName }}',
'{{ props }}',
'{{ view }}',
], [
$componentName,
$props,
str_replace('/', ' ', $inertiaStatement->view()),
], $stub);
}
}
10 changes: 10 additions & 0 deletions stubs/inertia.react.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Head } from '@inertiajs/react'

export default function {{ componentName }}({{ props }}) {
return (
<div>
<Head title="{{ view }}" />
<h1>{{ view }}</h1>
</div>
)
}
11 changes: 11 additions & 0 deletions stubs/inertia.svelte.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
let {{ props }} = $props()
</script>

<svelte:head>
<title>{{ view }}</title>
</svelte:head>

<div>
<h1>{{ view }}</h1>
</div>
10 changes: 10 additions & 0 deletions stubs/inertia.vue.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup>
import { Head } from '@inertiajs/vue3'

defineProps({{ props }})
</script>

<template>
<Head title="{{ view }}" />
<h1>{{ view }}</h1>
</template>
153 changes: 153 additions & 0 deletions tests/Feature/Generators/Statements/InertiaPageGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Tests\Feature\Generators\Statements;

use Blueprint\Blueprint;
use Blueprint\Generators\Statements\InertiaPageGenerator;
use Blueprint\Lexers\StatementLexer;
use Blueprint\Tree;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
* @see InertiaPageGenerator
*/
final class InertiaPageGeneratorTest extends TestCase
{
private $blueprint;

protected $files;

/** @var InertiaPageGenerator */
private $subject;

protected function setUp(): void
{
parent::setUp();

$this->subject = new InertiaPageGenerator($this->files);

$this->blueprint = new Blueprint;
$this->blueprint->registerLexer(new \Blueprint\Lexers\ControllerLexer(new StatementLexer));
$this->blueprint->registerGenerator($this->subject);
}

#[Test]
public function output_writes_nothing_for_empty_tree(): void
{
$this->filesystem->shouldNotHaveReceived('put');

$this->assertEquals([], $this->subject->output(new Tree(['controllers' => []])));
}

#[Test]
public function output_writes_nothing_without_inertia_statements(): void
{
$this->filesystem->shouldNotHaveReceived('put');

$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals([], $this->subject->output($tree));
}

#[Test]
public function output_writes_nothing_when_package_json_is_missing(): void
{
$this->filesystem->expects('exists')
->with(base_path('package.json'))
->andReturnFalse();
$this->filesystem->shouldNotHaveReceived('put');

$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals([], $this->subject->output($tree));
}

#[Test]
public function output_writes_nothing_when_adapter_is_not_found(): void
{
$this->filesystem->expects('exists')
->with(base_path('package.json'))
->andReturnTrue();
$this->filesystem->expects('get')
->with(base_path('package.json'))
->andReturn('');
$this->filesystem->shouldNotHaveReceived('put');

$tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals([], $this->subject->output($tree));
}

#[Test]
#[DataProvider('inertiaAdaptersDataProvider')]
public function output_writes_pages_for_inertia_statements($framework, $dependencies, $path, $extension): void
{
$this->filesystem->expects('exists')
->with(base_path('package.json'))
->andReturnTrue();
$this->filesystem->expects('get')
->with(base_path('package.json'))
->andReturn($dependencies);
$this->filesystem->expects('stub')
->with("inertia.$framework.stub")
->andReturn($this->stub("inertia.$framework.stub"));
$this->filesystem->expects('exists')
->with($path)
->andReturnFalse();
$this->filesystem->expects('put')
->with($path, $this->fixture('inertia-pages/customer-show' . $extension));

$tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
$tree = $this->blueprint->analyze($tokens);
$output = $this->subject->output($tree);

$this->assertContains(
$path,
$output['created'],
);
}

#[Test]
#[DataProvider('inertiaAdaptersDataProvider')]
public function it_outputs_skipped_pages($framework, $dependencies, $path): void
{
$this->filesystem->expects('exists')
->with(base_path('package.json'))
->andReturnTrue();
$this->filesystem->expects('get')
->with(base_path('package.json'))
->andReturn($dependencies);
$this->filesystem->expects('stub')
->with("inertia.$framework.stub")
->andReturn($this->stub("inertia.$framework.stub"));
$this->filesystem->expects('exists')
->with($path)
->andReturnTrue();
$this->filesystem->expects('put')
->never();

$tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
$tree = $this->blueprint->analyze($tokens);
$ouput = $this->subject->output($tree);

$this->assertEquals([
'skipped' => [
$path,
],
], $ouput);
}

public static function inertiaAdaptersDataProvider(): array
{
return [
['vue', '"@inertiajs/vue3": "^2.0.0"', 'resources/js/Pages/Customer/Show.vue', '.vue'],
['react', '"@inertiajs/react": "^2.0.0"', 'resources/js/Pages/Customer/Show.jsx', '.jsx'],
['svelte', '"@inertiajs/svelte": "^2.0.0"', 'resources/js/Pages/Customer/Show.svelte', '.svelte'],
];
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/inertia-pages/customer-show.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Head } from '@inertiajs/react'

export default function Show({ customer, customers }) {
return (
<div>
<Head title="Customer Show" />
<h1>Customer Show</h1>
</div>
)
}
11 changes: 11 additions & 0 deletions tests/fixtures/inertia-pages/customer-show.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
let { customer, customers } = $props()
</script>

<svelte:head>
<title>Customer Show</title>
</svelte:head>

<div>
<h1>Customer Show</h1>
</div>
10 changes: 10 additions & 0 deletions tests/fixtures/inertia-pages/customer-show.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup>
import { Head } from '@inertiajs/vue3'
defineProps(["customer","customers"])
</script>

<template>
<Head title="Customer Show" />
<h1>Customer Show</h1>
</template>

0 comments on commit 3463db7

Please sign in to comment.