Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inertia page generator #734

Merged
merged 2 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
jasonmccreary marked this conversation as resolved.
Show resolved Hide resolved
'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>
Loading