Skip to content

Commit 46c56c8

Browse files
WIP: Frontend components installation.
1 parent 0515ab7 commit 46c56c8

File tree

11 files changed

+287
-11
lines changed

11 files changed

+287
-11
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"require": {
2727
"php": "^8.3",
2828
"illuminate/support": "11.* || 12.*",
29-
"inertiajs/inertia-laravel": "^2.0"
29+
"inertiajs/inertia-laravel": "^2.0",
30+
"laravel/prompts": "^0.3.6"
3031
},
3132
"require-dev": {
3233
"larastan/larastan": "^3.6",

pint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"visibility_required": true
5757
},
5858
"notName": [
59-
"TestCase.php"
59+
"TestCase.php",
60+
"SupportedStacks.php"
6061
]
6162
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codelabmw\InfiniteScroll\Console\Commands;
6+
7+
use Codelabmw\InfiniteScroll\Contracts\Stack;
8+
use Codelabmw\InfiniteScroll\SupportedStacks;
9+
use Illuminate\Console\Command;
10+
use Illuminate\Support\Collection;
11+
use Illuminate\Support\Facades\App;
12+
13+
use function Laravel\Prompts\error;
14+
use function Laravel\Prompts\info;
15+
use function Laravel\Prompts\select;
16+
use function Laravel\Prompts\spin;
17+
use function Laravel\Prompts\text;
18+
19+
final class InstallCommand extends Command
20+
{
21+
/**
22+
* The name and signature of the console command.
23+
*
24+
* @var string
25+
*/
26+
protected $signature = 'install:infinite-scroll';
27+
28+
/**
29+
* The console command description.
30+
*
31+
* @var string
32+
*/
33+
protected $description = 'Install Infinite Scrolling components for Inertia.';
34+
35+
/**
36+
* List of registered supported stacks.
37+
*
38+
* @var Collection<string, class-string<Stack>>
39+
*/
40+
private readonly Collection $supportedStacks;
41+
42+
/**
43+
* Creates a new InstallCommand instance.
44+
*/
45+
public function __construct(SupportedStacks $supportedStacksService)
46+
{
47+
parent::__construct();
48+
$this->supportedStacks = collect($supportedStacksService->get());
49+
}
50+
51+
/**
52+
* Execute the console command.
53+
*/
54+
public function handle(): ?int
55+
{
56+
/** @var string */
57+
$stack = select(
58+
label: 'Which stack are you using?',
59+
options: $this->supportedStacks->keys(), // @phpstan-ignore-line
60+
required: true,
61+
);
62+
63+
/** @var Stack */
64+
$stack = App::make((string) $this->supportedStacks->get($stack));
65+
66+
$installationPath = text(
67+
label: 'Where do you want to install components?',
68+
placeholder: $stack->getDefaultInstallationPath(),
69+
required: true,
70+
);
71+
72+
$error = spin(fn (): ?string => $this->install($stack->getStubs()), 'Installing infinite scroll components for '.$stack->getLabel().' in '.$installationPath.'.');
73+
74+
if ($error) {
75+
error('Error occurred while installing components. '.$error);
76+
77+
return 1;
78+
}
79+
80+
// @codeCoverageIgnoreStart
81+
info('Successfully installed components.');
82+
83+
return null;
84+
// @codeCoverageIgnoreEnd
85+
}
86+
87+
/**
88+
* Installs infinite scrolling frontend components.
89+
*
90+
* @param Collection<int, string> $stubs
91+
*/
92+
private function install(Collection $stubs): ?string
93+
{
94+
if ($stubs->isEmpty()) {
95+
return 'Installation files were not found.';
96+
}
97+
98+
$error = null;
99+
$stubs->each(function (string $file) use (&$error): void {
100+
if (! file_exists($file)) {
101+
$error = 'The file: '.$file.' does not exists.';
102+
103+
return;
104+
}
105+
});
106+
107+
return $error;
108+
}
109+
}

src/Contracts/Stack.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codelabmw\InfiniteScroll\Contracts;
6+
7+
use Illuminate\Support\Collection;
8+
9+
interface Stack
10+
{
11+
/**
12+
* The display name of the stack.
13+
*/
14+
public function getLabel(): string;
15+
16+
/**
17+
* The default installation path of components for this stack.
18+
*/
19+
public function getDefaultInstallationPath(): string;
20+
21+
/**
22+
* The paths of the stubs to copy.
23+
*
24+
* @return Collection<int, string>
25+
*/
26+
public function getStubs(): Collection;
27+
}

src/InfiniteScrollServiceProvider.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Codelabmw\InfiniteScroll;
66

7+
use Codelabmw\InfiniteScroll\Console\Commands\InstallCommand;
78
use Illuminate\Support\ServiceProvider;
89

