Skip to content

Commit efe40e7

Browse files
Feat: Guesses the current stack and sets it as the default.
1 parent 924001a commit efe40e7

File tree

9 files changed

+226
-14
lines changed

9 files changed

+226
-14
lines changed

src/Console/Commands/InstallCommand.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ final class InstallCommand extends Command
3636
/**
3737
* List of registered supported stacks.
3838
*
39-
* @var Collection<string, class-string<Stack>>
39+
* @var Collection<string, Stack>
4040
*/
4141
private readonly Collection $supportedStacks;
4242

@@ -46,7 +46,13 @@ final class InstallCommand extends Command
4646
public function __construct(SupportedStacks $supportedStacksService)
4747
{
4848
parent::__construct();
49-
$this->supportedStacks = collect($supportedStacksService->get());
49+
50+
$this->supportedStacks = collect($supportedStacksService->get())->mapWithKeys(function (string $stack): array {
51+
/** @var Stack */
52+
$stack = App::make($stack);
53+
54+
return [$stack->getLabel() => $stack];
55+
});
5056
}
5157

5258
/**
@@ -58,18 +64,24 @@ public function handle(): ?int
5864
$stack = select(
5965
label: 'Which stack are you using?',
6066
options: $this->supportedStacks->keys(), // @phpstan-ignore-line
67+
default: $this->supportedStacks->firstWhere(fn (Stack $stack): bool => $stack->isCurrent())?->getLabel(),
6168
required: true,
6269
);
6370

6471
/** @var Stack */
65-
$stack = App::make((string) $this->supportedStacks->get($stack));
72+
$stack = $this->supportedStacks->get($stack);
6673

67-
// @phpstan-ignore-next-line
6874
$installationPath = text(
6975
label: 'Where do you want to install components?',
7076
placeholder: $stack->getDefaultInstallationPath(),
7177
required: false,
72-
) ?? $stack->getDefaultInstallationPath();
78+
);
79+
80+
// @codeCoverageIgnoreStart
81+
if ($installationPath === '') {
82+
$installationPath = $stack->getDefaultInstallationPath();
83+
}
84+
// @codeCoverageIgnoreEnd
7385

7486
$error = spin(fn (): ?string => $this->install($stack->getStubs(), $installationPath), 'Installing infinite scroll components for '.$stack->getLabel().' in '.$installationPath.'.');
7587

src/Contracts/Stack.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getDefaultInstallationPath(): string;
2424
* @return Collection<int, string>
2525
*/
2626
public function getStubs(): Collection;
27+
28+
/**
29+
* Whether this stack is the current one.
30+
*/
31+
public function isCurrent(): bool;
2732
}

src/Stacks/React.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,30 @@ public function getStubs(): Collection
3737
FileSystem::stubs('components/react/ts/infinite-scroll.tsx'),
3838
]);
3939
}
40+
41+
/**
42+
* Whether this stack is the current one.
43+
*/
44+
public function isCurrent(): bool
45+
{
46+
$packageJsonPath = base_path('package.json');
47+
48+
if (! FileSystem::exists($packageJsonPath)) {
49+
return false;
50+
}
51+
52+
$json = json_decode((string) FileSystem::getContents($packageJsonPath), true);
53+
54+
if (! is_array($json)) {
55+
return false;
56+
}
57+
58+
/** @var array<string, string> */
59+
$deps = $json['dependencies'] ?? [];
60+
61+
/** @var array<string, string> */
62+
$devDeps = $json['devDependencies'] ?? [];
63+
64+
return isset($deps['@inertiajs/react']) || isset($devDeps['@inertiajs/react']);
65+
}
4066
}

src/Support/FileSystem.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ public static function copy(string $source, string $destination): bool
4242
return copy($source, $destination);
4343
}
4444

45+
/**
46+
* Returns the contents of a file.
47+
*/
48+
public static function getContents(string $path): bool|string
49+
{
50+
return file_get_contents($path);
51+
}
52+
4553
/**
4654
* Returns the path to the stubs directory.
4755
*/

