Skip to content
This repository was archived by the owner on Feb 14, 2026. It is now read-only.

Commit 15dac5f

Browse files
Merge pull request #306 from CodeWithDennis/improve-current-command
Improve current command
2 parents 629d6d7 + 6541818 commit 15dac5f

File tree

7 files changed

+222
-134
lines changed

7 files changed

+222
-134
lines changed

src/Commands/FilamentTestsCommand.php

Lines changed: 22 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,150 +2,46 @@
22

33
namespace CodeWithDennis\FilamentTests\Commands;
44

5-
use App\Filament\Resources\Users\UserResource;
5+
use CodeWithDennis\FilamentTests\Concerns\Commands\InteractsWithFilesystem;
6+
use CodeWithDennis\FilamentTests\Concerns\Commands\InteractsWithUserInput;
7+
use CodeWithDennis\FilamentTests\TestRenderers\BaseTest;
68
use CodeWithDennis\FilamentTests\TestRenderers\BeforeEach;
7-
use CodeWithDennis\FilamentTests\TestRenderers\Resources\Pages\Create\CanRenderCreatePageTest;
8-
use CodeWithDennis\FilamentTests\TestRenderers\Resources\Pages\Edit\CanRenderEditPageTest;
99
use CodeWithDennis\FilamentTests\TestRenderers\Resources\Pages\Index\CanRenderIndexPageTest;
10-
use Filament\Facades\Filament;
1110
use Illuminate\Console\Command;
12-
use Illuminate\Filesystem\Filesystem;
13-
use Illuminate\Support\Collection;
14-
use Illuminate\Support\Facades\Process;
15-
16-
use function Laravel\Prompts\multiselect;
1711