910
final class InfiniteScrollServiceProvider extends ServiceProvider
@@ -14,13 +15,18 @@ final class InfiniteScrollServiceProvider extends ServiceProvider
1415
public function register(): void
1516
{
1617
$this->app->bind('infinite-scroll', fn (): InfiniteScroll => new InfiniteScroll());
18+
$this->app->singleton(SupportedStacks::class, fn (): SupportedStacks => new SupportedStacks());
1719
}
1820

1921
/**
2022
* Bootstrap any application services.
2123
*/
2224
public function boot(): void
2325
{
24-
//
26+
if ($this->app->runningInConsole()) {
27+
$this->commands([
28+
InstallCommand::class,
29+
]);
30+
}
2531
}
2632
}

src/Stacks/React.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codelabmw\InfiniteScroll\Stacks;
6+
7+
use Codelabmw\InfiniteScroll\Contracts\Stack;
8+
use Illuminate\Support\Collection;
9+
10+
final class React implements Stack
11+
{
12+
/**
13+
* The display name of the stack.
14+
*/
15+
public function getLabel(): string
16+
{
17+
return 'React';
18+
}
19+
20+
/**
21+
* The default installation path of components for this stack.
22+
*/
23+
public function getDefaultInstallationPath(): string
24+
{
25+
return resource_path('js/components');
26+
}
27+
28+
/**
29+
* The paths of the stubs to copy.
30+
*
31+
* @return Collection<int, string>
32+
*/
33+
public function getStubs(): Collection
34+
{
35+
return Collection::make();
36+
}
37+
}

src/SupportedStacks.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codelabmw\InfiniteScroll;
6+
7+
use Codelabmw\InfiniteScroll\Contracts\Stack;
8+
use Codelabmw\InfiniteScroll\Stacks\React;
9+
10+
class SupportedStacks
11+
{
12+
/**
13+
* @return array<string, class-string<Stack>>
14+
*/
15+
public function get(): array
16+
{
17+
return [
18+
'React' => React::class,
19+
];
20+
}
21+
}

tests/Architecture/ArchTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
<?php
22

33
declare(strict_types=1);
4+
45
use Codelabmw\InfiniteScroll\Facades\InfiniteScroll;
6+
use Codelabmw\InfiniteScroll\SupportedStacks;
57

68
arch()->preset()->php();
79
arch()->preset()->security();
8-
arch()->preset()->strict()->ignoring([InfiniteScroll::class]);
10+
arch()->preset()->strict()->ignoring([
11+
InfiniteScroll::class,
12+
SupportedStacks::class,
13+
]);
914
arch()->preset()->laravel();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Codelabmw\InfiniteScroll\Stacks\React;
6+
use Codelabmw\InfiniteScroll\SupportedStacks;
7+
8+
beforeEach(function (): void {
9+
// Arrange
10+
$mock = Mockery::mock(new SupportedStacks());
11+
$mock->shouldReceive('get')->andReturn([
12+
'React' => React::class,
13+
]);
14+
15+
$this->app->bind(SupportedStacks::class, fn () => $mock);
16+
});
17+
18+
it('requires input', function (): void {
19+
// Act & Assert
20+
$this->artisan('install:infinite-scroll')
21+
->expectsQuestion('Which stack are you using?', 'React')
22+
->expectsQuestion('Where do you want to install components?', 'resources/js/components');
23+
});
24+
25+
it('aborts if stack has no stub files', function (): void {
26+
// Arrange
27+
$mock = Mockery::mock(new React());
28+
$mock->shouldReceive('getStubs')->andReturn(collect([]));
29+
30+
$this->app->bind(React::class, fn () => $mock);
31+
32+
// Act & Assert
33+
$this->artisan('install:infinite-scroll')
34+
->expectsQuestion('Which stack are you using?', 'React')
35+
->expectsQuestion('Where do you want to install components?', 'resources/js/components')
36+
->assertExitCode(1);
37+
});
38+
39+
it('aborts if stack has stub files that does not exists', function (): void {
40+
// Arrange
41+
$mock = Mockery::mock(new React());
42+
$mock->shouldReceive('getStubs')->andReturn(collect([
43+
'none-existent.file',
44+
]));
45+
46+
$this->app->bind(React::class, fn () => $mock);
47+
48+
// Act & Assert
49+
$this->artisan('install:infinite-scroll')
50+
->expectsQuestion('Which stack are you using?', 'React')
51+
->expectsQuestion('Where do you want to install components?', 'resources/js/components')
52+
->assertExitCode(1);
53+
});
54+
55+
it('installs proper files', function (): void {})->todo();

tests/Feature/ExampleTest.php

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)