src/SupportedStacks.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
class SupportedStacks
1111
{
1212
/**
13-
* @return array<string, class-string<Stack>>
13+
* @return array<int, class-string<Stack>>
1414
*/
1515
public function get(): array
1616
{
1717
return [
18-
'React' => React::class,
18+
React::class,
1919
];
2020
}
2121
}

tests/Feature/Console/Commands/InstallCommandTest.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,44 @@
22

33
declare(strict_types=1);
44

5+
use Codelabmw\InfiniteScroll\Contracts\Stack;
56
use Codelabmw\InfiniteScroll\Stacks\React;
67
use Codelabmw\InfiniteScroll\Support\FileSystem;
78
use Codelabmw\InfiniteScroll\SupportedStacks;
9+
use Illuminate\Support\Collection;
810

911
beforeEach(function (): void {
1012
// Arrange
1113
$mock = Mockery::mock(new SupportedStacks());
1214
$mock->shouldReceive('get')->andReturn([
13-
'React' => React::class,
15+
React::class,
1416
]);
1517

1618
$this->app->bind(SupportedStacks::class, fn () => $mock);
1719

20+
$this->reactStack = new class implements Stack
21+
{
22+
public function getLabel(): string
23+
{
24+
return 'React';
25+
}
26+
27+
public function isCurrent(): bool
28+
{
29+
return true;
30+
}
31+
32+
public function getDefaultInstallationPath(): string
33+
{
34+
return 'resources/js/components';
35+
}
36+
37+
public function getStubs(): Collection
38+
{
39+
return collect([]);
40+
}
41+
};
42+
1843
$this->componentPath = FileSystem::tests('Fixtures/Storage/infinite-scroll.tsx');
1944
});
2045

@@ -27,8 +52,7 @@
2752

