From 7500de1ceb754e2ad9b62d7d7933391cedda20e3 Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Mon, 13 Oct 2025 20:35:27 +0100 Subject: [PATCH 1/5] test(remove): testing remove command --- src/Commands/RemoveComponentCommand.php | 16 +++---- src/Services/ComponentRemover.php | 27 ++++++++--- tests/Feature/Commands/RemoveCommandTest.php | 49 ++++++++++++++++++++ 3 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 tests/Feature/Commands/RemoveCommandTest.php diff --git a/src/Commands/RemoveComponentCommand.php b/src/Commands/RemoveComponentCommand.php index a9de8f7..5fd9619 100644 --- a/src/Commands/RemoveComponentCommand.php +++ b/src/Commands/RemoveComponentCommand.php @@ -2,11 +2,8 @@ namespace Sheaf\Cli\Commands; -use Sheaf\Cli\Services\ComponentInstaller; -use Sheaf\Cli\Support\InstallationConfig; use Illuminate\Console\Command; use Illuminate\Support\Arr; -use Illuminate\Support\Str; use Sheaf\Cli\Services\ComponentRemover; use function Laravel\Prompts\text; @@ -14,9 +11,6 @@ class RemoveComponentCommand extends Command { - public function __construct(protected ComponentRemover $componentRemover) { - parent::__construct(); - } /** * The name and signature of the console command. * @@ -39,14 +33,20 @@ public function handle() { $componentNames = $this->getComponentName(); + $success = Command::SUCCESS; + + $componentRemover = new ComponentRemover($this); foreach ($componentNames as $name) { $this->banner("Removing all $name files"); - $this->componentRemover->remove($name); + $success = $componentRemover->remove($name); + } + + if($success === Command::SUCCESS) { + $this->info("+ updated sheaf-lock.json and sheaf.json files"); } - $this->info("+ updated sheaf-lock.json and sheaf.json files"); return Command::SUCCESS; } diff --git a/src/Services/ComponentRemover.php b/src/Services/ComponentRemover.php index 21da607..c26b43c 100644 --- a/src/Services/ComponentRemover.php +++ b/src/Services/ComponentRemover.php @@ -3,36 +3,51 @@ namespace Sheaf\Cli\Services; +use Illuminate\Console\Command; use Illuminate\Support\Facades\File; -use Symfony\Component\Console\Output\ConsoleOutput; - +use Illuminate\Console\Concerns\InteractsWithIO; +use Laravel\Prompts\Output\ConsoleOutput; +use Symfony\Component\Console\Input\StringInput; class ComponentRemover { protected $output; protected $componentName; - public function __construct() + public function __construct(protected $command) { - $this->output = new ConsoleOutput(); } public function remove($name) { $this->componentName = $name; + $isExists = $this->checkComponentExistence(); + + if(!$isExists) { + $this->message("Component is not installed in this project."); + return Command::FAILURE; + } + $this->deleteComponentFiles(); $this->cleaningSheafLock($name); } + protected function checkComponentExistence() + { + + return File::exists(resource_path("views/components/ui/{$this->componentName}")) || + File::exists(resource_path("views/components/ui/{$this->componentName}.blade.php")); + } + protected function deleteComponentFiles() { $componentDirectory = resource_path("views/components/ui/{$this->componentName}"); if (File::isDirectory($componentDirectory)) { File::deleteDirectory($componentDirectory); - $this->message("+ Deleted directory: $componentDirectory"); + $this->message("+ Deleted directory: resources/views/components/ui/{$this->componentName}"); } $componentFile = resource_path("views/components/ui/{$this->componentName}.blade.php"); @@ -136,6 +151,6 @@ protected function deleteHelperFile($helper) protected function message(string $message) { - $this->output->writeln("$message"); + $this->command->info("$message"); } } diff --git a/tests/Feature/Commands/RemoveCommandTest.php b/tests/Feature/Commands/RemoveCommandTest.php new file mode 100644 index 0000000..0bac6c0 --- /dev/null +++ b/tests/Feature/Commands/RemoveCommandTest.php @@ -0,0 +1,49 @@ +artisan("sheaf:install $component") + ->assertExitCode(0) + ->run(); + + + expect(File::isDirectory($componentDirectory))->toBeTrue(); + + $this->view("components.ui.$component.index"); + + $this->artisan("sheaf:remove $component") + ->assertExitCode(0) + ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") + ->run(); + + expect(File::isDirectory($componentDirectory))->toBeFalse(); + + $sheafLock = File::get(base_path("sheaf-lock.json")); + + expect($sheafLock)->not->toContain("$component"); + +}); + +it("quits successfully when attempting to remove a non-installed component", function () { + + $component = 'modal'; + $componentDirectory = resource_path("views/components/ui/$component"); + + + $this->artisan("sheaf:remove $component") + ->assertExitCode(0) + ->expectsOutputToContain("Component is not installed in this project.") + ->run(); + + expect(File::isDirectory($componentDirectory))->toBeFalse(); + + $sheafLock = File::get(base_path("sheaf-lock.json")); + + expect($sheafLock)->not->toContain("$component"); + +}); From 3ad95634dd45d71718e731bea71e718175f1734f Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Mon, 13 Oct 2025 21:03:25 +0100 Subject: [PATCH 2/5] test(remove): test removing component with dependencies and helpers --- src/Services/ComponentRemover.php | 5 +- tests/Feature/Commands/InstallCommandTest.php | 60 ++++++--------- tests/Feature/Commands/RemoveCommandTest.php | 74 +++++++++++++++---- 3 files changed, 85 insertions(+), 54 deletions(-) diff --git a/src/Services/ComponentRemover.php b/src/Services/ComponentRemover.php index c26b43c..a24eb60 100644 --- a/src/Services/ComponentRemover.php +++ b/src/Services/ComponentRemover.php @@ -5,9 +5,6 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\File; -use Illuminate\Console\Concerns\InteractsWithIO; -use Laravel\Prompts\Output\ConsoleOutput; -use Symfony\Component\Console\Input\StringInput; class ComponentRemover { @@ -93,7 +90,7 @@ protected function cleaningDependencies(&$sheafLock) $remainingComponents = $this->removeComponentFromList($components); if (empty($remainingComponents)) { - $remover = new self(); + $remover = new self($this->command); $remover->remove($dep); unset($sheafLock['internalDependencies'][$dep]); diff --git a/tests/Feature/Commands/InstallCommandTest.php b/tests/Feature/Commands/InstallCommandTest.php index 7c71de1..ca3d224 100644 --- a/tests/Feature/Commands/InstallCommandTest.php +++ b/tests/Feature/Commands/InstallCommandTest.php @@ -1,30 +1,14 @@ artisan("sheaf:install separator") ->assertExitCode(0) ->run(); $this->view('components.ui.separator.index'); + $this->artisan("sheaf:remove separator"); }); it("installs a component along with its dependencies when confirmed", function () { @@ -35,24 +19,27 @@ ->run(); $this->view("components.ui.radio.group", ['slot' => 'default']); + + $this->artisan("sheaf:remove radio"); }); it("overwrites existing component files when forced", function () { $this->artisan("sheaf:install separator") - ->assertExitCode(0) - ->run(); + ->assertExitCode(0) + ->run(); $this->artisan("sheaf:install separator") - ->expectsQuestion("Component 'Separator' already exists. What would you like to do?", "overwrite") - ->expectsQuestion("All the component files will be overwritten, you might lose your modifications. are you sure you want to processed?", "yes") - ->expectsOutputToContain("All component files will be overwritten.") - ->assertExitCode(0) - ->run(); + ->expectsQuestion("Component 'Separator' already exists. What would you like to do?", "overwrite") + ->expectsQuestion("All the component files will be overwritten, you might lose your modifications. are you sure you want to processed?", "yes") + ->expectsOutputToContain("All component files will be overwritten.") + ->assertExitCode(0) + ->run(); $this->view("components.ui.separator.index"); + $this->artisan("sheaf:remove separator"); }); it("installs only dependencies when the component already exists and that option is chosen", function () { @@ -60,26 +47,27 @@ $command = 'sheaf:install separator'; $this->artisan($command) - ->assertExitCode(0) - ->run(); + ->assertExitCode(0) + ->run(); $this->artisan($command) - ->expectsQuestion("Component 'Separator' already exists. What would you like to do?", "dependencies") - ->expectsOutputToContain("Skipping component files, checking dependencies...") - ->assertExitCode(0) - ->run(); + ->expectsQuestion("Component 'Separator' already exists. What would you like to do?", "dependencies") + ->expectsOutputToContain("Skipping component files, checking dependencies...") + ->assertExitCode(0) + ->run(); $this->view("components.ui.separator.index"); + $this->artisan("sheaf:remove separator"); }); it("simulates component installation with the dry-run option", function () { $this->artisan("sheaf:install alerts --dry-run") - ->expectsOutputToContain("Preview: Installing Alerts (Dry Run)") - ->expectsOutputToContain("Will create") - ->assertExitCode(0) - ->run(); + ->expectsOutputToContain("Preview: Installing Alerts (Dry Run)") + ->expectsOutputToContain("Will create") + ->assertExitCode(0) + ->run(); expect(view()->exists("components.ui.alerts.index"))->toBeFalse(); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Commands/RemoveCommandTest.php b/tests/Feature/Commands/RemoveCommandTest.php index 0bac6c0..a52826f 100644 --- a/tests/Feature/Commands/RemoveCommandTest.php +++ b/tests/Feature/Commands/RemoveCommandTest.php @@ -1,5 +1,7 @@ toBeTrue(); $this->view("components.ui.$component.index"); - + $this->artisan("sheaf:remove $component") - ->assertExitCode(0) - ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") - ->run(); - + ->assertExitCode(0) + ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") + ->run(); + expect(File::isDirectory($componentDirectory))->toBeFalse(); - + $sheafLock = File::get(base_path("sheaf-lock.json")); expect($sheafLock)->not->toContain("$component"); - }); it("quits successfully when attempting to remove a non-installed component", function () { @@ -34,16 +35,61 @@ $component = 'modal'; $componentDirectory = resource_path("views/components/ui/$component"); - + $this->artisan("sheaf:remove $component") - ->assertExitCode(0) - ->expectsOutputToContain("Component is not installed in this project.") - ->run(); - + ->assertExitCode(0) + ->expectsOutputToContain("Component is not installed in this project.") + ->run(); + expect(File::isDirectory($componentDirectory))->toBeFalse(); - + $sheafLock = File::get(base_path("sheaf-lock.json")); expect($sheafLock)->not->toContain("$component"); - }); + + +// it("removes an installed component with dependencies", function () { + +// $component = 'select'; +// $dependency = 'icon'; +// $helper = 'popup'; +// $baseDirectory = resource_path("views/components/ui/"); + +// //* using force option to ensure the command runs +// $this->artisan("sheaf:install $component --force --internal-deps") +// ->assertExitCode(0) +// ->run(); + +// expect(File::isDirectory("$baseDirectory/$component"))->toBeTrue(); +// expect(File::isDirectory("$baseDirectory/$dependency"))->toBeTrue(); +// expect(File::exists("$baseDirectory/$helper.blade.php"))->toBeTrue(); + +// expect(view()->exists("components.ui.$component.index"))->toBeTrue(); +// expect(view()->exists("components.ui.$dependency.index"))->toBeTrue(); +// expect(view()->exists("components.ui.$helper"))->toBeTrue(); + + +// $output = new BufferedConsoleOutput(); +// Artisan::call("sheaf:remove $component", [], $output); +// $sheafLock = File::get(base_path("sheaf-lock.json")); +// dd($output->fetch(), $sheafLock); + + +// $this->artisan("sheaf:remove $component") +// ->assertExitCode(0) +// ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") +// ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$dependency") +// ->expectsOutputToContain("Removed internal dependency: $dependency (no longer used.)") +// ->expectsOutputToContain("Removed helper: $helper (no longer used.)") +// ->run(); +// // Deleted directory: resources/views/components/ui/icon + +// expect(File::isDirectory("$baseDirectory/$component"))->toBeFalse(); +// expect(File::isDirectory("$baseDirectory/$dependency"))->toBeFalse(); + +// $sheafLock = File::get(base_path("sheaf-lock.json")); + +// expect($sheafLock)->not->toContain("$component"); +// expect($sheafLock)->not->toContain("$dependency"); +// })->only(); From ab87af13b823ae7f8cb9c892af1692c8deee3cdf Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Mon, 13 Oct 2025 21:05:50 +0100 Subject: [PATCH 3/5] fix: typo --- src/Services/ComponentInstaller.php | 2 +- tests/Feature/Commands/InstallCommandTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Services/ComponentInstaller.php b/src/Services/ComponentInstaller.php index 6e8e07b..dde7200 100644 --- a/src/Services/ComponentInstaller.php +++ b/src/Services/ComponentInstaller.php @@ -126,6 +126,6 @@ public function handleOverwriteChoice() public function confirmDestructiveAction() { - return confirm("All the component files will be overwritten, you might lose your modifications. are you sure you want to processed?"); + return confirm("All the component files will be overwritten, you might lose your modifications. are you sure you want to proceed?"); } } diff --git a/tests/Feature/Commands/InstallCommandTest.php b/tests/Feature/Commands/InstallCommandTest.php index ca3d224..415aaaf 100644 --- a/tests/Feature/Commands/InstallCommandTest.php +++ b/tests/Feature/Commands/InstallCommandTest.php @@ -32,7 +32,7 @@ $this->artisan("sheaf:install separator") ->expectsQuestion("Component 'Separator' already exists. What would you like to do?", "overwrite") - ->expectsQuestion("All the component files will be overwritten, you might lose your modifications. are you sure you want to processed?", "yes") + ->expectsQuestion("All the component files will be overwritten, you might lose your modifications. are you sure you want to proceed?", "yes") ->expectsOutputToContain("All component files will be overwritten.") ->assertExitCode(0) ->run(); From d0cdf259156774ba777f8f927bf556e96517e805 Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Mon, 13 Oct 2025 21:08:55 +0100 Subject: [PATCH 4/5] test(remove): test removing component with dependencies and helpers --- tests/Feature/Commands/RemoveCommandTest.php | 63 +++++++++----------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/tests/Feature/Commands/RemoveCommandTest.php b/tests/Feature/Commands/RemoveCommandTest.php index a52826f..026e038 100644 --- a/tests/Feature/Commands/RemoveCommandTest.php +++ b/tests/Feature/Commands/RemoveCommandTest.php @@ -49,47 +49,40 @@ }); -// it("removes an installed component with dependencies", function () { +it("removes an installed component with dependencies", function () { -// $component = 'select'; -// $dependency = 'icon'; -// $helper = 'popup'; -// $baseDirectory = resource_path("views/components/ui/"); + $component = 'select'; + $dependency = 'icon'; + $helper = 'popup'; + $baseDirectory = resource_path("views/components/ui/"); -// //* using force option to ensure the command runs -// $this->artisan("sheaf:install $component --force --internal-deps") -// ->assertExitCode(0) -// ->run(); - -// expect(File::isDirectory("$baseDirectory/$component"))->toBeTrue(); -// expect(File::isDirectory("$baseDirectory/$dependency"))->toBeTrue(); -// expect(File::exists("$baseDirectory/$helper.blade.php"))->toBeTrue(); - -// expect(view()->exists("components.ui.$component.index"))->toBeTrue(); -// expect(view()->exists("components.ui.$dependency.index"))->toBeTrue(); -// expect(view()->exists("components.ui.$helper"))->toBeTrue(); + //* using force option to ensure the command runs + $this->artisan("sheaf:install $component --force --internal-deps") + ->assertExitCode(0) + ->run(); + expect(File::isDirectory("$baseDirectory/$component"))->toBeTrue(); + expect(File::isDirectory("$baseDirectory/$dependency"))->toBeTrue(); + expect(File::exists("$baseDirectory/$helper.blade.php"))->toBeTrue(); -// $output = new BufferedConsoleOutput(); -// Artisan::call("sheaf:remove $component", [], $output); -// $sheafLock = File::get(base_path("sheaf-lock.json")); -// dd($output->fetch(), $sheafLock); + expect(view()->exists("components.ui.$component.index"))->toBeTrue(); + expect(view()->exists("components.ui.$dependency.index"))->toBeTrue(); + expect(view()->exists("components.ui.$helper"))->toBeTrue(); -// $this->artisan("sheaf:remove $component") -// ->assertExitCode(0) -// ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") -// ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$dependency") -// ->expectsOutputToContain("Removed internal dependency: $dependency (no longer used.)") -// ->expectsOutputToContain("Removed helper: $helper (no longer used.)") -// ->run(); -// // Deleted directory: resources/views/components/ui/icon + $this->artisan("sheaf:remove $component") + ->assertExitCode(0) + ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") + ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$dependency") + ->expectsOutputToContain("Removed internal dependency: $dependency (no longer used.)") + ->expectsOutputToContain("Removed helper: $helper (no longer used.)") + ->run(); -// expect(File::isDirectory("$baseDirectory/$component"))->toBeFalse(); -// expect(File::isDirectory("$baseDirectory/$dependency"))->toBeFalse(); + expect(File::isDirectory("$baseDirectory/$component"))->toBeFalse(); + expect(File::isDirectory("$baseDirectory/$dependency"))->toBeFalse(); -// $sheafLock = File::get(base_path("sheaf-lock.json")); + $sheafLock = File::get(base_path("sheaf-lock.json")); -// expect($sheafLock)->not->toContain("$component"); -// expect($sheafLock)->not->toContain("$dependency"); -// })->only(); + expect($sheafLock)->not->toContain("$component"); + expect($sheafLock)->not->toContain("$dependency"); +}); From 540b804018808c100a0448fd3d7b044a236fbca2 Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Mon, 13 Oct 2025 21:14:46 +0100 Subject: [PATCH 5/5] fix: checking if internal dependencies and helpers keys are exists in sheaf lock before access them --- src/Services/ComponentRemover.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Services/ComponentRemover.php b/src/Services/ComponentRemover.php index a24eb60..13aa063 100644 --- a/src/Services/ComponentRemover.php +++ b/src/Services/ComponentRemover.php @@ -86,6 +86,10 @@ protected function cleaningFiles(&$sheafLock) protected function cleaningDependencies(&$sheafLock) { + if(!isset($sheafLock['internalDependencies'])) { + return; + } + foreach ($sheafLock['internalDependencies'] as $dep => $components) { $remainingComponents = $this->removeComponentFromList($components); @@ -103,6 +107,10 @@ protected function cleaningDependencies(&$sheafLock) protected function cleaningHelpers(&$sheafLock) { + if(!isset($sheafLock['helpers'])) { + return; + } + foreach ($sheafLock['helpers'] as $helper => $components) { $remainingComponents = $this->removeComponentFromList($components);