1812
class FilamentTestsCommand extends Command
1913
{
20-
protected $signature = 'make:filament-test
21-
{--skip-pint : Skip running Pint on generated files}';
22-
23-
protected $description = 'Create a new test for a Filament component';
14+
use InteractsWithFilesystem;
15+
use InteractsWithUserInput;
2416

25-
// protected ?Collection $resources = null;
17+
protected $signature = 'make:filament-test
18+
{--skip-pint : Skip running Pint on generated files}
19+
{--force : Overwrite existing test files without confirmation}';
2620

27-
public function __construct(
28-
protected ?Collection $resources = null,
29-
protected ?Collection $panels = null,
30-
protected array $generatedFiles = [],
31-
protected ?Filesystem $files = null,
32-
) {
33-
$this->resources ??= collect();
34-
$this->panels ??= collect();
35-
$this->files ??= new Filesystem;
36-
parent::__construct();
37-
}
21+
protected $description = 'Create tests for your Filament resources';
3822

3923
public function handle(): void
4024
{
41-
// $this->panels = $this->askUserToSelectPanels();
42-
// $this->resources = $this->askUserToSelectWhichResourcesFromTheSelectedPanel();
43-
44-
$this->panels = collect(['admin']);
45-
$this->resources = collect([
46-
'admin' => [
47-
UserResource::class,
48-
],
49-
]);
50-
51-
foreach ($this->resources as $resourceClasses) {
52-
foreach ($resourceClasses as $resourceClass) {
53-
$rendered = $this->renderTestsForResource($resourceClass);
54-
55-
$filePath = $this->getTestFilePath($resourceClass);
56-
$this->files->ensureDirectoryExists(dirname($filePath));
57-
58-
file_put_contents($filePath, $rendered);
25+
$this->panels = $this->askUserToSelectPanels();
26+
$this->resources = $this->askUserToSelectResourcesFromTheSelectedPanels();
5927

60-
$this->generatedFiles[] = $filePath;
28+
$this->generateTests();
6129

62-
$this->info("Created test for {$resourceClass}{$filePath}");
63-
}
64-
}
30+
$this->showGenerationSummary();
6531

66-
$this->runPintOnGeneratedFiles();
32+
$this->runPintOnGeneratedTests();
6733
}
6834

6935
/**
70-
* Render all tests for a single resource.
36+
* @return class-string<BaseTest>[]
7137
*/
72-
protected function renderTestsForResource(string $resourceClass): string
38+
protected function getRenderers(): array
7339
{
74-
return implode("\n\n", [
75-
'<?php',
76-
BeforeEach::build($resourceClass)->render(),
77-
CanRenderIndexPageTest::build($resourceClass)->render(),
78-
// CanRenderCreatePageTest::build($resourceClass)->render(),
79-
// CanRenderEditPageTest::build($resourceClass)->render(),
80-
]);
81-
}
82-
83-
protected function runPintOnGeneratedFiles(): void
84-
{
85-
if ($this->generatedFiles === []) {
86-
return;
87-
}
88-
89-
if ($this->option('skip-pint')) {
90-
return;
91-
}
92-
93-
$files = implode(' ', $this->generatedFiles);
94-
95-
Process::run("vendor/bin/pint {$files}");
96-
}
97-
98-
protected function getTestFilePath(string $resourceClass): string
99-
{
100-
$relativeClass = str($resourceClass)
101-
->replaceFirst('App\\', '')
102-
->replace('\\', '/');
103-
104-
return base_path("tests/Feature/{$relativeClass}Test.php");
105-
}
106-
107-
protected function askUserToSelectPanels(): Collection
108-
{
109-
$allPanels = collect(Filament::getPanels());
110-
111-
$selectedPanelIds = multiselect(
112-
label: 'Which Filament panel do you want to use?',
113-
options: $allPanels->mapWithKeys(fn ($panel) => [
114-
$panel->getId() => $panel->getId(),
115-
])->toArray(),
116-
);
117-
118-
return collect($selectedPanelIds);
119-
}
120-
121-
public function askUserToSelectWhichResourcesFromTheSelectedPanel(): Collection
122-
{
123-
$resourcesByPanel = $this->panels->mapWithKeys(fn ($panelId) => [
124-
$panelId => collect(Filament::getPanel($panelId)?->getResources() ?? [])
125-
->mapWithKeys(fn ($resource) => [$resource => class_basename($resource)])
126-
->toArray(),
127-
]);
128-
129-
$selectedResources = collect();
130-
131-
foreach ($this->panels as $panelId) {
132-
$resources = $resourcesByPanel[$panelId] ?? [];
133-
134-
if (empty($resources)) {
135-
continue;
136-
}
137-
138-
$selected = multiselect(
139-
label: "Select resources for panel: {$panelId}",
140-
options: $resources,
141-
required: true,
142-
);
143-
144-
if ($selected !== []) {
145-
$selectedResources[$panelId] = $selected;
146-
}
147-
}
148-
149-
return $selectedResources;
40+
return [
41+
BeforeEach::class,
42+
CanRenderIndexPageTest::class,
43+
// CanRenderCreatePageTest::class,
44+
// CanRenderEditPageTest::class,
45+
];
15046
}
15147
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
namespace CodeWithDennis\FilamentTests\Concerns\Commands;
4+
5+
use CodeWithDennis\FilamentTests\TestRenderers\BaseTest;
6+
use Illuminate\Support\Facades\File;
7+
use Illuminate\Support\Facades\Process;
8+
9+
use function Laravel\Prompts\confirm;
10+
use function Laravel\Prompts\info;
11+
use function Laravel\Prompts\table;
12+
use function Laravel\Prompts\warning;
13+
14+
trait InteractsWithFilesystem
15+
{
16+
protected array $generatedFiles = [];
17+
18+
protected function getGeneratedFiles(): array
19+
{
20+
return $this->generatedFiles;
21+
}
22+
23+
protected function runPintOnGeneratedTests(): void
24+
{
25+
if (empty($this->getGeneratedFiles()) || $this->option('skip-pint')) {
26+
return;
27+
}
28+
29+
$files = collect($this->getGeneratedFiles())
30+
->map(fn ($resources) => collect($resources)->pluck('path'))
31+
->flatten()
32+
->implode(' ');
33+
34+
Process::run("vendor/bin/pint {$files}");
35+
}
36+
37+
protected function getTestFilePath(string $resourceClass): string
38+
{
39+
$relativeClass = str($resourceClass)
40+
->replaceFirst('App\\', '')
41+
->replace('\\', '/');
42+
43+
return base_path("tests/Feature/{$relativeClass}Test.php");
44+
}
45+
46+
protected function generateTestsForSelectedResource(string $resource, ?string $panel = null): void
47+
{
48+
$filePath = $this->getTestFilePath($resource);
49+
$force = (bool) $this->option('force');
50+
51+
if (File::exists($filePath) && ! $force && ! confirm("The tests for {$resource} already exists. Do you want to overwrite it?", false)) {
52+
info("Skipped generating test for {$resource}.");
53+
54+
return;
55+
}
56+
57+
$renderResult = $this->renderTestsForResource($resource);
58+
59+
File::ensureDirectoryExists(dirname((string) $filePath));
60+
File::put($filePath, $renderResult['content']);
61+
62+
$panelKey = $panel ?? 'default';
63+
64+
$this->generatedFiles[$panelKey][$resource] = [
65+
'path' => $filePath,
66+
'num_tests' => (int) $renderResult['num_tests'] - 1, // -1 for the BeforeEach
67+
];
68+
}
69+
70+
protected function generateTests(): void
71+
{
72+
collect($this->getSelectedResources())
73+
->each(function (array $resources, string $panelId): void {
74+
collect($resources)
75+
->flatten()
76+
->each(fn (string $resourceClass) => $this->generateTestsForSelectedResource($resourceClass, $panelId));
77+
});
78+
}
79+
80+
protected function renderTestsForResource(string $resource): array
81+
{
82+
$renderers = collect($this->getRenderers());
83+
84+
$output = $renderers
85+
->map(fn (string $renderer) =>
86+
/** @var BaseTest $renderer */
87+
$renderer::build($resource)->render())
88+
->prepend('<?php')
89+
->implode("\n\n");
90+
91+
return [
92+
'content' => $output,
93+
'num_tests' => $renderers->count(),
94+
];
95+
}
96+
97+
protected function showGenerationSummary(): void
98+
{
99+
if (blank($this->getGeneratedFiles())) {
100+
warning('No test files were generated.');
101+
102+
return;
103+
}
104+
105+
$rows = collect($this->getGeneratedFiles())
106+
->flatMap(fn (array $resources, string $panelName) => collect($resources)
107+
->map(fn (array $data, string $resource): array => [
108+
$resource,
109+
$panelName,
110+
$data['num_tests'] ?? 0,
111+
])
112+
)
113+
->values()
114+
->all();
115+
116+
table(
117+
['Resource', 'Panel', '# Tests'],
118+
$rows
119+
);
120+
}
121+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace CodeWithDennis\FilamentTests\Concerns\Commands;
4+
5+
use Filament\Facades\Filament;
6+
use Filament\Panel;
7+
use Illuminate\Support\Collection;
8+
9+
use function Laravel\Prompts\multiselect;
10+
11+
trait InteractsWithUserInput
12+
{
13+
protected Collection $panels;
14+
15+
protected Collection $resources;
16+
17+
protected function getSelectedPanels(): Collection
18+
{
19+
return $this->panels ??= collect();
20+
}
21+
22+
protected function getSelectedResources(): Collection
23+
{
24+
return $this->resources ??= collect();
25+
}
26+
27+
protected function askUserToSelectPanels(): Collection
28+
{
29+
$allPanels = collect(Filament::getPanels());
30+
31+
if ($allPanels->count() === 1) {
32+
return collect([$allPanels->first()->getId()]);
33+
}
34+
35+
$options = $allPanels->mapWithKeys(fn (Panel $panel) => [$panel->getId() => $panel->getId()])->toArray();
36+
37+
return collect(multiselect(
38+
label: 'Which Filament panel/s do you want to generate tests for?',
39+
options: $options,
40+
required: true,
41+
hint: 'You can select multiple panels',
42+
));
43+
}
44+
45+
protected function askUserToSelectResourcesFromTheSelectedPanels(): Collection
46+
{
47+
$selectedResources = collect();
48+
49+
foreach ($this->getSelectedPanels() as $panelId) {
50+
$resources = collect(Filament::getPanel($panelId)?->getResources() ?? [])
51+
->mapWithKeys(fn (string $resource) => [$resource => class_basename($resource)])
52+
->toArray();
53+
54+
if (empty($resources)) {
55+
continue;
56+
}
57+
58+
$selected = multiselect(
59+
label: "Select resources for panel: {$panelId}",
60+
options: $resources,
61+
required: true,
62+
);
63+
64+
if ($selected !== []) {
65+
$selectedResources[$panelId] = $selected;
66+
}
67+
}
68+
69+
return $selectedResources;
70+
}
71+
}

src/Concerns/ExposesPublicMethodsToViews.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace CodeWithDennis\FilamentTests\Concerns;
44

5+
use CodeWithDennis\FilamentTests\TestRenderers\BaseTest;
56
use ReflectionClass;
67
use ReflectionMethod;
78

89
trait ExposesPublicMethodsToViews
910
{
1011
protected array $methodCache = [];
1112

12-
protected function extractPublicMethods($renderer): array
13+
protected function extractPublicMethods(BaseTest $renderer): array
1314
{
1415
if (! isset($this->methodCache[$renderer::class])) {
1516
$reflection = new ReflectionClass($renderer);

src/Concerns/HasFilamentResources.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace CodeWithDennis\FilamentTests\Concerns;
44

5+
use Filament\Resources\Resource;
6+
57
interface HasFilamentResources
68
{
79
public function getResourceClass(): ?string;
810

9-
public function getResource();
11+
public function getResource(): Resource;
1012
}

0 commit comments

Comments
 (0)