2853
it('aborts if stack has no stub files', function (): void {
2954
// Arrange
30-
$mock = Mockery::mock(new React());
31-
$mock->shouldReceive('getStubs')->andReturn(collect([]));
55+
$mock = Mockery::mock($this->reactStack);
3256

3357
$this->app->bind(React::class, fn () => $mock);
3458

@@ -41,7 +65,7 @@
4165

4266
it('aborts if stack has stub files that does not exists', function (): void {
4367
// Arrange
44-
$mock = Mockery::mock(new React());
68+
$mock = Mockery::mock($this->reactStack);
4569
$mock->shouldReceive('getStubs')->andReturn(collect([
4670
FileSystem::stubs('none-existent.file'),
4771
]));
@@ -57,7 +81,7 @@
5781

5882
it('installs proper files', function (): void {
5983
// Arrange
60-
$mock = Mockery::mock(new React());
84+
$mock = Mockery::mock($this->reactStack);
6185
$mock->shouldReceive('getStubs')->andReturn(collect([
6286
FileSystem::stubs('components/react/ts/infinite-scroll.tsx'),
6387
]));

tests/Unit/Stacks/ReactTest.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Codelabmw\InfiniteScroll\Stacks\React;
6+
use Illuminate\Support\Collection;
7+
8+
beforeEach(function (): void {
9+
$this->react = new React();
10+
$this->originalBasePath = base_path();
11+
12+
$this->tmpBase = sys_get_temp_dir().'/react_stack_test_'.uniqid();
13+
mkdir($this->tmpBase, 0777, true);
14+
15+
// Use Laravel's app()->setBasePath() to mock base_path
16+
app()->setBasePath($this->tmpBase);
17+
});
18+
19+
afterEach(function (): void {
20+
// Restore original base path
21+
app()->setBasePath($this->originalBasePath);
22+
23+
if (is_dir($this->tmpBase)) {
24+
$files = new RecursiveIteratorIterator(
25+
new RecursiveDirectoryIterator($this->tmpBase, RecursiveDirectoryIterator::SKIP_DOTS),
26+
RecursiveIteratorIterator::CHILD_FIRST
27+
);
28+
foreach ($files as $file) {
29+
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
30+
}
31+
rmdir($this->tmpBase);
32+
}
33+
});
34+
35+
test('getLabel returns React', function (): void {
36+
// Act
37+
$label = $this->react->getLabel();
38+
39+
// Assert
40+
expect($label)->toBe('React');
41+
});
42+
43+
test('getDefaultInstallationPath returns expected path', function (): void {
44+
// Act
45+
$path = $this->react->getDefaultInstallationPath();
46+
47+
// Assert
48+
expect($path)->toBe(resource_path('js/components'));
49+
});
50+
51+
test('getStubs returns correct stub path', function (): void {
52+
// Act
53+
$stubs = $this->react->getStubs();
54+
55+
// Assert
56+
expect($stubs)->toBeInstanceOf(Collection::class);
57+
expect($stubs->first())->toContain('stubs/components/react/ts/infinite-scroll.tsx');
58+
});
59+
60+
test('isCurrent returns true if @inertiajs/react is in dependencies', function (): void {
61+
// Arrange
62+
$packageJson = [
63+
'dependencies' => [
64+
'@inertiajs/react' => '^1.0.0',
65+
],
66+
];
67+
file_put_contents($this->tmpBase.'/package.json', json_encode($packageJson));
68+
69+
// Act
70+
$result = $this->react->isCurrent();
71+
72+
// Assert
73+
expect($result)->toBeTrue();
74+
});
75+
76+
test('isCurrent returns true if @inertiajs/react is in devDependencies', function (): void {
77+
// Arrange
78+
$packageJson = [
79+
'devDependencies' => [
80+
'@inertiajs/react' => '^1.0.0',
81+
],
82+
];
83+
file_put_contents($this->tmpBase.'/package.json', json_encode($packageJson));
84+
85+
// Act
86+
$result = $this->react->isCurrent();
87+
88+
// Assert
89+
expect($result)->toBeTrue();
90+
});
91+
92+
test('isCurrent returns false if @inertiajs/react is not present', function (): void {
93+
// Arrange
94+
$packageJson = [
95+
'dependencies' => [
96+
'vue' => '^3.0.0',
97+
],
98+
];
99+
file_put_contents($this->tmpBase.'/package.json', json_encode($packageJson));
100+
101+
// Act
102+
$result = $this->react->isCurrent();
103+
104+
// Assert
105+
expect($result)->toBeFalse();
106+
});
107+
108+
test('isCurrent returns false if package.json is missing', function (): void {
109+
// Act
110+
$result = $this->react->isCurrent();
111+
112+
// Assert
113+
expect($result)->toBeFalse();
114+
});
115+
116+
test('isCurrent returns false if package.json is malformed', function (): void {
117+
// Arrange
118+
file_put_contents($this->tmpBase.'/package.json', '{not json');
119+
120+
// Act
121+
$result = $this->react->isCurrent();
122+
123+
// Assert
124+
expect($result)->toBeFalse();
125+
});

tests/Unit/Support/FileSystemTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@
100100
expect(file_get_contents($dest))->toBe('copy me');
101101
});
102102

103+
test('getContents returns the contents of a file', function (): void {
104+
// Arrange
105+
$file = $this->tmpDir.'/test.txt';
106+
file_put_contents($file, 'test');
107+
108+
// Act
109+
$contents = FileSystem::getContents($file);
110+
111+
// Assert
112+
expect($contents)->toBe('test');
113+
});
114+
103115
test('stubs returns correct stubs path', function (): void {
104116
// Arrange
105117
// No setup needed

tests/Unit/SupportedStacksTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
// Arrange
1010
$mock = Mockery::mock(new SupportedStacks());
1111
$mock->shouldReceive('get')->andReturn([
12-
'React' => React::class,
12+
React::class,
1313
]);
1414

1515
$this->app->bind(SupportedStacks::class, fn () => $mock);
1616

1717
// Act & Assert
1818
expect((new SupportedStacks)->get())->toEqual([
19-
'React' => React::class,
19+
React::class,
2020
]);
2121
});

0 commit comments

Comments
 (0)