diff --git a/rector.php b/rector.php index cf667f12..f587882a 100644 --- a/rector.php +++ b/rector.php @@ -10,10 +10,11 @@ return RectorConfig::configure() ->withPaths([ __DIR__.'/src', - // __DIR__.'/tests', + __DIR__.'/tests', ]) ->withSkip([ AddOverrideAttributeToOverriddenMethodsRector::class, + __DIR__.'/tests/Unit/helpers.php', ]) ->withPreparedSets( deadCode: true, diff --git a/src/VersionCheck/Infrastructure/Services/PackagistVersionChecker.php b/src/VersionCheck/Infrastructure/Services/PackagistVersionChecker.php index 0421bef1..f256bcfd 100644 --- a/src/VersionCheck/Infrastructure/Services/PackagistVersionChecker.php +++ b/src/VersionCheck/Infrastructure/Services/PackagistVersionChecker.php @@ -92,7 +92,7 @@ private function fetchLatestVersion(): ?string } $body = wp_remote_retrieve_body($response); - $data = json_decode($body, true); + $data = json_decode((string) $body, true); if (! is_array($data)) { return null; diff --git a/tests/Feature/Modules/ThemeAutoDiscoveryTest.php b/tests/Feature/Modules/ThemeAutoDiscoveryTest.php index 499fcb94..bbe0b822 100644 --- a/tests/Feature/Modules/ThemeAutoDiscoveryTest.php +++ b/tests/Feature/Modules/ThemeAutoDiscoveryTest.php @@ -4,11 +4,11 @@ use Illuminate\Container\Container; -beforeEach(function () { +beforeEach(function (): void { $this->app = new Container; }); -it('can create container for auto discovery testing', function () { +it('can create container for auto discovery testing', function (): void { expect($this->app)->toBeInstanceOf(Container::class); }); diff --git a/tests/Feature/Modules/ThemeModuleIntegrationTest.php b/tests/Feature/Modules/ThemeModuleIntegrationTest.php index cbcf76f1..42abd63e 100644 --- a/tests/Feature/Modules/ThemeModuleIntegrationTest.php +++ b/tests/Feature/Modules/ThemeModuleIntegrationTest.php @@ -4,11 +4,11 @@ use Illuminate\Container\Container; -beforeEach(function () { +beforeEach(function (): void { $this->app = new Container; }); -it('can create container for testing', function () { +it('can create container for testing', function (): void { expect($this->app)->toBeInstanceOf(Container::class); }); diff --git a/tests/Feature/Option/OptionIntegrationTest.php b/tests/Feature/Option/OptionIntegrationTest.php index 38a8b483..05a367ac 100644 --- a/tests/Feature/Option/OptionIntegrationTest.php +++ b/tests/Feature/Option/OptionIntegrationTest.php @@ -2,32 +2,24 @@ declare(strict_types=1); -namespace Tests\Feature\Option; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Application\Services\OptionService; use Pollora\Support\Facades\Option; -final class OptionIntegrationTest extends TestCase -{ - public function test_facade_class_exists(): void - { - $this->assertTrue(class_exists(Option::class)); - } +describe('OptionIntegration', function (): void { + it('facade class exists', function (): void { + expect(class_exists(Option::class))->toBeTrue(); + }); - public function test_facade_has_correct_accessor(): void - { - $reflection = new \ReflectionClass(Option::class); + it('facade has correct accessor', function (): void { + $reflection = new ReflectionClass(Option::class); $method = $reflection->getMethod('getFacadeAccessor'); - $method->setAccessible(true); $accessor = $method->invoke(null); - $this->assertEquals(OptionService::class, $accessor); - } + expect($accessor)->toBe(OptionService::class); + }); - public function test_facade_has_forget_alias(): void - { - $this->assertTrue(method_exists(Option::class, 'forget')); - } -} + it('facade has forget alias', function (): void { + expect(method_exists(Option::class, 'forget'))->toBeTrue(); + }); +}); diff --git a/tests/Feature/Route/UI/Http/Controllers/FrontendControllerTest.php b/tests/Feature/Route/UI/Http/Controllers/FrontendControllerTest.php index 8f0e2141..37104c69 100644 --- a/tests/Feature/Route/UI/Http/Controllers/FrontendControllerTest.php +++ b/tests/Feature/Route/UI/Http/Controllers/FrontendControllerTest.php @@ -11,25 +11,25 @@ require_once dirname(__DIR__, 5).'/Unit/helpers.php'; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); $this->templateFinder = Mockery::mock(TemplateFinderInterface::class); $this->controller = new FrontendController($this->templateFinder); }); -describe('FrontendController', function () { - it('aborts when themes disabled', function () { - setWordPressFunction('wp_using_themes', fn () => false); +describe('FrontendController', function (): void { + it('aborts when themes disabled', function (): void { + setWordPressFunction('wp_using_themes', fn (): false => false); $request = Request::create('/test'); expect(fn () => $this->controller->handle($request)) ->toThrow(HttpException::class, 'Themes are disabled'); }); - it('renders blade view when available', function () { - setWordPressFunction('wp_using_themes', fn () => true); - setWordPressFunction('is_page', fn () => true); - setWordPressFunction('get_page_template', fn () => '/theme/page.php'); + it('renders blade view when available', function (): void { + setWordPressFunction('wp_using_themes', fn (): true => true); + setWordPressFunction('is_page', fn (): true => true); + setWordPressFunction('get_page_template', fn (): string => '/theme/page.php'); setWordPressFunction('apply_filters', fn ($filter, $value) => $value); $this->templateFinder->shouldReceive('getViewNameFromPath') @@ -50,11 +50,11 @@ expect($response->getContent())->toBe('Blade page content'); }); - it('falls back to php template', function () { + it('falls back to php template', function (): void { $templatePath = __DIR__.'/test-template.php'; - setWordPressFunction('wp_using_themes', fn () => true); - setWordPressFunction('is_page', fn () => true); - setWordPressFunction('get_page_template', fn () => $templatePath); + setWordPressFunction('wp_using_themes', fn (): true => true); + setWordPressFunction('is_page', fn (): true => true); + setWordPressFunction('get_page_template', fn (): string => $templatePath); setWordPressFunction('apply_filters', fn ($filter, $value) => $value); $this->templateFinder->shouldReceive('getViewNameFromPath') @@ -68,8 +68,8 @@ expect($response->getContent())->toBe('This is a PHP template'); }); - it('throws 404 when no template', function () { - setWordPressFunction('wp_using_themes', fn () => true); + it('throws 404 when no template', function (): void { + setWordPressFunction('wp_using_themes', fn (): true => true); setWordPressConditions([ 'is_page' => false, @@ -91,7 +91,7 @@ 'is_embed' => false, ]); - setWordPressFunction('get_index_template', fn () => ''); + setWordPressFunction('get_index_template', fn (): string => ''); setWordPressFunction('apply_filters', fn ($filter, $value) => $value); $this->templateFinder->shouldReceive('getViewNameFromPath') diff --git a/tests/Feature/Theme/SelfRegistrationTest.php b/tests/Feature/Theme/SelfRegistrationTest.php index 36572b25..bf666051 100644 --- a/tests/Feature/Theme/SelfRegistrationTest.php +++ b/tests/Feature/Theme/SelfRegistrationTest.php @@ -8,11 +8,11 @@ require_once dirname(__DIR__, 2).'/Unit/helpers.php'; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); - setWordPressFunction('get_stylesheet', fn () => 'my-theme'); - setWordPressFunction('get_stylesheet_directory', fn () => __DIR__.'/fixtures/my-theme'); + setWordPressFunction('get_stylesheet', fn (): string => 'my-theme'); + setWordPressFunction('get_stylesheet_directory', fn (): string => __DIR__.'/fixtures/my-theme'); // Create a minimal theme fixture $fixturePath = __DIR__.'/fixtures/my-theme'; @@ -22,7 +22,7 @@ } }); -afterEach(function () { +afterEach(function (): void { // Clean up fixture $fixturePath = __DIR__.'/fixtures/my-theme'; if (is_dir($fixturePath)) { @@ -32,8 +32,8 @@ } }); -describe('ThemeRegistrar integration', function () { - it('registers a theme with parsed headers from style.css', function () { +describe('ThemeRegistrar integration', function (): void { + it('registers a theme with parsed headers from style.css', function (): void { $parser = new WordPressThemeParser; $registrar = new ThemeRegistrar($this->app, $parser); @@ -46,7 +46,7 @@ expect($theme->isEnabled())->toBeTrue(); }); - it('can retrieve the active theme after registration', function () { + it('can retrieve the active theme after registration', function (): void { $parser = new WordPressThemeParser; $registrar = new ThemeRegistrar($this->app, $parser); @@ -57,7 +57,7 @@ expect($registrar->isThemeActive('other-theme'))->toBeFalse(); }); - it('can reset the active theme', function () { + it('can reset the active theme', function (): void { $parser = new WordPressThemeParser; $registrar = new ThemeRegistrar($this->app, $parser); diff --git a/tests/Pest.php b/tests/Pest.php index 01de20a9..67887abf 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -29,9 +29,7 @@ | */ -expect()->extend('toBeOne', function () { - return $this->toBe(1); -}); +expect()->extend('toBeOne', fn () => $this->toBe(1)); /* |-------------------------------------------------------------------------- @@ -44,7 +42,7 @@ | */ -function something() +function something(): void { // .. } diff --git a/tests/TestCase.php b/tests/TestCase.php index 638c7f0d..6f9fb943 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,7 +4,9 @@ namespace Tests; +use Dotenv\Repository\RepositoryBuilder; use Mockery as m; +use PhpOption\Option; use PHPUnit\Framework\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase @@ -13,7 +15,7 @@ protected function setUp(): void { parent::setUp(); - if (! class_exists('Dotenv\\Repository\\RepositoryBuilder')) { + if (! class_exists(RepositoryBuilder::class)) { eval('namespace Dotenv\\Repository; class RepositoryBuilder { public static function createWithDefaultAdapters() { @@ -33,7 +35,7 @@ public function get($key) { return null; } }'); } - if (! class_exists('PhpOption\\Option')) { + if (! class_exists(Option::class)) { eval('namespace PhpOption; class Option { public static function fromValue($value) { return new class($value) { diff --git a/tests/Unit/Ajax/AjaxActionTest.php b/tests/Unit/Ajax/AjaxActionTest.php index 84ac1913..bae8203c 100644 --- a/tests/Unit/Ajax/AjaxActionTest.php +++ b/tests/Unit/Ajax/AjaxActionTest.php @@ -18,29 +18,30 @@ public function execute($action): void } } -describe('AjaxAction', function () { - it('can be instantiated with valid parameters', function () { - $action = new AjaxAction('my_action', function () {}); +describe('AjaxAction', function (): void { + it('can be instantiated with valid parameters', function (): void { + $action = new AjaxAction('my_action', function (): void {}); expect($action->getName())->toBe('my_action') ->and($action->getUserType())->toBe(AjaxAction::BOTH_USERS) ->and(is_callable($action->getCallback()) || is_string($action->getCallback()))->toBeTrue(); }); - it('throws exception if name or callback is empty', function () { - expect(fn () => new AjaxAction('', function () {}))->toThrow(InvalidAjaxActionException::class) - ->and(fn () => new AjaxAction('my_action', null))->toThrow(InvalidAjaxActionException::class); + it('throws exception if name or callback is empty', function (): void { + expect(fn (): AjaxAction => new AjaxAction('', function (): void {}))->toThrow(InvalidAjaxActionException::class) + ->and(fn (): AjaxAction => new AjaxAction('my_action', null))->toThrow(InvalidAjaxActionException::class); }); - it('can set user type to logged or guest', function () { - $action = new AjaxAction('my_action', function () {}); + it('can set user type to logged or guest', function (): void { + $action = new AjaxAction('my_action', function (): void {}); $action->forLoggedUsers(); + expect($action->getUserType())->toBe(AjaxAction::LOGGED_USERS); $action->forGuestUsers(); expect($action->getUserType())->toBe(AjaxAction::GUEST_USERS); }); - it('isBothOrLoggedUsers and isBothOrGuestUsers logic works', function () { - $action = new AjaxAction('my_action', function () {}); + it('isBothOrLoggedUsers and isBothOrGuestUsers logic works', function (): void { + $action = new AjaxAction('my_action', function (): void {}); expect($action->isBothOrLoggedUsers())->toBeTrue() ->and($action->isBothOrGuestUsers())->toBeTrue(); $action->forLoggedUsers(); @@ -51,9 +52,9 @@ public function execute($action): void ->and($action->isBothOrLoggedUsers())->toBeFalse(); }); - it('registers via RegisterAjaxActionService on destruct', function () { + it('registers via RegisterAjaxActionService on destruct', function (): void { $mockService = new DummyRegisterAjaxActionService; - $action = new AjaxAction('my_action', function () {}, $mockService); + $action = new AjaxAction('my_action', function (): void {}, $mockService); unset($action); expect($mockService->calls)->toHaveCount(1) ->and($mockService->calls[0]->getName())->toBe('my_action'); diff --git a/tests/Unit/Ajax/WordPressAjaxActionRegistrarTest.php b/tests/Unit/Ajax/WordPressAjaxActionRegistrarTest.php index cde61fbf..3750afdd 100644 --- a/tests/Unit/Ajax/WordPressAjaxActionRegistrarTest.php +++ b/tests/Unit/Ajax/WordPressAjaxActionRegistrarTest.php @@ -9,13 +9,13 @@ use Pollora\Support\Facades\Action as ActionFacade; use Psr\Container\ContainerInterface; -beforeEach(function () { +beforeEach(function (): void { // Patch the Action facade statically for all tests in this file ActionFacade::swap(new class { public array $calls = []; - public function add($hook, $callback) + public function add($hook, $callback): void { $GLOBALS['pollora_action_calls'][] = [$hook, $callback]; } @@ -23,12 +23,12 @@ public function add($hook, $callback) $GLOBALS['pollora_action_calls'] = []; }); -afterEach(function () { +afterEach(function (): void { m::close(); }); -describe('WordPressAjaxActionRegistrar', function () { - it('registers both hooks for BOTH_USERS', function () { +describe('WordPressAjaxActionRegistrar', function (): void { + it('registers both hooks for BOTH_USERS', function (): void { $container = m::mock(ContainerInterface::class); $actionService = m::mock(Action::class); $actionService->shouldReceive('add')->andReturnUsing(function ($hook, $callback) use ($actionService) { @@ -38,13 +38,13 @@ public function add($hook, $callback) }); $container->shouldReceive('get')->with(Action::class)->andReturn($actionService); $registrar = new WordPressAjaxActionRegistrar($container); - $action = (new AjaxAction('my_action', function () {})); + $action = (new AjaxAction('my_action', function (): void {})); $registrar->register($action); expect($GLOBALS['pollora_action_calls'])->toContain(['wp_ajax_my_action', $action->getCallback()]) ->and($GLOBALS['pollora_action_calls'])->toContain(['wp_ajax_nopriv_my_action', $action->getCallback()]); }); - it('registers only wp_ajax for LOGGED_USERS', function () { + it('registers only wp_ajax for LOGGED_USERS', function (): void { $container = m::mock(ContainerInterface::class); $actionService = m::mock(Action::class); $actionService->shouldReceive('add')->andReturnUsing(function ($hook, $callback) use ($actionService) { @@ -54,13 +54,13 @@ public function add($hook, $callback) }); $container->shouldReceive('get')->with(Action::class)->andReturn($actionService); $registrar = new WordPressAjaxActionRegistrar($container); - $action = (new AjaxAction('my_action', function () {}))->forLoggedUsers(); + $action = (new AjaxAction('my_action', function (): void {}))->forLoggedUsers(); $registrar->register($action); expect($GLOBALS['pollora_action_calls'])->toContain(['wp_ajax_my_action', $action->getCallback()]) ->and($GLOBALS['pollora_action_calls'])->not->toContain(['wp_ajax_nopriv_my_action', $action->getCallback()]); }); - it('registers only wp_ajax_nopriv for GUEST_USERS', function () { + it('registers only wp_ajax_nopriv for GUEST_USERS', function (): void { $container = m::mock(ContainerInterface::class); $actionService = m::mock(Action::class); $actionService->shouldReceive('add')->andReturnUsing(function ($hook, $callback) use ($actionService) { @@ -70,7 +70,7 @@ public function add($hook, $callback) }); $container->shouldReceive('get')->with(Action::class)->andReturn($actionService); $registrar = new WordPressAjaxActionRegistrar($container); - $action = (new AjaxAction('my_action', function () {}))->forGuestUsers(); + $action = (new AjaxAction('my_action', function (): void {}))->forGuestUsers(); $registrar->register($action); expect($GLOBALS['pollora_action_calls'])->not->toContain(['wp_ajax_my_action', $action->getCallback()]) ->and($GLOBALS['pollora_action_calls'])->toContain(['wp_ajax_nopriv_my_action', $action->getCallback()]); diff --git a/tests/Unit/Asset/AssetDomainTest.php b/tests/Unit/Asset/AssetDomainTest.php index 63567135..f56d63fc 100644 --- a/tests/Unit/Asset/AssetDomainTest.php +++ b/tests/Unit/Asset/AssetDomainTest.php @@ -8,8 +8,8 @@ use Pollora\Asset\Domain\Models\ViteManager; use Pollora\Asset\Infrastructure\Repositories\AssetContainer; -describe('Asset domain model', function () { - it('can be instantiated with name, path, and attributes', function () { +describe('Asset domain model', function (): void { + it('can be instantiated with name, path, and attributes', function (): void { $asset = new Asset('main', 'assets/main.js', ['type' => 'js']); expect($asset->getName())->toBe('main'); expect($asset->getPath())->toBe('assets/main.js'); @@ -17,34 +17,34 @@ }); }); -describe('AssetFile domain model', function () { - it('can be instantiated and return filename and container', function () { +describe('AssetFile domain model', function (): void { + it('can be instantiated and return filename and container', function (): void { $file = new AssetFile('assets/app.css'); expect($file->getFilename())->toBe('assets/app.css'); expect($file->getAssetContainer())->toBe('theme'); }); - it('can set a custom asset container', function () { + it('can set a custom asset container', function (): void { $file = (new AssetFile('assets/app.css'))->from('custom'); expect($file->getAssetContainer())->toBe('custom'); }); - it('can be cast to string as filename', function () { + it('can be cast to string as filename', function (): void { $file = new AssetFile('assets/app.css'); expect((string) $file)->toBe('assets/app.css'); }); }); -describe('ViteManager domain stub', function () { - beforeEach(function () { +describe('ViteManager domain stub', function (): void { + beforeEach(function (): void { $app = Mockery::mock(Container::class)->makePartial(); - $app->shouldReceive('publicPath')->andReturnUsing(fn ($path = '') => '/tmp/public'.($path ? '/'.$path : '')); + $app->shouldReceive('publicPath')->andReturnUsing(fn ($path = ''): string => '/tmp/public'.($path ? '/'.$path : '')); $app->instance('path.public', '/tmp/public'); Container::setInstance($app); }); - afterEach(function () { + afterEach(function (): void { Container::setInstance(new Container); }); - it('returns stub values for all interface methods', function () { + it('returns stub values for all interface methods', function (): void { $vite = new ViteManager; expect($vite->container())->toBeInstanceOf(AssetContainer::class); expect($vite->getAssetUrls(['entry.js']))->toBeArray()->toBeEmpty(); diff --git a/tests/Unit/Attributes/ActionTest.php b/tests/Unit/Attributes/ActionTest.php index 9cbabde9..532fa974 100644 --- a/tests/Unit/Attributes/ActionTest.php +++ b/tests/Unit/Attributes/ActionTest.php @@ -6,19 +6,14 @@ use Pollora\Attributes\Action; use Pollora\Hook\Infrastructure\Services\Action as ActionService; -beforeEach(function () { +beforeEach(function (): void { // Mock Action service $this->mockAction = Mockery::mock(ActionService::class); // Create a fake service locator $this->mockServiceLocator = new class($this->mockAction) { - private $actionService; - - public function __construct($actionService) - { - $this->actionService = $actionService; - } + public function __construct(private $actionService) {} public function get($serviceClass) { @@ -34,24 +29,24 @@ public function get($serviceClass) class SingleActionClass { #[Action('test_action', priority: 10)] - public function actionMethod($param = null) + public function actionMethod($param = null): string { // Test method - return $param ? "processed_{$param}" : 'processed'; + return $param ? 'processed_'.$param : 'processed'; } } class MultipleActionClass { #[Action('test_action', priority: 10)] - public function actionMethod($param = null) + public function actionMethod($param = null): string { // Test method - return $param ? "processed_{$param}" : 'processed'; + return $param ? 'processed_'.$param : 'processed'; } #[Action('another_action', priority: 20)] - public function anotherActionMethod() + public function anotherActionMethod(): string { // Another test method return 'another_processed'; @@ -61,35 +56,33 @@ public function anotherActionMethod() class DefaultPriorityActionClass { #[Action('test_action')] - public function actionMethod($param = null) + public function actionMethod($param = null): string { // Test method with default priority - return $param ? "processed_{$param}" : 'processed'; + return $param ? 'processed_'.$param : 'processed'; } } class CustomPriorityActionClass { #[Action('test_action', priority: 42)] - public function actionMethod($param = null) + public function actionMethod($param = null): string { // Test method with custom priority - return $param ? "processed_{$param}" : 'processed'; + return $param ? 'processed_'.$param : 'processed'; } } -it('registers an action hook correctly', function () { +it('registers an action hook correctly', function (): void { // Set up expectations $this->mockAction->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_action' - && is_array($callback) - && $callback[0] instanceof SingleActionClass - && $callback[1] === 'actionMethod' - && $priority === 10 - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_action' + && is_array($callback) + && $callback[0] instanceof SingleActionClass + && $callback[1] === 'actionMethod' + && $priority === 10 + && $acceptedArgs === 1); // Test the action attribute directly using handle method $testClass = new SingleActionClass; @@ -99,19 +92,17 @@ public function actionMethod($param = null) $actionAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $actionAttribute); }); -it('registers multiple action hooks with different priorities', function () { +it('registers multiple action hooks with different priorities', function (): void { $testClass = new MultipleActionClass; // Test first action $this->mockAction->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_action' - && is_array($callback) - && $callback[0] instanceof MultipleActionClass - && $callback[1] === 'actionMethod' - && $priority === 10; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_action' + && is_array($callback) + && $callback[0] instanceof MultipleActionClass + && $callback[1] === 'actionMethod' + && $priority === 10); $actionAttribute1 = new Action('test_action', 10); $methodReflection1 = new ReflectionMethod($testClass, 'actionMethod'); @@ -120,31 +111,27 @@ public function actionMethod($param = null) // Test second action $this->mockAction->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'another_action' - && is_array($callback) - && $callback[0] instanceof MultipleActionClass - && $callback[1] === 'anotherActionMethod' - && $priority === 20; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'another_action' + && is_array($callback) + && $callback[0] instanceof MultipleActionClass + && $callback[1] === 'anotherActionMethod' + && $priority === 20); $actionAttribute2 = new Action('another_action', 20); $methodReflection2 = new ReflectionMethod($testClass, 'anotherActionMethod'); $actionAttribute2->handle($this->mockServiceLocator, $testClass, $methodReflection2, $actionAttribute2); }); -it('registers an action hook with default priority (10)', function () { +it('registers an action hook with default priority (10)', function (): void { // Set up expectations $this->mockAction->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_action' - && is_array($callback) - && $callback[0] instanceof DefaultPriorityActionClass - && $callback[1] === 'actionMethod' - && $priority === 10 // Default priority should be 10 - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_action' + && is_array($callback) + && $callback[0] instanceof DefaultPriorityActionClass + && $callback[1] === 'actionMethod' + && $priority === 10 // Default priority should be 10 + && $acceptedArgs === 1); // Test with default priority $testClass = new DefaultPriorityActionClass; @@ -154,18 +141,16 @@ public function actionMethod($param = null) $actionAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $actionAttribute); }); -it('registers an action hook with custom priority', function () { +it('registers an action hook with custom priority', function (): void { // Set up expectations $this->mockAction->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_action' - && is_array($callback) - && $callback[0] instanceof CustomPriorityActionClass - && $callback[1] === 'actionMethod' - && $priority === 42 // Custom priority - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_action' + && is_array($callback) + && $callback[0] instanceof CustomPriorityActionClass + && $callback[1] === 'actionMethod' + && $priority === 42 // Custom priority + && $acceptedArgs === 1); // Test with custom priority $testClass = new CustomPriorityActionClass; @@ -175,11 +160,11 @@ public function actionMethod($param = null) $actionAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $actionAttribute); }); -it('handles null service locator resolution gracefully', function () { +it('handles null service locator resolution gracefully', function (): void { // Create a service locator that returns null for the service $mockServiceLocator = new class { - public function get($serviceClass) + public function get($serviceClass): null { return null; } @@ -196,7 +181,7 @@ public function get($serviceClass) expect(true)->toBeTrue(); }); -afterEach(function () { +afterEach(function (): void { Mockery::close(); Facade::clearResolvedInstances(); }); diff --git a/tests/Unit/Attributes/FilterTest.php b/tests/Unit/Attributes/FilterTest.php index 4ea70b5d..d6e91929 100644 --- a/tests/Unit/Attributes/FilterTest.php +++ b/tests/Unit/Attributes/FilterTest.php @@ -6,19 +6,14 @@ use Pollora\Attributes\Filter; use Pollora\Hook\Infrastructure\Services\Filter as FilterService; -beforeEach(function () { +beforeEach(function (): void { // Mock Filter service $this->mockFilter = Mockery::mock(FilterService::class); // Create a fake service locator $this->mockServiceLocator = new class($this->mockFilter) { - private $filterService; - - public function __construct($filterService) - { - $this->filterService = $filterService; - } + public function __construct(private $filterService) {} public function get($serviceClass) { @@ -36,7 +31,7 @@ class SingleFilterClass #[Filter('test_filter', priority: 10)] public function filterMethod(string $value): string { - return "modified_{$value}"; + return 'modified_'.$value; } } @@ -45,7 +40,7 @@ class MultipleFilterClass #[Filter('test_filter', priority: 10)] public function filterMethod(string $value): string { - return "modified_{$value}"; + return 'modified_'.$value; } #[Filter('another_filter', priority: 20)] @@ -63,7 +58,7 @@ class DefaultPriorityFilterClass public function filterMethod(string $value): string { // Test method with default priority - return "modified_{$value}"; + return 'modified_'.$value; } } @@ -73,22 +68,20 @@ class CustomPriorityFilterClass public function filterMethod(string $value): string { // Test method with custom priority - return "modified_{$value}"; + return 'modified_'.$value; } } -it('registers a filter hook correctly', function () { +it('registers a filter hook correctly', function (): void { // Set up expectations $this->mockFilter->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_filter' - && is_array($callback) - && $callback[0] instanceof SingleFilterClass - && $callback[1] === 'filterMethod' - && $priority === 10 - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_filter' + && is_array($callback) + && $callback[0] instanceof SingleFilterClass + && $callback[1] === 'filterMethod' + && $priority === 10 + && $acceptedArgs === 1); // Test the filter attribute directly using handle method $testClass = new SingleFilterClass; @@ -98,19 +91,17 @@ public function filterMethod(string $value): string $filterAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $filterAttribute); }); -it('registers multiple filter hooks with different priorities', function () { +it('registers multiple filter hooks with different priorities', function (): void { $testClass = new MultipleFilterClass; // Test first filter $this->mockFilter->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_filter' - && is_array($callback) - && $callback[0] instanceof MultipleFilterClass - && $callback[1] === 'filterMethod' - && $priority === 10; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_filter' + && is_array($callback) + && $callback[0] instanceof MultipleFilterClass + && $callback[1] === 'filterMethod' + && $priority === 10); $filterAttribute1 = new Filter('test_filter', 10); $methodReflection1 = new ReflectionMethod($testClass, 'filterMethod'); @@ -119,31 +110,27 @@ public function filterMethod(string $value): string // Test second filter $this->mockFilter->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'another_filter' - && is_array($callback) - && $callback[0] instanceof MultipleFilterClass - && $callback[1] === 'anotherFilterMethod' - && $priority === 20; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'another_filter' + && is_array($callback) + && $callback[0] instanceof MultipleFilterClass + && $callback[1] === 'anotherFilterMethod' + && $priority === 20); $filterAttribute2 = new Filter('another_filter', 20); $methodReflection2 = new ReflectionMethod($testClass, 'anotherFilterMethod'); $filterAttribute2->handle($this->mockServiceLocator, $testClass, $methodReflection2, $filterAttribute2); }); -it('registers a filter hook with default priority (10)', function () { +it('registers a filter hook with default priority (10)', function (): void { // Set up expectations $this->mockFilter->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_filter' - && is_array($callback) - && $callback[0] instanceof DefaultPriorityFilterClass - && $callback[1] === 'filterMethod' - && $priority === 10 // Default priority should be 10 - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_filter' + && is_array($callback) + && $callback[0] instanceof DefaultPriorityFilterClass + && $callback[1] === 'filterMethod' + && $priority === 10 // Default priority should be 10 + && $acceptedArgs === 1); // Test with default priority $testClass = new DefaultPriorityFilterClass; @@ -153,18 +140,16 @@ public function filterMethod(string $value): string $filterAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $filterAttribute); }); -it('registers a filter hook with custom priority', function () { +it('registers a filter hook with custom priority', function (): void { // Set up expectations $this->mockFilter->shouldReceive('add') ->once() - ->withArgs(function ($hook, $callback, $priority, $acceptedArgs) { - return $hook === 'test_filter' - && is_array($callback) - && $callback[0] instanceof CustomPriorityFilterClass - && $callback[1] === 'filterMethod' - && $priority === 99 // Custom priority - && $acceptedArgs === 1; - }); + ->withArgs(fn ($hook, $callback, $priority, $acceptedArgs): bool => $hook === 'test_filter' + && is_array($callback) + && $callback[0] instanceof CustomPriorityFilterClass + && $callback[1] === 'filterMethod' + && $priority === 99 // Custom priority + && $acceptedArgs === 1); // Test with custom priority $testClass = new CustomPriorityFilterClass; @@ -174,7 +159,7 @@ public function filterMethod(string $value): string $filterAttribute->handle($this->mockServiceLocator, $testClass, $methodReflection, $filterAttribute); }); -it('executes filter and returns modified value', function () { +it('executes filter and returns modified value', function (): void { // Set up expectations for the add method $this->mockFilter->shouldReceive('add') ->once() @@ -197,11 +182,11 @@ public function filterMethod(string $value): string expect($result)->toBe('modified_original'); }); -it('handles null service locator resolution gracefully', function () { +it('handles null service locator resolution gracefully', function (): void { // Create a service locator that returns null for the service $mockServiceLocator = new class { - public function get($serviceClass) + public function get($serviceClass): null { return null; } @@ -218,7 +203,7 @@ public function get($serviceClass) expect(true)->toBeTrue(); }); -afterEach(function () { +afterEach(function (): void { Mockery::close(); Facade::clearResolvedInstances(); }); diff --git a/tests/Unit/Attributes/HookTest.php b/tests/Unit/Attributes/HookTest.php index b7d0b40c..72b02c9c 100644 --- a/tests/Unit/Attributes/HookTest.php +++ b/tests/Unit/Attributes/HookTest.php @@ -24,43 +24,43 @@ public function handle( } } -it('initializes with default priority', function () { +it('initializes with default priority', function (): void { $hook = new ConcreteHook('test_hook'); expect($hook->hook)->toBe('test_hook'); expect($hook->priority)->toBe(10); }); -it('initializes with custom priority', function () { +it('initializes with custom priority', function (): void { $hook = new ConcreteHook('test_hook', 20); expect($hook->hook)->toBe('test_hook'); expect($hook->priority)->toBe(20); }); -it('stores hook name correctly', function () { +it('stores hook name correctly', function (): void { $hookName = 'custom_hook_name'; $hook = new ConcreteHook($hookName); expect($hook->hook)->toBe($hookName); }); -it('allows priority to be a negative number', function () { +it('allows priority to be a negative number', function (): void { $priority = -1; $hook = new ConcreteHook('test_hook', $priority); expect($hook->priority)->toBe($priority); }); -it('allows priority to be zero', function () { +it('allows priority to be zero', function (): void { $priority = 0; $hook = new ConcreteHook('test_hook', $priority); expect($hook->priority)->toBe($priority); }); -it('allows priority to be a large number', function () { +it('allows priority to be a large number', function (): void { $priority = 9999; $hook = new ConcreteHook('test_hook', $priority); expect($hook->priority)->toBe($priority); }); -afterEach(function () { +afterEach(function (): void { Mockery::close(); Facade::clearResolvedInstances(); }); diff --git a/tests/Unit/Attributes/PostTypeAttributeTest.php b/tests/Unit/Attributes/PostTypeAttributeTest.php index e99faa30..7be03b79 100644 --- a/tests/Unit/Attributes/PostTypeAttributeTest.php +++ b/tests/Unit/Attributes/PostTypeAttributeTest.php @@ -49,7 +49,7 @@ public function getProductDetails(): array } } -it('creates PostType attribute with all parameters', function () { +it('creates PostType attribute with all parameters', function (): void { $postType = new PostType('custom-slug', 'Custom Type', 'Custom Types'); expect($postType->slug)->toBe('custom-slug'); @@ -57,7 +57,7 @@ public function getProductDetails(): array expect($postType->plural)->toBe('Custom Types'); }); -it('creates PostType attribute with only slug', function () { +it('creates PostType attribute with only slug', function (): void { $postType = new PostType('events'); expect($postType->slug)->toBe('events'); @@ -65,7 +65,7 @@ public function getProductDetails(): array expect($postType->plural)->toBeNull(); }); -it('creates PostType attribute with no parameters', function () { +it('creates PostType attribute with no parameters', function (): void { $postType = new PostType; expect($postType->slug)->toBeNull(); @@ -73,7 +73,7 @@ public function getProductDetails(): array expect($postType->plural)->toBeNull(); }); -it('stores PostType properties as readonly', function () { +it('stores PostType properties as readonly', function (): void { $postType = new PostType('test'); expect($postType->slug)->toBe('test'); @@ -89,72 +89,72 @@ public function getProductDetails(): array expect($pluralProperty->isReadOnly())->toBeTrue(); }); -it('creates HasArchive attribute with default value', function () { +it('creates HasArchive attribute with default value', function (): void { $hasArchive = new HasArchive; expect($hasArchive->value)->toBeTrue(); }); -it('creates HasArchive attribute with custom slug', function () { +it('creates HasArchive attribute with custom slug', function (): void { $hasArchive = new HasArchive('custom-archive'); expect($hasArchive->value)->toBe('custom-archive'); }); -it('creates HasArchive attribute disabled', function () { +it('creates HasArchive attribute disabled', function (): void { $hasArchive = new HasArchive(false); expect($hasArchive->value)->toBeFalse(); }); -it('creates Supports attribute with default features', function () { +it('creates Supports attribute with default features', function (): void { $supports = new Supports; expect($supports->features)->toBe(['title', 'editor']); }); -it('creates Supports attribute with custom features', function () { +it('creates Supports attribute with custom features', function (): void { $features = ['title', 'editor', 'thumbnail', 'excerpt']; $supports = new Supports($features); expect($supports->features)->toBe($features); }); -it('creates MenuIcon attribute with dashicon', function () { +it('creates MenuIcon attribute with dashicon', function (): void { $menuIcon = new MenuIcon('dashicons-admin-post'); expect($menuIcon->value)->toBe('dashicons-admin-post'); }); -it('creates MenuIcon attribute with custom URL', function () { +it('creates MenuIcon attribute with custom URL', function (): void { $menuIcon = new MenuIcon('https://example.com/icon.png'); expect($menuIcon->value)->toBe('https://example.com/icon.png'); }); -it('creates PubliclyQueryable attribute with default value', function () { +it('creates PubliclyQueryable attribute with default value', function (): void { $publiclyQueryable = new PubliclyQueryable; expect($publiclyQueryable->value)->toBeTrue(); }); -it('creates PubliclyQueryable attribute disabled', function () { +it('creates PubliclyQueryable attribute disabled', function (): void { $publiclyQueryable = new PubliclyQueryable(false); expect($publiclyQueryable->value)->toBeFalse(); }); -it('creates AdminCol attribute with all parameters', function () { +it('creates AdminCol attribute with all parameters', function (): void { $adminCol = new AdminCol( 'price', 'Product Price', sortable: true, - width: 120, titleIcon: 'dashicons-money', dateFormat: 'd/m/Y', link: 'edit', cap: 'edit_posts', - default: 'ASC' + default: 'ASC', + width: 120 ); expect($adminCol->key)->toBe('price'); @@ -168,7 +168,7 @@ public function getProductDetails(): array expect($adminCol->default)->toBe('ASC'); }); -it('creates AdminCol attribute with minimal parameters', function () { +it('creates AdminCol attribute with minimal parameters', function (): void { $adminCol = new AdminCol('title', 'Title'); expect($adminCol->key)->toBe('title'); @@ -182,7 +182,7 @@ public function getProductDetails(): array expect($adminCol->default)->toBeNull(); }); -it('creates AdminCol attribute for meta fields', function () { +it('creates AdminCol attribute for meta fields', function (): void { $adminCol = new AdminCol( 'custom_price', 'Price', @@ -196,7 +196,7 @@ public function getProductDetails(): array expect($adminCol->metaKey)->toBe('product_price'); }); -it('creates AdminCol attribute for taxonomy fields', function () { +it('creates AdminCol attribute for taxonomy fields', function (): void { $adminCol = new AdminCol( 'categories', 'Categories', @@ -208,7 +208,7 @@ public function getProductDetails(): array expect($adminCol->taxonomy)->toBe('product_category'); }); -it('creates AdminCol attribute for featured image', function () { +it('creates AdminCol attribute for featured image', function (): void { $adminCol = new AdminCol( 'image', 'Featured Image', @@ -222,13 +222,13 @@ public function getProductDetails(): array expect($adminCol->width)->toBe(80); }); -it('creates RegisterMetaBoxCb attribute', function () { +it('creates RegisterMetaBoxCb attribute', function (): void { $registerMetaBoxCb = new RegisterMetaBoxCb; expect($registerMetaBoxCb)->toBeInstanceOf(RegisterMetaBoxCb::class); }); -it('has correct PHP attribute configurations', function () { +it('has correct PHP attribute configurations', function (): void { // Test PostType attribute configuration $postTypeReflection = new ReflectionClass(PostType::class); $postTypeAttributes = $postTypeReflection->getAttributes(Attribute::class); @@ -254,7 +254,7 @@ public function getProductDetails(): array expect($registerMetaBoxCbAttribute->flags)->toBe(Attribute::TARGET_METHOD); }); -it('test class has correct attributes applied', function () { +it('test class has correct attributes applied', function (): void { $reflection = new ReflectionClass(TestProduct::class); // Check class-level attributes @@ -281,7 +281,7 @@ public function getProductDetails(): array expect($metaBoxAttrs)->toHaveCount(1); }); -it('attribute values can be extracted correctly', function () { +it('attribute values can be extracted correctly', function (): void { $reflection = new ReflectionClass(TestProduct::class); // Extract PostType attribute values @@ -309,26 +309,26 @@ public function getProductDetails(): array expect($adminCol->title)->toBe('Price'); }); -it('accepts all types without validation', function () { +it('accepts all types without validation', function (): void { // No validation should happen in attribute constructors // All validation is now handled by PostTypeDiscovery // PostType with any values - expect(fn () => new PostType('', '', ''))->not->toThrow(Exception::class); - expect(fn () => new PostType('invalid slug', 'invalid name', 'invalid plural'))->not->toThrow(Exception::class); + expect(fn (): PostType => new PostType('', '', ''))->not->toThrow(Exception::class); + expect(fn (): PostType => new PostType('invalid slug', 'invalid name', 'invalid plural'))->not->toThrow(Exception::class); // HasArchive with any values - expect(fn () => new HasArchive(''))->not->toThrow(Exception::class); - expect(fn () => new HasArchive('invalid-archive'))->not->toThrow(Exception::class); + expect(fn (): HasArchive => new HasArchive(''))->not->toThrow(Exception::class); + expect(fn (): HasArchive => new HasArchive('invalid-archive'))->not->toThrow(Exception::class); // Supports with any array - expect(fn () => new Supports([]))->not->toThrow(Exception::class); - expect(fn () => new Supports(['invalid-feature']))->not->toThrow(Exception::class); + expect(fn (): Supports => new Supports([]))->not->toThrow(Exception::class); + expect(fn (): Supports => new Supports(['invalid-feature']))->not->toThrow(Exception::class); // MenuIcon with any string - expect(fn () => new MenuIcon(''))->not->toThrow(Exception::class); - expect(fn () => new MenuIcon('invalid-icon'))->not->toThrow(Exception::class); + expect(fn (): MenuIcon => new MenuIcon(''))->not->toThrow(Exception::class); + expect(fn (): MenuIcon => new MenuIcon('invalid-icon'))->not->toThrow(Exception::class); // AdminCol with any values - expect(fn () => new AdminCol('', ''))->not->toThrow(Exception::class); + expect(fn (): AdminCol => new AdminCol('', ''))->not->toThrow(Exception::class); }); diff --git a/tests/Unit/Attributes/PostTypeSubAttributesTest.php b/tests/Unit/Attributes/PostTypeSubAttributesTest.php index 1165a989..ae2e589c 100644 --- a/tests/Unit/Attributes/PostTypeSubAttributesTest.php +++ b/tests/Unit/Attributes/PostTypeSubAttributesTest.php @@ -47,7 +47,7 @@ public function getProductName(): string } } -it('detects all class-level PostType attributes', function () { +it('detects all class-level PostType attributes', function (): void { $reflection = new ReflectionClass(ProductWithSubAttributes::class); // Should detect PostType main attribute @@ -67,7 +67,7 @@ public function getProductName(): string expect($menuIconAttrs)->toHaveCount(1); }); -it('detects all method-level AdminCol attributes', function () { +it('detects all method-level AdminCol attributes', function (): void { $reflection = new ReflectionClass(ProductWithSubAttributes::class); // Check price method @@ -91,7 +91,7 @@ public function getProductName(): string expect($nameAttrs)->toHaveCount(0); }); -it('extracts correct values from class-level attributes', function () { +it('extracts correct values from class-level attributes', function (): void { $reflection = new ReflectionClass(ProductWithSubAttributes::class); // Extract PostType attribute @@ -117,7 +117,7 @@ public function getProductName(): string expect($menuIcon->value)->toBe('dashicons-cart'); }); -it('extracts correct values from method-level AdminCol attributes', function () { +it('extracts correct values from method-level AdminCol attributes', function (): void { $reflection = new ReflectionClass(ProductWithSubAttributes::class); // Extract price AdminCol @@ -148,7 +148,7 @@ public function getProductName(): string expect($categoryAdminCol->sortable)->toBeFalse(); // Default value }); -it('supports multiple AdminCol attributes on different methods', function () { +it('supports multiple AdminCol attributes on different methods', function (): void { $reflection = new ReflectionClass(ProductWithSubAttributes::class); $adminColMethods = []; @@ -163,12 +163,12 @@ public function getProductName(): string expect($adminColMethods)->toHaveKeys(['getPriceColumn', 'getStockColumn', 'getCategoryColumn']); // Verify each column has unique key - $keys = array_map(fn ($adminCol) => $adminCol->key, $adminColMethods); + $keys = array_map(fn (AdminCol $adminCol): string => $adminCol->key, $adminColMethods); expect($keys)->toBe(['getPriceColumn' => 'price', 'getStockColumn' => 'stock', 'getCategoryColumn' => 'category']); expect(array_unique($keys))->toHaveCount(3); // All keys are unique }); -it('demonstrates attribute composition pattern', function () { +it('demonstrates attribute composition pattern', function (): void { // This test demonstrates how the new system works: // 1. PostType attribute defines the main post type // 2. Sub-attributes (HasArchive, Supports, MenuIcon) configure post type settings @@ -220,7 +220,7 @@ public function getProductName(): string expect($allMethodAttributes)->toHaveCount(3); // 3 AdminCol attributes }); -it('attributes have correct target configurations', function () { +it('attributes have correct target configurations', function (): void { // Verify class-level attributes target classes $classLevelAttributes = [ PostType::class, @@ -253,19 +253,19 @@ public function getProductName(): string } }); -it('demonstrates no validation in attributes', function () { +it('demonstrates no validation in attributes', function (): void { // All attributes should accept any values without validation // Validation will be handled by PostTypeDiscovery - expect(fn () => new PostType('', '', ''))->not->toThrow(Exception::class); - expect(fn () => new HasArchive(''))->not->toThrow(Exception::class); - expect(fn () => new Supports([]))->not->toThrow(Exception::class); - expect(fn () => new MenuIcon(''))->not->toThrow(Exception::class); - expect(fn () => new AdminCol('', ''))->not->toThrow(Exception::class); + expect(fn (): PostType => new PostType('', '', ''))->not->toThrow(Exception::class); + expect(fn (): HasArchive => new HasArchive(''))->not->toThrow(Exception::class); + expect(fn (): Supports => new Supports([]))->not->toThrow(Exception::class); + expect(fn (): MenuIcon => new MenuIcon(''))->not->toThrow(Exception::class); + expect(fn (): AdminCol => new AdminCol('', ''))->not->toThrow(Exception::class); // Even completely invalid values should not throw - expect(fn () => new PostType('invalid slug with spaces', 'inv@lid', 'pl{ur}al'))->not->toThrow(Exception::class); - expect(fn () => new HasArchive(123))->not->toThrow(Exception::class); // Wrong type - expect(fn () => new Supports(['invalid-feature', '', null]))->not->toThrow(Exception::class); - expect(fn () => new MenuIcon(null))->not->toThrow(Exception::class); // Wrong type + expect(fn (): PostType => new PostType('invalid slug with spaces', 'inv@lid', 'pl{ur}al'))->not->toThrow(Exception::class); + expect(fn (): HasArchive => new HasArchive(123))->not->toThrow(Exception::class); // Wrong type + expect(fn (): Supports => new Supports(['invalid-feature', '', null]))->not->toThrow(Exception::class); + expect(fn (): MenuIcon => new MenuIcon(null))->not->toThrow(Exception::class); // Wrong type }); diff --git a/tests/Unit/Attributes/ScheduleTest.php b/tests/Unit/Attributes/ScheduleTest.php index 75d61db2..97843335 100644 --- a/tests/Unit/Attributes/ScheduleTest.php +++ b/tests/Unit/Attributes/ScheduleTest.php @@ -12,7 +12,7 @@ * Tests for the simplified Schedule attribute that now only contains properties * and delegates all processing logic to the ScheduleDiscovery service. */ -it('creates Schedule attribute with string recurrence', function () { +it('creates Schedule attribute with string recurrence', function (): void { $schedule = new Schedule('daily'); expect($schedule)->toBeInstanceOf(Schedule::class); @@ -21,7 +21,7 @@ expect($schedule->args)->toBe([]); }); -it('creates Schedule attribute with custom hook name', function () { +it('creates Schedule attribute with custom hook name', function (): void { $schedule = new Schedule('hourly', 'custom_hook_name'); expect($schedule->recurrence)->toBe('hourly'); @@ -29,7 +29,7 @@ expect($schedule->args)->toBe([]); }); -it('creates Schedule attribute with arguments', function () { +it('creates Schedule attribute with arguments', function (): void { $args = ['type' => 'full', 'force' => true]; $schedule = new Schedule('daily', null, $args); @@ -38,7 +38,7 @@ expect($schedule->args)->toBe($args); }); -it('creates Schedule attribute with all parameters', function () { +it('creates Schedule attribute with all parameters', function (): void { $args = ['batch_size' => 100]; $schedule = new Schedule('weekly', 'weekly_cleanup', $args); @@ -47,7 +47,7 @@ expect($schedule->args)->toBe($args); }); -it('creates Schedule attribute with array recurrence', function () { +it('creates Schedule attribute with array recurrence', function (): void { $recurrence = ['interval' => 3600, 'display' => 'Every Hour']; $schedule = new Schedule($recurrence); @@ -56,7 +56,7 @@ expect($schedule->args)->toBe([]); }); -it('creates Schedule attribute with Every enum', function () { +it('creates Schedule attribute with Every enum', function (): void { $schedule = new Schedule(Every::DAY); expect($schedule->recurrence)->toBe(Every::DAY); @@ -64,8 +64,8 @@ expect($schedule->args)->toBe([]); }); -it('creates Schedule attribute with Interval instance', function () { - $interval = new Interval(hours: 2, minutes: 30); +it('creates Schedule attribute with Interval instance', function (): void { + $interval = new Interval(minutes: 30, hours: 2); $schedule = new Schedule($interval); expect($schedule->recurrence)->toBe($interval); @@ -73,7 +73,7 @@ expect($schedule->args)->toBe([]); }); -it('creates Schedule attribute with complex Every enum and parameters', function () { +it('creates Schedule attribute with complex Every enum and parameters', function (): void { $args = ['source' => 'api', 'limit' => 50]; $schedule = new Schedule(Every::MONTH, 'monthly_sync', $args); @@ -82,8 +82,8 @@ expect($schedule->args)->toBe($args); }); -it('creates Schedule attribute with complex Interval and parameters', function () { - $interval = new Interval(days: 1, hours: 12, minutes: 30); +it('creates Schedule attribute with complex Interval and parameters', function (): void { + $interval = new Interval(minutes: 30, hours: 12, days: 1); $args = ['cleanup_type' => 'deep']; $schedule = new Schedule($interval, 'complex_cleanup', $args); @@ -92,7 +92,7 @@ expect($schedule->args)->toBe($args); }); -it('stores recurrence as readonly property', function () { +it('stores recurrence as readonly property', function (): void { $schedule = new Schedule('daily'); expect($schedule->recurrence)->toBe('daily'); @@ -103,7 +103,7 @@ expect($property->isReadOnly())->toBeTrue(); }); -it('stores hook as readonly property', function () { +it('stores hook as readonly property', function (): void { $schedule = new Schedule('daily', 'test_hook'); expect($schedule->hook)->toBe('test_hook'); @@ -114,7 +114,7 @@ expect($property->isReadOnly())->toBeTrue(); }); -it('stores args as readonly property', function () { +it('stores args as readonly property', function (): void { $args = ['key' => 'value']; $schedule = new Schedule('daily', null, $args); @@ -126,7 +126,7 @@ expect($property->isReadOnly())->toBeTrue(); }); -it('has correct PHP attribute configuration', function () { +it('has correct PHP attribute configuration', function (): void { $reflection = new ReflectionClass(Schedule::class); $attributes = $reflection->getAttributes(Attribute::class); @@ -136,23 +136,23 @@ expect($attribute->flags)->toBe(Attribute::TARGET_METHOD); }); -it('accepts all supported recurrence types without validation', function () { +it('accepts all supported recurrence types without validation', function (): void { // No validation should happen in the attribute constructor // All validation is now handled by ScheduleDiscovery // String recurrence - expect(fn () => new Schedule('daily'))->not->toThrow(Exception::class); - expect(fn () => new Schedule('invalid_schedule'))->not->toThrow(Exception::class); // No validation + expect(fn (): Schedule => new Schedule('daily'))->not->toThrow(Exception::class); + expect(fn (): Schedule => new Schedule('invalid_schedule'))->not->toThrow(Exception::class); // No validation // Array recurrence - expect(fn () => new Schedule(['interval' => 3600, 'display' => 'Valid']))->not->toThrow(Exception::class); - expect(fn () => new Schedule(['invalid' => 'array']))->not->toThrow(Exception::class); // No validation + expect(fn (): Schedule => new Schedule(['interval' => 3600, 'display' => 'Valid']))->not->toThrow(Exception::class); + expect(fn (): Schedule => new Schedule(['invalid' => 'array']))->not->toThrow(Exception::class); // No validation // Every enum - expect(fn () => new Schedule(Every::HOUR))->not->toThrow(Exception::class); - expect(fn () => new Schedule(Every::MONTH))->not->toThrow(Exception::class); + expect(fn (): Schedule => new Schedule(Every::HOUR))->not->toThrow(Exception::class); + expect(fn (): Schedule => new Schedule(Every::MONTH))->not->toThrow(Exception::class); // Interval instance $interval = new Interval(minutes: 30); - expect(fn () => new Schedule($interval))->not->toThrow(Exception::class); + expect(fn (): Schedule => new Schedule($interval))->not->toThrow(Exception::class); }); diff --git a/tests/Unit/Attributes/TaxonomyAttributeTest.php b/tests/Unit/Attributes/TaxonomyAttributeTest.php index cf588b5d..0d88e989 100644 --- a/tests/Unit/Attributes/TaxonomyAttributeTest.php +++ b/tests/Unit/Attributes/TaxonomyAttributeTest.php @@ -128,13 +128,13 @@ public function getArgs(): array } } -beforeAll(function () { +beforeAll(function (): void { // Create and configure the container $app = new Container; Facade::setFacadeApplication($app); }); -afterAll(function () { +afterAll(function (): void { m::close(); Facade::clearResolvedInstances(); Facade::setFacadeApplication(null); @@ -143,7 +143,7 @@ public function getArgs(): array // Helper function to test simple boolean attributes function testBooleanAttribute(string $attributeName, string $argName): void { - test("$attributeName attribute sets $argName parameter", function () use ($argName) { + test(sprintf('%s attribute sets %s parameter', $attributeName, $argName), function () use ($argName): void { $taxonomy = new TestTaxonomy; // Simulate the discovery process by manually processing attributes @@ -162,7 +162,7 @@ function testBooleanAttribute(string $attributeName, string $argName): void // Helper function to test string/value attributes function testValueAttribute(string $attributeName, string $argName, mixed $expectedValue): void { - test("$attributeName attribute sets $argName parameter", function () use ($argName, $expectedValue) { + test(sprintf('%s attribute sets %s parameter', $attributeName, $argName), function () use ($argName, $expectedValue): void { $taxonomy = new TestTaxonomy; // Simulate the discovery process by manually processing attributes @@ -181,7 +181,7 @@ function testValueAttribute(string $attributeName, string $argName, mixed $expec // Helper function to test method attributes function testMethodAttribute(string $attributeName, string $argName, string $methodName, string $attributeClass): void { - test("$attributeName attribute sets $argName parameter", function () use ($argName, $methodName, $attributeClass) { + test(sprintf('%s attribute sets %s parameter', $attributeName, $argName), function () use ($argName, $methodName, $attributeClass): void { $taxonomy = new TestTaxonomy; // Reset attributeArgs to avoid interference @@ -241,7 +241,7 @@ function testMethodAttribute(string $attributeName, string $argName, string $met testMethodAttribute('UpdateCountCallback', 'update_count_callback', 'updateCount', UpdateCountCallback::class); // Test the final getArgs method -test('getArgs method merges attribute args with withArgs and labels', function () { +test('getArgs method merges attribute args with withArgs and labels', function (): void { $taxonomy = new TestTaxonomy; // Simulate the discovery process by manually processing attributes diff --git a/tests/Unit/Attributes/TaxonomySubAttributesTest.php b/tests/Unit/Attributes/TaxonomySubAttributesTest.php index 50822f39..3838bb40 100644 --- a/tests/Unit/Attributes/TaxonomySubAttributesTest.php +++ b/tests/Unit/Attributes/TaxonomySubAttributesTest.php @@ -49,7 +49,7 @@ public function updateTermCount($terms, $taxonomy): void public function sanitizeTerms($terms): array { // Sanitize the terms - return array_map('sanitize_text_field', (array) $terms); + return array_map(sanitize_text_field(...), (array) $terms); } public function getTaxonomyName(): string @@ -58,7 +58,7 @@ public function getTaxonomyName(): string } } -it('detects all class-level Taxonomy attributes', function () { +it('detects all class-level Taxonomy attributes', function (): void { $reflection = new ReflectionClass(ProductCategoryWithSubAttributes::class); // Should detect Taxonomy main attribute @@ -86,7 +86,7 @@ public function getTaxonomyName(): string expect($objectTypeAttrs)->toHaveCount(1); }); -it('detects all method-level callback attributes', function () { +it('detects all method-level callback attributes', function (): void { $reflection = new ReflectionClass(ProductCategoryWithSubAttributes::class); // Check custom meta box method @@ -114,7 +114,7 @@ public function getTaxonomyName(): string expect($nameCallbackAttrs)->toHaveCount(0); }); -it('extracts correct values from class-level attributes', function () { +it('extracts correct values from class-level attributes', function (): void { $reflection = new ReflectionClass(ProductCategoryWithSubAttributes::class); // Extract Taxonomy attribute @@ -147,7 +147,7 @@ public function getTaxonomyName(): string expect($objectType)->toBeInstanceOf(ObjectType::class); }); -it('extracts correct values from method-level callback attributes', function () { +it('extracts correct values from method-level callback attributes', function (): void { $reflection = new ReflectionClass(ProductCategoryWithSubAttributes::class); // Extract MetaBoxCb @@ -169,7 +169,7 @@ public function getTaxonomyName(): string expect($sanitizeCb)->toBeInstanceOf(MetaBoxSanitizeCb::class); }); -it('supports multiple callback attributes on different methods', function () { +it('supports multiple callback attributes on different methods', function (): void { $reflection = new ReflectionClass(ProductCategoryWithSubAttributes::class); $callbackMethods = []; @@ -180,7 +180,7 @@ public function getTaxonomyName(): string $method->getAttributes(MetaBoxSanitizeCb::class) ); - if (! empty($callbackAttrs)) { + if ($callbackAttrs !== []) { $callbackMethods[$method->getName()] = $callbackAttrs[0]->newInstance(); } } @@ -194,7 +194,7 @@ public function getTaxonomyName(): string expect($callbackMethods['sanitizeTerms'])->toBeInstanceOf(MetaBoxSanitizeCb::class); }); -it('demonstrates attribute composition pattern for taxonomies', function () { +it('demonstrates attribute composition pattern for taxonomies', function (): void { // This test demonstrates how the taxonomy attribute system works: // 1. Taxonomy attribute defines the main taxonomy // 2. Sub-attributes (Hierarchical, PublicTaxonomy, ShowUI, etc.) configure taxonomy settings @@ -231,7 +231,7 @@ public function getTaxonomyName(): string $method->getAttributes(MetaBoxSanitizeCb::class) ); - if (! empty($callbackAttrs)) { + if ($callbackAttrs !== []) { $callbackMethods[] = [ 'method' => $method->getName(), 'attribute' => $callbackAttrs[0]->newInstance(), @@ -255,7 +255,7 @@ public function getTaxonomyName(): string expect($allMethodAttributes)->toHaveCount(3); // 3 callback attributes }); -it('attributes have correct target configurations', function () { +it('attributes have correct target configurations', function (): void { // Verify class-level attributes target classes $classLevelAttributes = [ Taxonomy::class, @@ -292,22 +292,22 @@ public function getTaxonomyName(): string } }); -it('demonstrates no validation in attributes', function () { +it('demonstrates no validation in attributes', function (): void { // All attributes should accept any values without validation // Validation will be handled by TaxonomyDiscovery - expect(fn () => new Taxonomy('', '', '', []))->not->toThrow(Throwable::class); - expect(fn () => new Hierarchical(false))->not->toThrow(Throwable::class); - expect(fn () => new PublicTaxonomy(false))->not->toThrow(Throwable::class); - expect(fn () => new ShowUI(false))->not->toThrow(Throwable::class); - expect(fn () => new ShowInRest(false))->not->toThrow(Throwable::class); - expect(fn () => new ObjectType([]))->not->toThrow(Throwable::class); - expect(fn () => new MetaBoxCb)->not->toThrow(Throwable::class); - expect(fn () => new UpdateCountCallback)->not->toThrow(Throwable::class); - expect(fn () => new MetaBoxSanitizeCb)->not->toThrow(Throwable::class); + expect(fn (): Taxonomy => new Taxonomy('', '', '', []))->not->toThrow(Throwable::class); + expect(fn (): Hierarchical => new Hierarchical(false))->not->toThrow(Throwable::class); + expect(fn (): PublicTaxonomy => new PublicTaxonomy(false))->not->toThrow(Throwable::class); + expect(fn (): ShowUI => new ShowUI(false))->not->toThrow(Throwable::class); + expect(fn (): ShowInRest => new ShowInRest(false))->not->toThrow(Throwable::class); + expect(fn (): ObjectType => new ObjectType([]))->not->toThrow(Throwable::class); + expect(fn (): MetaBoxCb => new MetaBoxCb)->not->toThrow(Throwable::class); + expect(fn (): UpdateCountCallback => new UpdateCountCallback)->not->toThrow(Throwable::class); + expect(fn (): MetaBoxSanitizeCb => new MetaBoxSanitizeCb)->not->toThrow(Throwable::class); // Even completely invalid values should not throw - expect(fn () => new Taxonomy('invalid slug with spaces', 'inv@lid', 'pl{ur}al', 'invalid'))->not->toThrow(Throwable::class); - expect(fn () => new Hierarchical('not-boolean'))->not->toThrow(Throwable::class); // Wrong type - expect(fn () => new ObjectType('not-array'))->not->toThrow(Throwable::class); // Wrong type + expect(fn (): Taxonomy => new Taxonomy('invalid slug with spaces', 'inv@lid', 'pl{ur}al', 'invalid'))->not->toThrow(Throwable::class); + expect(fn (): Hierarchical => new Hierarchical('not-boolean'))->not->toThrow(Throwable::class); // Wrong type + expect(fn (): ObjectType => new ObjectType('not-array'))->not->toThrow(Throwable::class); // Wrong type }); diff --git a/tests/Unit/Discovery/Domain/Models/DiscoveryItemsTest.php b/tests/Unit/Discovery/Domain/Models/DiscoveryItemsTest.php index 59d0d443..677b3d44 100644 --- a/tests/Unit/Discovery/Domain/Models/DiscoveryItemsTest.php +++ b/tests/Unit/Discovery/Domain/Models/DiscoveryItemsTest.php @@ -2,80 +2,62 @@ declare(strict_types=1); -namespace Tests\Unit\Discovery\Domain\Models; - use Pollora\Discovery\Domain\Models\DiscoveryItems; use Pollora\Discovery\Domain\Models\DiscoveryLocation; -use Tests\TestCase; - -/** - * Discovery Items Test - * - * Tests the DiscoveryItems domain model functionality including - * item management, location-based organization, and iteration. - */ -final class DiscoveryItemsTest extends TestCase -{ - public function test_can_create_empty_discovery_items(): void - { + +describe('DiscoveryItems', function (): void { + it('can create empty discovery items', function (): void { $items = new DiscoveryItems; - $this->assertFalse($items->isLoaded()); - $this->assertCount(0, $items); - $this->assertEquals([], $items->all()); - } + expect($items->isLoaded())->toBeFalse(); + expect($items)->toHaveCount(0); + expect($items->all())->toBe([]); + }); - public function test_can_create_discovery_items_with_initial_data(): void - { - $initialData = [ + it('can create with initial data', function (): void { + $items = new DiscoveryItems([ 'location1' => ['item1', 'item2'], 'location2' => ['item3'], - ]; - - $items = new DiscoveryItems($initialData); + ]); - $this->assertTrue($items->isLoaded()); - $this->assertCount(3, $items); - $this->assertEquals(['item1', 'item2', 'item3'], $items->all()); - } + expect($items->isLoaded())->toBeTrue(); + expect($items)->toHaveCount(3); + expect($items->all())->toEqual(['item1', 'item2', 'item3']); + }); - public function test_can_add_single_item_for_location(): void - { + it('can add single item for location', function (): void { $items = new DiscoveryItems; $location = new DiscoveryLocation('App\\Models', '/app/models'); $items->add($location, 'test-item'); - $this->assertTrue($items->hasLocation($location)); - $this->assertEquals(['test-item'], $items->getForLocation($location)); - $this->assertCount(1, $items); - } + expect($items->hasLocation($location))->toBeTrue(); + expect($items->getForLocation($location))->toEqual(['test-item']); + expect($items)->toHaveCount(1); + }); - public function test_can_add_multiple_items_for_location(): void - { + it('can add multiple items for location', function (): void { $items = new DiscoveryItems; $location = new DiscoveryLocation('App\\Models', '/app/models'); $items->addForLocation($location, ['item1', 'item2', 'item3']); - $this->assertEquals(['item1', 'item2', 'item3'], $items->getForLocation($location)); - $this->assertCount(3, $items); - } + expect($items->getForLocation($location))->toEqual(['item1', 'item2', 'item3']); + expect($items)->toHaveCount(3); + }); - public function test_can_add_items_to_existing_location(): void - { + it('can add items to existing location', function (): void { $items = new DiscoveryItems; $location = new DiscoveryLocation('App\\Models', '/app/models'); $items->add($location, 'item1'); $items->addForLocation($location, ['item2', 'item3']); - $this->assertEquals(['item1', 'item2', 'item3'], $items->getForLocation($location)); - $this->assertCount(3, $items); - } + expect($items->getForLocation($location))->toEqual(['item1', 'item2', 'item3']); + expect($items)->toHaveCount(3); + }); - public function test_can_handle_multiple_locations(): void - { + it('handles multiple locations', function (): void { $items = new DiscoveryItems; $location1 = new DiscoveryLocation('App\\Models', '/app/models'); $location2 = new DiscoveryLocation('App\\Services', '/app/services'); @@ -83,25 +65,21 @@ public function test_can_handle_multiple_locations(): void $items->addForLocation($location1, ['model1', 'model2']); $items->addForLocation($location2, ['service1']); - $this->assertCount(3, $items); - $this->assertEquals(['model1', 'model2'], $items->getForLocation($location1)); - $this->assertEquals(['service1'], $items->getForLocation($location2)); - $this->assertEquals(['model1', 'model2', 'service1'], $items->all()); - } + expect($items)->toHaveCount(3); + expect($items->getForLocation($location1))->toEqual(['model1', 'model2']); + expect($items->getForLocation($location2))->toEqual(['service1']); + expect($items->all())->toEqual(['model1', 'model2', 'service1']); + }); - public function test_returns_empty_array_for_unknown_location(): void - { + it('returns empty array for unknown location', function (): void { $items = new DiscoveryItems; $location = new DiscoveryLocation('App\\Models', '/app/models'); - $result = $items->getForLocation($location); - - $this->assertEquals([], $result); - $this->assertFalse($items->hasLocation($location)); - } + expect($items->getForLocation($location))->toBe([]); + expect($items->hasLocation($location))->toBeFalse(); + }); - public function test_can_iterate_over_all_items(): void - { + it('can iterate over all items', function (): void { $items = new DiscoveryItems; $location1 = new DiscoveryLocation('App\\Models', '/app/models'); $location2 = new DiscoveryLocation('App\\Services', '/app/services'); @@ -114,30 +92,26 @@ public function test_can_iterate_over_all_items(): void $iteratedItems[] = $item; } - $this->assertEquals(['model1', 'model2', 'service1'], $iteratedItems); - } + expect($iteratedItems)->toEqual(['model1', 'model2', 'service1']); + }); - public function test_serialization_and_unserialization(): void - { + it('supports serialization and unserialization', function (): void { $items = new DiscoveryItems; $location = new DiscoveryLocation('App\\Models', '/app/models'); $items->addForLocation($location, ['item1', 'item2']); - // Test serialization $serialized = $items->__serialize(); - $this->assertIsArray($serialized); + expect($serialized)->toBeArray(); - // Test unserialization $newItems = new DiscoveryItems; $newItems->__unserialize($serialized); - $this->assertEquals($items->all(), $newItems->all()); - $this->assertEquals($items->count(), $newItems->count()); - } + expect($newItems->all())->toEqual($items->all()); + expect($newItems->count())->toBe($items->count()); + }); - public function test_only_vendor_returns_new_instance(): void - { + it('onlyVendor returns new instance', function (): void { $items = new DiscoveryItems([ 'location1' => ['item1'], 'location2' => ['item2'], @@ -145,7 +119,7 @@ public function test_only_vendor_returns_new_instance(): void $vendorItems = $items->onlyVendor(); - $this->assertNotSame($items, $vendorItems); - $this->assertInstanceOf(DiscoveryItems::class, $vendorItems); - } -} + expect($vendorItems)->not->toBe($items); + expect($vendorItems)->toBeInstanceOf(DiscoveryItems::class); + }); +}); diff --git a/tests/Unit/Discovery/Domain/Models/DiscoveryLocationTest.php b/tests/Unit/Discovery/Domain/Models/DiscoveryLocationTest.php index bcf41a75..b821be06 100644 --- a/tests/Unit/Discovery/Domain/Models/DiscoveryLocationTest.php +++ b/tests/Unit/Discovery/Domain/Models/DiscoveryLocationTest.php @@ -2,98 +2,66 @@ declare(strict_types=1); -namespace Tests\Unit\Discovery\Domain\Models; - use Pollora\Discovery\Domain\Models\DiscoveryLocation; -use Tests\TestCase; - -/** - * Discovery Location Test - * - * Tests the DiscoveryLocation domain model functionality including - * path resolution, namespace handling, and class name conversion. - */ -final class DiscoveryLocationTest extends TestCase -{ - public function test_can_create_discovery_location(): void - { + +describe('DiscoveryLocation', function (): void { + it('can create discovery location', function (): void { $location = new DiscoveryLocation('App\\Models', '/path/to/models'); - $this->assertEquals('App\\Models', $location->getNamespace()); - $this->assertEquals('/path/to/models', $location->getPath()); - } + expect($location->getNamespace())->toBe('App\\Models'); + expect($location->getPath())->toBe('/path/to/models'); + }); - public function test_generates_unique_key(): void - { + it('generates unique key based on path', function (): void { $location1 = new DiscoveryLocation('App\\Models', '/path/to/models'); $location2 = new DiscoveryLocation('App\\Services', '/path/to/models'); $location3 = new DiscoveryLocation('App\\Models', '/path/to/services'); - // Same path should generate same key regardless of namespace - $this->assertEquals($location1->getKey(), $location2->getKey()); + expect($location1->getKey())->toBe($location2->getKey()); + expect($location1->getKey())->not->toBe($location3->getKey()); + }); - // Different paths should generate different keys - $this->assertNotEquals($location1->getKey(), $location3->getKey()); - } - - public function test_detects_vendor_locations(): void - { + it('detects vendor locations', function (): void { $vendorLocation = new DiscoveryLocation('Vendor\\Package', '/path/to/vendor/package'); $appLocation = new DiscoveryLocation('App\\Models', '/path/to/app/models'); - $this->assertTrue($vendorLocation->isVendor()); - $this->assertFalse($appLocation->isVendor()); - } + expect($vendorLocation->isVendor())->toBeTrue(); + expect($appLocation->isVendor())->toBeFalse(); + }); - public function test_detects_vendor_locations_windows_path(): void - { + it('detects vendor locations with windows path', function (): void { $vendorLocation = new DiscoveryLocation('Vendor\\Package', 'C:\\path\\to\\vendor\\package'); - $this->assertTrue($vendorLocation->isVendor()); - } + expect($vendorLocation->isVendor())->toBeTrue(); + }); - public function test_converts_file_path_to_class_name(): void - { + it('converts file path to class name', function (): void { $location = new DiscoveryLocation('App\\Models', '/app/Models'); - $className = $location->toClassName('/app/Models/User.php'); - - $this->assertEquals('App\\Models\\User', $className); - } + expect($location->toClassName('/app/Models/User.php'))->toBe('App\\Models\\User'); + }); - public function test_converts_nested_file_path_to_class_name(): void - { + it('converts nested file path to class name', function (): void { $location = new DiscoveryLocation('App\\Services', '/app/Services'); - $className = $location->toClassName('/app/Services/Auth/UserService.php'); + expect($location->toClassName('/app/Services/Auth/UserService.php'))->toBe('App\\Services\\Auth\\UserService'); + }); - $this->assertEquals('App\\Services\\Auth\\UserService', $className); - } - - public function test_returns_empty_string_for_file_outside_location(): void - { + it('returns empty string for file outside location', function (): void { $location = new DiscoveryLocation('App\\Models', '/app/Models'); - $className = $location->toClassName('/app/Services/UserService.php'); - - $this->assertEquals('', $className); - } + expect($location->toClassName('/app/Services/UserService.php'))->toBe(''); + }); - public function test_handles_windows_paths_in_class_name_conversion(): void - { + it('handles windows paths in class name conversion', function (): void { $location = new DiscoveryLocation('App\\Models', 'C:\\app\\Models'); - $className = $location->toClassName('C:\\app\\Models\\Auth\\User.php'); + expect($location->toClassName('C:\\app\\Models\\Auth\\User.php'))->toBe('App\\Models\\Auth\\User'); + }); - $this->assertEquals('App\\Models\\Auth\\User', $className); - } - - public function test_trims_namespace_slashes(): void - { + it('trims namespace slashes', function (): void { $location = new DiscoveryLocation('App\\Models\\', '/app/Models'); - $className = $location->toClassName('/app/Models/User.php'); - - $this->assertEquals('App\\Models\\User', $className); - } -} + expect($location->toClassName('/app/Models/User.php'))->toBe('App\\Models\\User'); + }); +}); diff --git a/tests/Unit/Exceptions/ModuleAwareErrorViewResolverTest.php b/tests/Unit/Exceptions/ModuleAwareErrorViewResolverTest.php index 2d932d77..fd24d80d 100644 --- a/tests/Unit/Exceptions/ModuleAwareErrorViewResolverTest.php +++ b/tests/Unit/Exceptions/ModuleAwareErrorViewResolverTest.php @@ -2,167 +2,84 @@ declare(strict_types=1); -namespace Tests\Unit\Exceptions; - use Illuminate\Contracts\Config\Repository; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Http\Request; -use PHPUnit\Framework\TestCase; use Pollora\Exceptions\Infrastructure\Services\ModuleAwareErrorViewResolver; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -/** - * Test suite for ModuleAwareErrorViewResolver. - * - * Tests the module-aware error view resolution functionality to ensure - * proper prioritization of module error views over framework defaults. - */ -class ModuleAwareErrorViewResolverTest extends TestCase -{ - protected ModuleAwareErrorViewResolver $resolver; - - protected Container $container; - - protected ViewFactory $viewFactory; - - protected function setUp(): void - { - parent::setUp(); - - $this->container = $this->createMock(Container::class); - $this->viewFactory = $this->createMock(ViewFactory::class); - - $this->resolver = new ModuleAwareErrorViewResolver( - $this->container, - $this->viewFactory - ); - } - - /** - * Test that resolver returns correct error view for 404 status. - */ - public function test_resolves_404_error_view(): void - { - $exception = new NotFoundHttpException('Not found'); - $request = Request::create('/test-path'); - - $this->viewFactory - ->expects($this->any()) - ->method('exists') - ->willReturnCallback(fn ($view) => $view === 'errors.404'); - - $result = $this->resolver->resolveErrorView($exception, $request, 404); - - $this->assertEquals('errors.404', $result); - } - - /** - * Test that resolver returns null when no error view exists. - */ - public function test_returns_null_when_no_view_exists(): void - { - $exception = new HttpException(500, 'Server error'); - $request = Request::create('/test-path'); - - $this->viewFactory - ->expects($this->any()) - ->method('exists') - ->willReturn(false); - - $result = $this->resolver->resolveErrorView($exception, $request, 500); - - $this->assertNull($result); - } - - /** - * Test that resolver tries fallback views for error categories. - */ - public function test_tries_fallback_views_for_error_categories(): void - { - $exception = new HttpException(403, 'Forbidden'); - $request = Request::create('/test-path'); - - $this->viewFactory - ->expects($this->any()) - ->method('exists') - ->willReturnCallback(fn ($view) => $view === 'errors.4xx'); - - $result = $this->resolver->resolveErrorView($exception, $request, 403); - - $this->assertEquals('errors.4xx', $result); - } - - /** - * Test that resolver converts exception class to view name. - */ - public function test_converts_exception_class_to_view_name(): void - { - $exception = new NotFoundHttpException('Not found'); - $request = Request::create('/test-path'); - - $this->viewFactory - ->expects($this->any()) - ->method('exists') - ->willReturnCallback(fn ($view) => $view === 'errors.not-found-http'); - - $result = $this->resolver->resolveErrorView($exception, $request, 404); - - $this->assertEquals('errors.not-found-http', $result); - } - - /** - * Test debug information generation returns empty in non-debug mode. - */ - public function test_returns_empty_debug_info_when_debug_disabled(): void - { - // Mock config service to return debug = false - $config = $this->createMock(Repository::class); - $config->expects($this->once()) - ->method('get') - ->with('app.debug', false) - ->willReturn(false); - - $this->container->expects($this->once()) - ->method('make') - ->with('config') - ->willReturn($config); - - $exception = new NotFoundHttpException('Not found'); - - $debugInfo = $this->resolver->getDebugInfo(404, $exception); - - $this->assertEmpty($debugInfo); - } - - /** - * Test that kebab case conversion works correctly. - */ - public function test_converts_pascal_case_to_kebab_case(): void - { - $reflection = new \ReflectionClass($this->resolver); +describe('ModuleAwareErrorViewResolver', function (): void { + beforeEach(function (): void { + $this->container = Mockery::mock(Container::class); + $this->viewFactory = Mockery::mock(ViewFactory::class); + $this->resolver = new ModuleAwareErrorViewResolver($this->container, $this->viewFactory); + }); + + it('resolves 404 error view', function (): void { + $this->viewFactory->shouldReceive('exists') + ->andReturnUsing(fn ($view): bool => $view === 'errors.404'); + + $result = $this->resolver->resolveErrorView(new NotFoundHttpException('Not found'), Request::create('/test-path'), 404); + + expect($result)->toBe('errors.404'); + }); + + it('returns null when no view exists', function (): void { + $this->viewFactory->shouldReceive('exists')->andReturn(false); + + $result = $this->resolver->resolveErrorView(new HttpException(500, 'Server error'), Request::create('/test-path'), 500); + + expect($result)->toBeNull(); + }); + + it('tries fallback views for error categories', function (): void { + $this->viewFactory->shouldReceive('exists') + ->andReturnUsing(fn ($view): bool => $view === 'errors.4xx'); + + $result = $this->resolver->resolveErrorView(new HttpException(403, 'Forbidden'), Request::create('/test-path'), 403); + + expect($result)->toBe('errors.4xx'); + }); + + it('converts exception class to view name', function (): void { + $this->viewFactory->shouldReceive('exists') + ->andReturnUsing(fn ($view): bool => $view === 'errors.not-found-http'); + + $result = $this->resolver->resolveErrorView(new NotFoundHttpException('Not found'), Request::create('/test-path'), 404); + + expect($result)->toBe('errors.not-found-http'); + }); + + it('returns empty debug info when debug disabled', function (): void { + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->with('app.debug', false)->once()->andReturn(false); + + $this->container->shouldReceive('make')->with('config')->once()->andReturn($config); + + $debugInfo = $this->resolver->getDebugInfo(404, new NotFoundHttpException('Not found')); + + expect($debugInfo)->toBeEmpty(); + }); + + it('converts PascalCase to kebab-case', function (): void { + $reflection = new ReflectionClass($this->resolver); $method = $reflection->getMethod('convertToKebabCase'); - $method->setAccessible(true); - - $this->assertEquals('not-found-http-exception', $method->invokeArgs($this->resolver, ['NotFoundHttpException'])); - $this->assertEquals('server-error', $method->invokeArgs($this->resolver, ['ServerError'])); - $this->assertEquals('test', $method->invokeArgs($this->resolver, ['Test'])); - $this->assertEquals('', $method->invokeArgs($this->resolver, [''])); - } - - /** - * Test that common suffixes are removed correctly. - */ - public function test_removes_common_suffixes(): void - { - $reflection = new \ReflectionClass($this->resolver); + + expect($method->invokeArgs($this->resolver, ['NotFoundHttpException']))->toBe('not-found-http-exception'); + expect($method->invokeArgs($this->resolver, ['ServerError']))->toBe('server-error'); + expect($method->invokeArgs($this->resolver, ['Test']))->toBe('test'); + expect($method->invokeArgs($this->resolver, ['']))->toBe(''); + }); + + it('removes common suffixes', function (): void { + $reflection = new ReflectionClass($this->resolver); $method = $reflection->getMethod('removeCommonSuffixes'); - $method->setAccessible(true); - - $this->assertEquals('NotFound', $method->invokeArgs($this->resolver, ['NotFoundException'])); - $this->assertEquals('Server', $method->invokeArgs($this->resolver, ['ServerError'])); - $this->assertEquals('NotFoundHttp', $method->invokeArgs($this->resolver, ['NotFoundHttpException'])); - $this->assertEquals('Test', $method->invokeArgs($this->resolver, ['Test'])); - } -} + + expect($method->invokeArgs($this->resolver, ['NotFoundException']))->toBe('NotFound'); + expect($method->invokeArgs($this->resolver, ['ServerError']))->toBe('Server'); + expect($method->invokeArgs($this->resolver, ['NotFoundHttpException']))->toBe('NotFoundHttp'); + expect($method->invokeArgs($this->resolver, ['Test']))->toBe('Test'); + }); +}); diff --git a/tests/Unit/Gutenberg/Helpers/PatternDataProcessorTest.php b/tests/Unit/Gutenberg/Helpers/PatternDataProcessorTest.php index d7e77225..4c37fd94 100755 --- a/tests/Unit/Gutenberg/Helpers/PatternDataProcessorTest.php +++ b/tests/Unit/Gutenberg/Helpers/PatternDataProcessorTest.php @@ -6,9 +6,9 @@ require_once __DIR__.'/../../helpers.php'; -describe('PatternDataProcessor', function () { +describe('PatternDataProcessor', function (): void { - it('extracts data from pattern file', function () { + it('extracts data from pattern file', function (): void { $processor = new PatternDataProcessor; // Mock of the global get_file_data function $data = $processor->getPatternData('dummy-path'); @@ -16,7 +16,7 @@ ->and($data['slug'])->toBe('slug-demo'); }); - it('processes array fields and viewportWidth', function () { + it('processes array fields and viewportWidth', function (): void { $processor = new PatternDataProcessor; $patternData = [ 'categories' => 'news,updates', @@ -31,7 +31,7 @@ ->and($result['viewportWidth'])->toBe(1200); }); - it('filters empty values', function () { + it('filters empty values', function (): void { $processor = new PatternDataProcessor; $patternData = [ 'categories' => '', diff --git a/tests/Unit/Gutenberg/Registrars/BlockCategoryRegistrarTest.php b/tests/Unit/Gutenberg/Registrars/BlockCategoryRegistrarTest.php index 9f4ca9fe..33667c10 100755 --- a/tests/Unit/Gutenberg/Registrars/BlockCategoryRegistrarTest.php +++ b/tests/Unit/Gutenberg/Registrars/BlockCategoryRegistrarTest.php @@ -6,9 +6,9 @@ require_once __DIR__.'/../../helpers.php'; -describe('PatternDataProcessor', function () { +describe('PatternDataProcessor', function (): void { - it('extracts data from pattern file', function () { + it('extracts data from pattern file', function (): void { $processor = new PatternDataProcessor; // Using the already defined stub for get_file_data $data = $processor->getPatternData('dummy-path'); @@ -16,7 +16,7 @@ ->and($data['slug'])->toBe('slug-demo'); }); - it('processes array fields and viewportWidth', function () { + it('processes array fields and viewportWidth', function (): void { $processor = new PatternDataProcessor; $patternData = [ 'categories' => 'news,updates', @@ -31,7 +31,7 @@ ->and($result['viewportWidth'])->toBe(1200); }); - it('filters empty values', function () { + it('filters empty values', function (): void { $processor = new PatternDataProcessor; $patternData = [ 'categories' => '', diff --git a/tests/Unit/Modules/Infrastructure/Services/ModuleAutoloaderTest.php b/tests/Unit/Modules/Infrastructure/Services/ModuleAutoloaderTest.php index 908c8612..8e40f101 100644 --- a/tests/Unit/Modules/Infrastructure/Services/ModuleAutoloaderTest.php +++ b/tests/Unit/Modules/Infrastructure/Services/ModuleAutoloaderTest.php @@ -4,161 +4,113 @@ use Composer\Autoload\ClassLoader; use Illuminate\Container\Container; -use PHPUnit\Framework\TestCase; use Pollora\Modules\Domain\Contracts\ModuleInterface; use Pollora\Modules\Infrastructure\Services\ModuleAutoloader; -class ModuleAutoloaderTest extends TestCase -{ - private Container $app; - - private ClassLoader $classLoader; - - private ModuleAutoloader $autoloader; - - protected function setUp(): void - { +describe('ModuleAutoloader', function (): void { + beforeEach(function (): void { $this->app = new Container; - $this->classLoader = $this->createMock(ClassLoader::class); - - // Bind the class loader to the container + $this->classLoader = Mockery::mock(ClassLoader::class)->shouldIgnoreMissing(); $this->app->instance(ClassLoader::class, $this->classLoader); - $this->autoloader = new ModuleAutoloader($this->app); - } + }); - public function test_it_builds_theme_namespace_correctly(): void - { + it('builds theme namespace correctly', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_namespace_'.uniqid(); - $appDir = $tempDir.'/app'; - mkdir($appDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); - $module = $this->createMockModule('TestTheme', $tempDir); + $module = createMockModuleForAutoloader('TestTheme', $tempDir); - $this->classLoader - ->expects($this->once()) - ->method('addPsr4') - ->with('Theme\\TestTheme\\', $tempDir.'/app'); + $this->classLoader->shouldReceive('addPsr4')->once()->with('Theme\\TestTheme\\', $tempDir.'/app'); $this->autoloader->registerTheme($module); - // Cleanup - rmdir($appDir); + rmdir($tempDir.'/app'); rmdir($tempDir); - } + }); - public function test_it_builds_plugin_namespace_correctly(): void - { + it('builds plugin namespace correctly', function (): void { $tempDir = sys_get_temp_dir().'/test_plugin_namespace_'.uniqid(); - $appDir = $tempDir.'/app'; - mkdir($appDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); - $module = $this->createMockModule('TestPlugin', $tempDir); + $module = createMockModuleForAutoloader('TestPlugin', $tempDir); - $this->classLoader - ->expects($this->once()) - ->method('addPsr4') - ->with('Plugin\\TestPlugin\\', $tempDir.'/app'); + $this->classLoader->shouldReceive('addPsr4')->once()->with('Plugin\\TestPlugin\\', $tempDir.'/app'); $this->autoloader->registerPlugin($module); - // Cleanup - rmdir($appDir); + rmdir($tempDir.'/app'); rmdir($tempDir); - } + }); - public function test_it_prefers_app_directory_over_src(): void - { + it('prefers app directory over src', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_preference_'.uniqid(); - $appDir = $tempDir.'/app'; - $srcDir = $tempDir.'/src'; - mkdir($appDir, 0777, true); - mkdir($srcDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); + mkdir($tempDir.'/src', 0777, true); - $module = $this->createMockModule('TestTheme', $tempDir); + $module = createMockModuleForAutoloader('TestTheme', $tempDir); - // Mock file system checks - $this->classLoader - ->expects($this->once()) - ->method('addPsr4') - ->with('Theme\\TestTheme\\', $tempDir.'/app'); + $this->classLoader->shouldReceive('addPsr4')->once()->with('Theme\\TestTheme\\', $tempDir.'/app'); $this->autoloader->registerTheme($module); - // Cleanup - rmdir($appDir); - rmdir($srcDir); + rmdir($tempDir.'/app'); + rmdir($tempDir.'/src'); rmdir($tempDir); - } + }); - public function test_it_tracks_registered_namespaces(): void - { + it('tracks registered namespaces', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_'.uniqid(); - $appDir = $tempDir.'/app'; - mkdir($appDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); - $module = $this->createMockModule('TestTheme', $tempDir); + $module = createMockModuleForAutoloader('TestTheme', $tempDir); $this->autoloader->registerTheme($module); - $this->assertTrue($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\')); - $this->assertFalse($this->autoloader->isNamespaceRegistered('Theme\\OtherTheme\\')); + expect($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\'))->toBeTrue(); + expect($this->autoloader->isNamespaceRegistered('Theme\\OtherTheme\\'))->toBeFalse(); - // Cleanup - rmdir($appDir); + rmdir($tempDir.'/app'); rmdir($tempDir); - } + }); - public function test_it_does_not_register_duplicate_namespaces(): void - { + it('does not register duplicate namespaces', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_duplicate_'.uniqid(); - $appDir = $tempDir.'/app'; - mkdir($appDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); - $module = $this->createMockModule('TestTheme', $tempDir); + $module = createMockModuleForAutoloader('TestTheme', $tempDir); - $this->classLoader - ->expects($this->once()) - ->method('addPsr4'); + $this->classLoader->shouldReceive('addPsr4')->once(); - // Register the same module twice $this->autoloader->registerTheme($module); $this->autoloader->registerTheme($module); - // Cleanup - rmdir($appDir); + rmdir($tempDir.'/app'); rmdir($tempDir); - } + }); - public function test_it_can_unregister_namespace(): void - { + it('can unregister namespace', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_unregister_'.uniqid(); - $appDir = $tempDir.'/app'; - mkdir($appDir, 0777, true); + mkdir($tempDir.'/app', 0777, true); - $module = $this->createMockModule('TestTheme', $tempDir); + $module = createMockModuleForAutoloader('TestTheme', $tempDir); $this->autoloader->registerTheme($module); - $this->assertTrue($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\')); + expect($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\'))->toBeTrue(); $this->autoloader->unregisterNamespace('Theme\\TestTheme\\'); - $this->assertFalse($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\')); + expect($this->autoloader->isNamespaceRegistered('Theme\\TestTheme\\'))->toBeFalse(); - // Cleanup - rmdir($appDir); + rmdir($tempDir.'/app'); rmdir($tempDir); - } - - private function createMockModule(string $name, string $path): ModuleInterface - { - $module = $this->createMock(ModuleInterface::class); + }); +}); - $module->method('getStudlyName') - ->willReturn($name); - - $module->method('getPath') - ->willReturn($path); +function createMockModuleForAutoloader(string $name, string $path): ModuleInterface +{ + $module = Mockery::mock(ModuleInterface::class); + $module->shouldReceive('getStudlyName')->andReturn($name); + $module->shouldReceive('getPath')->andReturn($path); - return $module; - } + return $module; } diff --git a/tests/Unit/Modules/ModuleAutoloadingIntegrationTest.php b/tests/Unit/Modules/ModuleAutoloadingIntegrationTest.php index defa9d12..becdf66a 100644 --- a/tests/Unit/Modules/ModuleAutoloadingIntegrationTest.php +++ b/tests/Unit/Modules/ModuleAutoloadingIntegrationTest.php @@ -8,38 +8,34 @@ use Pollora\Theme\Domain\Models\LaravelThemeModule; use Pollora\Theme\Infrastructure\Services\ThemeAutoloader; -beforeEach(function () { +beforeEach(function (): void { $this->app = new Container; // Register autoloader services in the container - $this->app->singleton(ModuleAutoloader::class, function ($app) { - return new ModuleAutoloader($app); - }); + $this->app->singleton(fn ($app): ModuleAutoloader => new ModuleAutoloader($app)); - $this->app->singleton(ThemeAutoloader::class, function ($app) { - return new ThemeAutoloader($app); - }); + $this->app->singleton(fn ($app): ThemeAutoloader => new ThemeAutoloader($app)); }); -afterEach(function () { +afterEach(function (): void { m::close(); }); -it('can create and use module autoloader', function () { +it('can create and use module autoloader', function (): void { $autoloader = $this->app->make(ModuleAutoloader::class); expect($autoloader)->toBeInstanceOf(ModuleAutoloader::class); expect($autoloader->getRegisteredNamespaces())->toBeArray()->toBeEmpty(); }); -it('can create and use theme autoloader', function () { +it('can create and use theme autoloader', function (): void { $autoloader = $this->app->make(ThemeAutoloader::class); expect($autoloader)->toBeInstanceOf(ThemeAutoloader::class); expect($autoloader->getThemeNamespace('TestTheme'))->toBe('Theme\\TestTheme\\'); }); -it('integrates autoloader with theme module registration', function () { +it('integrates autoloader with theme module registration', function (): void { // Create temporary directory for this test $tempDir = sys_get_temp_dir().'/pollora_module_test_'.uniqid(); $themePath = $tempDir.'/themes/test-theme'; @@ -48,7 +44,7 @@ // Create a real LaravelThemeModule instance for integration testing $theme = new class($tempDir.'/themes', $this->app) extends LaravelThemeModule { - public function __construct(string $path, $app) + public function __construct(string $path, Container $app) { parent::__construct('TestTheme', $path, $app); } diff --git a/tests/Unit/Option/Application/Services/OptionServiceTest.php b/tests/Unit/Option/Application/Services/OptionServiceTest.php index 6dad44bd..86c10b4c 100644 --- a/tests/Unit/Option/Application/Services/OptionServiceTest.php +++ b/tests/Unit/Option/Application/Services/OptionServiceTest.php @@ -2,131 +2,65 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Application\Services; - -use Mockery; -use PHPUnit\Framework\TestCase; use Pollora\Option\Application\Services\OptionService; use Pollora\Option\Domain\Contracts\OptionRepositoryInterface; use Pollora\Option\Domain\Models\Option; use Pollora\Option\Domain\Services\OptionValidationService; -final class OptionServiceTest extends TestCase -{ - private OptionService $service; - - private OptionRepositoryInterface $repository; - - private OptionValidationService $validator; - - protected function setUp(): void - { - parent::setUp(); - +describe('OptionService', function (): void { + beforeEach(function (): void { $this->repository = Mockery::mock(OptionRepositoryInterface::class); $this->validator = new OptionValidationService; $this->service = new OptionService($this->repository, $this->validator); - } - - protected function tearDown(): void - { - Mockery::close(); - parent::tearDown(); - } - - public function test_get_returns_option_value_when_exists(): void - { - $key = 'test_key'; - $value = 'test_value'; - $option = new Option($key, $value); - - $this->repository->shouldReceive('get')->with($key)->once()->andReturn($option); - - $result = $this->service->get($key); - - $this->assertEquals($value, $result); - } - - public function test_get_returns_default_when_option_not_exists(): void - { - $key = 'test_key'; - $default = 'default_value'; + }); - $this->repository->shouldReceive('get')->with($key)->once()->andReturn(null); + it('returns option value when exists', function (): void { + $this->repository->shouldReceive('get')->with('test_key')->once()->andReturn(new Option('test_key', 'test_value')); - $result = $this->service->get($key, $default); + expect($this->service->get('test_key'))->toBe('test_value'); + }); - $this->assertEquals($default, $result); - } + it('returns default when option not exists', function (): void { + $this->repository->shouldReceive('get')->with('test_key')->once()->andReturn(null); - public function test_get_returns_null_as_default_when_no_default_provided(): void - { - $key = 'test_key'; + expect($this->service->get('test_key', 'default_value'))->toBe('default_value'); + }); - $this->repository->shouldReceive('get')->with($key)->once()->andReturn(null); + it('returns null as default when no default provided', function (): void { + $this->repository->shouldReceive('get')->with('test_key')->once()->andReturn(null); - $result = $this->service->get($key); + expect($this->service->get('test_key'))->toBeNull(); + }); - $this->assertNull($result); - } - - public function test_set_stores_new_option_when_not_exists(): void - { - $key = 'test_key'; - $value = 'test_value'; - - $this->repository->shouldReceive('exists')->with($key)->once()->andReturn(false); + it('stores new option when not exists', function (): void { + $this->repository->shouldReceive('exists')->with('test_key')->once()->andReturn(false); $this->repository->shouldReceive('store')->once()->andReturn(true); - $result = $this->service->set($key, $value); - - $this->assertTrue($result); - } + expect($this->service->set('test_key', 'test_value'))->toBeTrue(); + }); - public function test_set_updates_existing_option_when_exists(): void - { - $key = 'test_key'; - $value = 'test_value'; - - $this->repository->shouldReceive('exists')->with($key)->once()->andReturn(true); + it('updates existing option when exists', function (): void { + $this->repository->shouldReceive('exists')->with('test_key')->once()->andReturn(true); $this->repository->shouldReceive('update')->once()->andReturn(true); - $result = $this->service->set($key, $value); - - $this->assertTrue($result); - } - - public function test_update_calls_repository_update(): void - { - $key = 'test_key'; - $value = 'test_value'; + expect($this->service->set('test_key', 'test_value'))->toBeTrue(); + }); + it('update calls repository update', function (): void { $this->repository->shouldReceive('update')->once()->andReturn(true); - $result = $this->service->update($key, $value); - - $this->assertTrue($result); - } - - public function test_delete_calls_repository_delete(): void - { - $key = 'test_key'; - - $this->repository->shouldReceive('delete')->with($key)->once()->andReturn(true); - - $result = $this->service->delete($key); - - $this->assertTrue($result); - } + expect($this->service->update('test_key', 'test_value'))->toBeTrue(); + }); - public function test_exists_calls_repository_exists(): void - { - $key = 'test_key'; + it('delete calls repository delete', function (): void { + $this->repository->shouldReceive('delete')->with('test_key')->once()->andReturn(true); - $this->repository->shouldReceive('exists')->with($key)->once()->andReturn(true); + expect($this->service->delete('test_key'))->toBeTrue(); + }); - $result = $this->service->exists($key); + it('exists calls repository exists', function (): void { + $this->repository->shouldReceive('exists')->with('test_key')->once()->andReturn(true); - $this->assertTrue($result); - } -} + expect($this->service->exists('test_key'))->toBeTrue(); + }); +}); diff --git a/tests/Unit/Option/Domain/Exceptions/InvalidOptionExceptionTest.php b/tests/Unit/Option/Domain/Exceptions/InvalidOptionExceptionTest.php index 89005037..adf45eed 100644 --- a/tests/Unit/Option/Domain/Exceptions/InvalidOptionExceptionTest.php +++ b/tests/Unit/Option/Domain/Exceptions/InvalidOptionExceptionTest.php @@ -2,25 +2,19 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Domain\Exceptions; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Domain\Exceptions\InvalidOptionException; -final class InvalidOptionExceptionTest extends TestCase -{ - public function test_creates_exception_with_custom_message(): void - { +describe('InvalidOptionException', function (): void { + it('creates exception with custom message', function (): void { $message = 'Custom error message'; $exception = new InvalidOptionException($message); - $this->assertEquals($message, $exception->getMessage()); - } + expect($exception->getMessage())->toBe($message); + }); - public function test_handles_empty_message(): void - { + it('handles empty message', function (): void { $exception = new InvalidOptionException(''); - $this->assertEquals('', $exception->getMessage()); - } -} + expect($exception->getMessage())->toBe(''); + }); +}); diff --git a/tests/Unit/Option/Domain/Exceptions/OptionNotFoundExceptionTest.php b/tests/Unit/Option/Domain/Exceptions/OptionNotFoundExceptionTest.php index e80a7dac..d904414d 100644 --- a/tests/Unit/Option/Domain/Exceptions/OptionNotFoundExceptionTest.php +++ b/tests/Unit/Option/Domain/Exceptions/OptionNotFoundExceptionTest.php @@ -2,24 +2,18 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Domain\Exceptions; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Domain\Exceptions\OptionNotFoundException; -final class OptionNotFoundExceptionTest extends TestCase -{ - public function test_creates_exception_with_formatted_message(): void - { +describe('OptionNotFoundException', function (): void { + it('creates exception with formatted message', function (): void { $exception = new OptionNotFoundException('test_key'); - $this->assertEquals("Option 'test_key' not found", $exception->getMessage()); - } + expect($exception->getMessage())->toBe("Option 'test_key' not found"); + }); - public function test_handles_special_characters_in_key(): void - { + it('handles special characters in key', function (): void { $exception = new OptionNotFoundException('test-key_with.special'); - $this->assertEquals("Option 'test-key_with.special' not found", $exception->getMessage()); - } -} + expect($exception->getMessage())->toBe("Option 'test-key_with.special' not found"); + }); +}); diff --git a/tests/Unit/Option/Domain/Models/OptionTest.php b/tests/Unit/Option/Domain/Models/OptionTest.php index dfd63d2b..061164ad 100644 --- a/tests/Unit/Option/Domain/Models/OptionTest.php +++ b/tests/Unit/Option/Domain/Models/OptionTest.php @@ -2,82 +2,72 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Domain\Models; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Domain\Models\Option; -final class OptionTest extends TestCase -{ - public function test_can_create_option_with_default_autoload(): void - { +describe('Option', function (): void { + it('can create option with default autoload', function (): void { $option = new Option('test_key', 'test_value'); - $this->assertEquals('test_key', $option->key); - $this->assertEquals('test_value', $option->value); - $this->assertTrue($option->autoload); - } + expect($option->key)->toBe('test_key'); + expect($option->value)->toBe('test_value'); + expect($option->autoload)->toBeTrue(); + }); - public function test_can_create_option_with_custom_autoload(): void - { + it('can create option with custom autoload', function (): void { $option = new Option('test_key', 'test_value', false); - $this->assertEquals('test_key', $option->key); - $this->assertEquals('test_value', $option->value); - $this->assertFalse($option->autoload); - } + expect($option->key)->toBe('test_key'); + expect($option->value)->toBe('test_value'); + expect($option->autoload)->toBeFalse(); + }); - public function test_can_create_option_with_different_value_types(): void - { + it('can create option with different value types', function (): void { $stringOption = new Option('string_key', 'test'); $intOption = new Option('int_key', 42); $arrayOption = new Option('array_key', ['foo' => 'bar']); $boolOption = new Option('bool_key', true); $nullOption = new Option('null_key', null); - $this->assertEquals('test', $stringOption->value); - $this->assertEquals(42, $intOption->value); - $this->assertEquals(['foo' => 'bar'], $arrayOption->value); - $this->assertTrue($boolOption->value); - $this->assertNull($nullOption->value); - } + expect($stringOption->value)->toBe('test'); + expect($intOption->value)->toBe(42); + expect($arrayOption->value)->toEqual(['foo' => 'bar']); + expect($boolOption->value)->toBeTrue(); + expect($nullOption->value)->toBeNull(); + }); - public function test_with_value_returns_new_instance(): void - { + it('withValue returns new instance', function (): void { $original = new Option('test_key', 'original_value'); $updated = $original->withValue('new_value'); - $this->assertNotSame($original, $updated); - $this->assertEquals('original_value', $original->value); - $this->assertEquals('new_value', $updated->value); - $this->assertEquals('test_key', $updated->key); - $this->assertEquals($original->autoload, $updated->autoload); - } + expect($updated)->not->toBe($original); + expect($original->value)->toBe('original_value'); + expect($updated->value)->toBe('new_value'); + expect($updated->key)->toBe('test_key'); + expect($updated->autoload)->toBe($original->autoload); + }); - public function test_with_autoload_returns_new_instance(): void - { + it('withAutoload returns new instance', function (): void { $original = new Option('test_key', 'test_value', true); $updated = $original->withAutoload(false); - $this->assertNotSame($original, $updated); - $this->assertTrue($original->autoload); - $this->assertFalse($updated->autoload); - $this->assertEquals($original->key, $updated->key); - $this->assertEquals($original->value, $updated->value); - } + expect($updated)->not->toBe($original); + expect($original->autoload)->toBeTrue(); + expect($updated->autoload)->toBeFalse(); + expect($updated->key)->toBe($original->key); + expect($updated->value)->toBe($original->value); + }); - public function test_chaining_with_methods(): void - { + it('supports chaining with methods', function (): void { $original = new Option('test_key', 'original_value', true); $updated = $original ->withValue('new_value') ->withAutoload(false); - $this->assertEquals('test_key', $updated->key); - $this->assertEquals('new_value', $updated->value); - $this->assertFalse($updated->autoload); + expect($updated->key)->toBe('test_key'); + expect($updated->value)->toBe('new_value'); + expect($updated->autoload)->toBeFalse(); - $this->assertEquals('original_value', $original->value); - $this->assertTrue($original->autoload); - } -} + expect($original->value)->toBe('original_value'); + expect($original->autoload)->toBeTrue(); + }); +}); diff --git a/tests/Unit/Option/Domain/Services/OptionValidationServiceTest.php b/tests/Unit/Option/Domain/Services/OptionValidationServiceTest.php index 4d0f3cae..1db8e48b 100644 --- a/tests/Unit/Option/Domain/Services/OptionValidationServiceTest.php +++ b/tests/Unit/Option/Domain/Services/OptionValidationServiceTest.php @@ -2,109 +2,64 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Domain\Services; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Domain\Exceptions\InvalidOptionException; use Pollora\Option\Domain\Services\OptionValidationService; -final class OptionValidationServiceTest extends TestCase -{ - private OptionValidationService $service; - - protected function setUp(): void - { - parent::setUp(); +describe('OptionValidationService', function (): void { + beforeEach(function (): void { $this->service = new OptionValidationService; - } + }); - public function test_validates_valid_key(): void - { - $this->expectNotToPerformAssertions(); + it('validates valid key', function (): void { $this->service->validateKey('valid_key'); - } - - public function test_throws_exception_for_empty_key(): void - { - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('Option key cannot be empty'); + })->throwsNoExceptions(); + it('throws exception for empty key', function (): void { $this->service->validateKey(''); - } - - public function test_throws_exception_for_too_long_key(): void - { - $longKey = str_repeat('a', 192); - - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('Option key cannot exceed 191 characters'); - - $this->service->validateKey($longKey); - } - - public function test_accepts_maximum_length_key(): void - { - $maxKey = str_repeat('a', 191); + })->throws(InvalidOptionException::class, 'Option key cannot be empty'); - $this->expectNotToPerformAssertions(); - $this->service->validateKey($maxKey); - } + it('throws exception for too long key', function (): void { + $this->service->validateKey(str_repeat('a', 192)); + })->throws(InvalidOptionException::class, 'Option key cannot exceed 191 characters'); - public function test_throws_exception_for_key_with_null_bytes(): void - { - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('Option key cannot contain null bytes'); + it('accepts maximum length key', function (): void { + $this->service->validateKey(str_repeat('a', 191)); + })->throwsNoExceptions(); + it('throws exception for key with null bytes', function (): void { $this->service->validateKey("test\0key"); - } - - public function test_validates_valid_scalar_values(): void - { - $this->expectNotToPerformAssertions(); + })->throws(InvalidOptionException::class, 'Option key cannot contain null bytes'); + it('validates valid scalar values', function (): void { $this->service->validateValue('string'); $this->service->validateValue(42); $this->service->validateValue(3.14); $this->service->validateValue(true); $this->service->validateValue(null); - } + })->throwsNoExceptions(); - public function test_validates_valid_array_value(): void - { - $this->expectNotToPerformAssertions(); + it('validates valid array value', function (): void { $this->service->validateValue(['key' => 'value']); - } + })->throwsNoExceptions(); - public function test_validates_serializable_object(): void - { - $object = new \stdClass; + it('validates serializable object', function (): void { + $object = new stdClass; $object->property = 'value'; - $this->expectNotToPerformAssertions(); $this->service->validateValue($object); - } + })->throwsNoExceptions(); - public function test_throws_exception_for_resource_value(): void - { + it('throws exception for resource value', function (): void { $resource = fopen('php://memory', 'r'); - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('Option value cannot be a resource'); - - $this->service->validateValue($resource); - - fclose($resource); - } - - public function test_throws_exception_for_non_serializable_object(): void - { - $closure = function () { - return 'test'; - }; - - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('Option value must be serializable'); - - $this->service->validateValue($closure); - } -} + try { + $this->service->validateValue($resource); + } finally { + fclose($resource); + } + })->throws(InvalidOptionException::class, 'Option value cannot be a resource'); + + it('throws exception for non-serializable object', function (): void { + $this->service->validateValue(fn (): string => 'test'); + })->throws(InvalidOptionException::class, 'Option value must be serializable'); +}); diff --git a/tests/Unit/Option/Infrastructure/Repositories/WordPressOptionRepositoryTest.php b/tests/Unit/Option/Infrastructure/Repositories/WordPressOptionRepositoryTest.php index f59e104e..0c2e5a00 100644 --- a/tests/Unit/Option/Infrastructure/Repositories/WordPressOptionRepositoryTest.php +++ b/tests/Unit/Option/Infrastructure/Repositories/WordPressOptionRepositoryTest.php @@ -2,114 +2,60 @@ declare(strict_types=1); -namespace Tests\Unit\Option\Infrastructure\Repositories; - -use PHPUnit\Framework\TestCase; use Pollora\Option\Domain\Models\Option; use Pollora\Option\Infrastructure\Repositories\WordPressOptionRepository; -final class WordPressOptionRepositoryTest extends TestCase -{ - private WordPressOptionRepository $repository; +require_once dirname(__DIR__, 3).'/helpers.php'; - protected function setUp(): void - { - parent::setUp(); +describe('WordPressOptionRepository', function (): void { + beforeEach(function (): void { setupWordPressMocks(); $this->repository = new WordPressOptionRepository; - } - - public function test_get_returns_option_when_exists(): void - { - $key = 'test_key'; - $value = 'test_value'; - - setWordPressFunction('get_option', function ($optionKey, $default) use ($key, $value) { - return $optionKey === $key ? $value : $default; - }); - - $result = $this->repository->get($key); - - $this->assertInstanceOf(Option::class, $result); - $this->assertEquals($key, $result->key); - $this->assertEquals($value, $result->value); - } - - public function test_get_returns_null_when_not_exists(): void - { - $key = 'non_existent_key'; - - setWordPressFunction('get_option', function ($optionKey, $default) use ($key) { - return $optionKey === $key ? null : $default; - }); - - $result = $this->repository->get($key); - - $this->assertNull($result); - } - - public function test_store_returns_true(): void - { - $option = new Option('test_key', 'test_value', true); - - setWordPressFunction('add_option', function () { - return true; - }); - - $result = $this->repository->store($option); - - $this->assertTrue($result); - } - - public function test_update_returns_true(): void - { - $option = new Option('test_key', 'updated_value', false); + }); - setWordPressFunction('update_option', function () { - return true; - }); + it('returns option when exists', function (): void { + setWordPressFunction('get_option', fn ($key, $default) => $key === 'test_key' ? 'test_value' : $default); - $result = $this->repository->update($option); + $result = $this->repository->get('test_key'); - $this->assertTrue($result); - } + expect($result)->toBeInstanceOf(Option::class); + expect($result->key)->toBe('test_key'); + expect($result->value)->toBe('test_value'); + }); - public function test_delete_returns_true(): void - { - $key = 'test_key'; + it('returns null when not exists', function (): void { + setWordPressFunction('get_option', fn ($key, $default) => $key === 'non_existent_key' ? null : $default); - setWordPressFunction('delete_option', function () { - return true; - }); + expect($this->repository->get('non_existent_key'))->toBeNull(); + }); - $result = $this->repository->delete($key); + it('store returns true', function (): void { + setWordPressFunction('add_option', fn (): true => true); - $this->assertTrue($result); - } + expect($this->repository->store(new Option('test_key', 'test_value', true)))->toBeTrue(); + }); - public function test_exists_returns_true_when_option_has_value(): void - { - $key = 'existing_key'; + it('update returns true', function (): void { + setWordPressFunction('update_option', fn (): true => true); - setWordPressFunction('get_option', function ($optionKey, $default) use ($key) { - return $optionKey === $key ? 'some_value' : $default; - }); + expect($this->repository->update(new Option('test_key', 'updated_value', false)))->toBeTrue(); + }); - $result = $this->repository->exists($key); + it('delete returns true', function (): void { + setWordPressFunction('delete_option', fn (): true => true); - $this->assertTrue($result); - } + expect($this->repository->delete('test_key'))->toBeTrue(); + }); - public function test_exists_returns_false_when_option_is_null(): void - { - $key = 'non_existent_key'; + it('exists returns true when option has value', function (): void { + setWordPressFunction('get_option', fn ($key, $default) => $key === 'existing_key' ? 'some_value' : $default); - setWordPressFunction('get_option', function ($optionKey, $default) use ($key) { - return $optionKey === $key ? null : $default; - }); + expect($this->repository->exists('existing_key'))->toBeTrue(); + }); - $result = $this->repository->exists($key); + it('exists returns false when option is null', function (): void { + setWordPressFunction('get_option', fn ($key, $default) => $key === 'non_existent_key' ? null : $default); - $this->assertFalse($result); - } -} + expect($this->repository->exists('non_existent_key'))->toBeFalse(); + }); +}); diff --git a/tests/Unit/Plugin/PluginMetadataTest.php b/tests/Unit/Plugin/PluginMetadataTest.php index 50e5a9eb..100e8934 100644 --- a/tests/Unit/Plugin/PluginMetadataTest.php +++ b/tests/Unit/Plugin/PluginMetadataTest.php @@ -2,173 +2,112 @@ declare(strict_types=1); -namespace Tests\Unit\Plugin; - -use PHPUnit\Framework\TestCase; use Pollora\Plugin\Domain\Models\PluginMetadata; -/** - * Tests for PluginMetadata class. - */ -class PluginMetadataTest extends TestCase -{ - private PluginMetadata $pluginMetadata; +describe('PluginMetadata', function (): void { + beforeEach(function (): void { + $this->pluginName = 'test-plugin'; + $this->basePath = '/path/to/plugins'; + $this->metadata = new PluginMetadata($this->pluginName, $this->basePath); + }); - private string $pluginName; + it('can be instantiated with name and base path', function (): void { + expect($this->metadata)->toBeInstanceOf(PluginMetadata::class); + expect($this->metadata->getName())->toBe($this->pluginName); + }); - private string $basePath; + it('returns correct base path', function (): void { + expect($this->metadata->getBasePath())->toBe($this->basePath.'/'.$this->pluginName); + }); - protected function setUp(): void - { - $this->pluginName = 'test-plugin'; - $this->basePath = '/path/to/plugins'; - $this->pluginMetadata = new PluginMetadata($this->pluginName, $this->basePath); - } - - public function test_it_can_be_instantiated_with_name_and_base_path(): void - { - $this->assertInstanceOf(PluginMetadata::class, $this->pluginMetadata); - $this->assertEquals($this->pluginName, $this->pluginMetadata->getName()); - } - - public function test_it_returns_correct_base_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName; - $this->assertEquals($expectedPath, $this->pluginMetadata->getBasePath()); - } - - public function test_it_returns_correct_main_file_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/'.$this->pluginName.'.php'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getMainFilePath()); - } - - public function test_it_returns_correct_config_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/plugin.json'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getConfigPath()); - } - - public function test_it_returns_correct_language_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/languages'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getLanguagePath()); - } - - public function test_it_returns_correct_plugin_namespace(): void - { - $expectedNamespace = 'TestPlugin'; - $this->assertEquals($expectedNamespace, $this->pluginMetadata->getPluginNamespace()); - } - - public function test_it_handles_complex_plugin_names(): void - { - $complexPlugin = new PluginMetadata('my-awesome-plugin', $this->basePath); - $expectedNamespace = 'MyAwesomePlugin'; - $this->assertEquals($expectedNamespace, $complexPlugin->getPluginNamespace()); - } - - public function test_it_returns_correct_plugin_app_dir(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/app'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPluginAppDir()); - } - - public function test_it_returns_correct_plugin_app_dir_with_subdirectory(): void - { - $subDirectory = 'Providers'; - $expectedPath = $this->basePath.'/'.$this->pluginName.'/app/'.$subDirectory; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPluginAppDir($subDirectory)); - } - - public function test_it_returns_correct_plugin_app_file(): void - { - $fileName = 'ServiceProvider.php'; - $expectedPath = $this->basePath.'/'.$this->pluginName.'/app/'.$fileName; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPluginAppFile($fileName)); - } - - public function test_it_returns_empty_config_initially(): void - { - $this->assertEquals([], $this->pluginMetadata->getConfig()); - } - - public function test_it_returns_correct_slug(): void - { - $this->assertEquals('test-plugin', $this->pluginMetadata->getSlug()); - } - - public function test_it_returns_correct_basename(): void - { - $expectedBasename = $this->pluginName.'/'.$this->pluginName.'.php'; - $this->assertEquals($expectedBasename, $this->pluginMetadata->getBasename()); - } - - public function test_it_handles_plugin_name_with_spaces(): void - { + it('returns correct main file path', function (): void { + expect($this->metadata->getMainFilePath())->toBe($this->basePath.'/'.$this->pluginName.'/'.$this->pluginName.'.php'); + }); + + it('returns correct config path', function (): void { + expect($this->metadata->getConfigPath())->toBe($this->basePath.'/'.$this->pluginName.'/plugin.json'); + }); + + it('returns correct language path', function (): void { + expect($this->metadata->getLanguagePath())->toBe($this->basePath.'/'.$this->pluginName.'/languages'); + }); + + it('returns correct plugin namespace', function (): void { + expect($this->metadata->getPluginNamespace())->toBe('TestPlugin'); + }); + + it('handles complex plugin names', function (): void { + $plugin = new PluginMetadata('my-awesome-plugin', $this->basePath); + expect($plugin->getPluginNamespace())->toBe('MyAwesomePlugin'); + }); + + it('returns correct plugin app dir', function (): void { + expect($this->metadata->getPluginAppDir())->toBe($this->basePath.'/'.$this->pluginName.'/app'); + }); + + it('returns correct plugin app dir with subdirectory', function (): void { + expect($this->metadata->getPluginAppDir('Providers'))->toBe($this->basePath.'/'.$this->pluginName.'/app/Providers'); + }); + + it('returns correct plugin app file', function (): void { + expect($this->metadata->getPluginAppFile('ServiceProvider.php'))->toBe($this->basePath.'/'.$this->pluginName.'/app/ServiceProvider.php'); + }); + + it('returns empty config initially', function (): void { + expect($this->metadata->getConfig())->toBe([]); + }); + + it('returns correct slug', function (): void { + expect($this->metadata->getSlug())->toBe('test-plugin'); + }); + + it('returns correct basename', function (): void { + expect($this->metadata->getBasename())->toBe($this->pluginName.'/'.$this->pluginName.'.php'); + }); + + it('handles plugin name with spaces', function (): void { $plugin = new PluginMetadata('My Plugin Name', $this->basePath); - $this->assertEquals('my-plugin-name', $plugin->getSlug()); - } + expect($plugin->getSlug())->toBe('my-plugin-name'); + }); - public function test_it_handles_plugin_name_with_underscores(): void - { + it('handles plugin name with underscores', function (): void { $plugin = new PluginMetadata('my_plugin_name', $this->basePath); - $this->assertEquals('my-plugin-name', $plugin->getSlug()); - } - - public function test_it_returns_correct_views_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/views'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getViewsPath()); - } - - public function test_it_returns_correct_assets_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/assets'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getAssetsPath()); - } - - public function test_it_returns_correct_routes_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/routes'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getRoutesPath()); - } - - public function test_it_returns_correct_config_dir(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/config'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getConfigDir()); - } - - public function test_it_returns_correct_database_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/database'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getDatabasePath()); - } - - public function test_it_returns_correct_tests_path(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/tests'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getTestsPath()); - } - - public function test_it_returns_path_for_item_with_array(): void - { - $pathParts = ['config', 'app.php']; - $expectedPath = $this->basePath.'/'.$this->pluginName.'/config/app.php'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPathForItem($pathParts)); - } - - public function test_it_returns_path_for_item_with_string(): void - { - $pathPart = 'config'; - $expectedPath = $this->basePath.'/'.$this->pluginName.'/config'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPathForItem($pathPart)); - } - - public function test_it_returns_path_for_item_with_null(): void - { - $expectedPath = $this->basePath.'/'.$this->pluginName.'/'; - $this->assertEquals($expectedPath, $this->pluginMetadata->getPathForItem(null)); - } -} + expect($plugin->getSlug())->toBe('my-plugin-name'); + }); + + it('returns correct views path', function (): void { + expect($this->metadata->getViewsPath())->toBe($this->basePath.'/'.$this->pluginName.'/views'); + }); + + it('returns correct assets path', function (): void { + expect($this->metadata->getAssetsPath())->toBe($this->basePath.'/'.$this->pluginName.'/assets'); + }); + + it('returns correct routes path', function (): void { + expect($this->metadata->getRoutesPath())->toBe($this->basePath.'/'.$this->pluginName.'/routes'); + }); + + it('returns correct config dir', function (): void { + expect($this->metadata->getConfigDir())->toBe($this->basePath.'/'.$this->pluginName.'/config'); + }); + + it('returns correct database path', function (): void { + expect($this->metadata->getDatabasePath())->toBe($this->basePath.'/'.$this->pluginName.'/database'); + }); + + it('returns correct tests path', function (): void { + expect($this->metadata->getTestsPath())->toBe($this->basePath.'/'.$this->pluginName.'/tests'); + }); + + it('returns path for item with array', function (): void { + expect($this->metadata->getPathForItem(['config', 'app.php']))->toBe($this->basePath.'/'.$this->pluginName.'/config/app.php'); + }); + + it('returns path for item with string', function (): void { + expect($this->metadata->getPathForItem('config'))->toBe($this->basePath.'/'.$this->pluginName.'/config'); + }); + + it('returns path for item with null', function (): void { + expect($this->metadata->getPathForItem(null))->toBe($this->basePath.'/'.$this->pluginName.'/'); + }); +}); diff --git a/tests/Unit/Plugin/PluginModuleTest.php b/tests/Unit/Plugin/PluginModuleTest.php index 30db1f79..57c588f1 100644 --- a/tests/Unit/Plugin/PluginModuleTest.php +++ b/tests/Unit/Plugin/PluginModuleTest.php @@ -2,258 +2,200 @@ declare(strict_types=1); -namespace Tests\Unit\Plugin; - -use PHPUnit\Framework\TestCase; use Pollora\Plugin\Domain\Models\PluginModule; -/** - * Tests for PluginModule class. - */ -class PluginModuleTest extends TestCase -{ - private PluginModule $pluginModule; - - private string $pluginName; - - private string $pluginPath; - - protected function setUp(): void - { +describe('PluginModule', function (): void { + beforeEach(function (): void { $this->pluginName = 'test-plugin'; $this->pluginPath = '/path/to/plugins/test-plugin'; - $this->pluginModule = new PluginModule($this->pluginName, $this->pluginPath); - } - - public function test_it_can_be_instantiated(): void - { - $this->assertInstanceOf(PluginModule::class, $this->pluginModule); - $this->assertEquals($this->pluginName, $this->pluginModule->getName()); - $this->assertEquals($this->pluginPath, $this->pluginModule->getPath()); - } - - public function test_it_has_default_disabled_state(): void - { - $this->assertFalse($this->pluginModule->isEnabled()); - $this->assertTrue($this->pluginModule->isDisabled()); - $this->assertFalse($this->pluginModule->isActive()); - } - - public function test_it_can_be_enabled_and_disabled(): void - { - $this->pluginModule->enable(); - $this->assertTrue($this->pluginModule->isEnabled()); - $this->assertFalse($this->pluginModule->isDisabled()); - - $this->pluginModule->disable(); - $this->assertFalse($this->pluginModule->isEnabled()); - $this->assertTrue($this->pluginModule->isDisabled()); - } - - public function test_it_can_be_activated_and_deactivated(): void - { - $this->pluginModule->activate(); - $this->assertTrue($this->pluginModule->isActive()); - - $this->pluginModule->deactivate(); - $this->assertFalse($this->pluginModule->isActive()); - } - - public function test_it_returns_correct_plugin_data(): void - { - $this->pluginModule->setHeaders([ + $this->module = new PluginModule($this->pluginName, $this->pluginPath); + }); + + it('can be instantiated', function (): void { + expect($this->module)->toBeInstanceOf(PluginModule::class); + expect($this->module->getName())->toBe($this->pluginName); + expect($this->module->getPath())->toBe($this->pluginPath); + }); + + it('has default disabled state', function (): void { + expect($this->module->isEnabled())->toBeFalse(); + expect($this->module->isDisabled())->toBeTrue(); + expect($this->module->isActive())->toBeFalse(); + }); + + it('can be enabled and disabled', function (): void { + $this->module->enable(); + expect($this->module->isEnabled())->toBeTrue(); + expect($this->module->isDisabled())->toBeFalse(); + + $this->module->disable(); + expect($this->module->isEnabled())->toBeFalse(); + expect($this->module->isDisabled())->toBeTrue(); + }); + + it('can be activated and deactivated', function (): void { + $this->module->activate(); + expect($this->module->isActive())->toBeTrue(); + + $this->module->deactivate(); + expect($this->module->isActive())->toBeFalse(); + }); + + it('returns correct plugin data', function (): void { + $this->module->setHeaders([ 'Name' => 'Test Plugin', 'Description' => 'A test plugin', 'Version' => '1.0.0', 'Author' => 'Test Author', ]); - $pluginData = $this->pluginModule->getPluginData(); - - $this->assertArrayHasKey('Name', $pluginData); - $this->assertArrayHasKey('Description', $pluginData); - $this->assertArrayHasKey('Version', $pluginData); - $this->assertArrayHasKey('Author', $pluginData); - $this->assertEquals('Test Plugin', $pluginData['Name']); - $this->assertEquals('A test plugin', $pluginData['Description']); - $this->assertEquals('1.0.0', $pluginData['Version']); - $this->assertEquals('Test Author', $pluginData['Author']); - } - - public function test_it_returns_correct_main_file_path(): void - { - $expectedPath = $this->pluginPath.'/'.$this->pluginName.'.php'; - $this->assertEquals($expectedPath, $this->pluginModule->getMainFile()); - } - - public function test_it_returns_default_version(): void - { - $this->assertEquals('1.0.0', $this->pluginModule->getVersion()); - } - - public function test_it_returns_custom_version_from_headers(): void - { - $this->pluginModule->setHeaders(['Version' => '2.1.0']); - $this->assertEquals('2.1.0', $this->pluginModule->getVersion()); - } - - public function test_it_returns_empty_author_by_default(): void - { - $this->assertEquals('', $this->pluginModule->getAuthor()); - } - - public function test_it_returns_custom_author_from_headers(): void - { - $this->pluginModule->setHeaders(['Author' => 'John Doe']); - $this->assertEquals('John Doe', $this->pluginModule->getAuthor()); - } - - public function test_it_returns_null_plugin_uri_by_default(): void - { - $this->assertNull($this->pluginModule->getPluginUri()); - } - - public function test_it_returns_custom_plugin_uri_from_headers(): void - { - $uri = 'https://example.com/plugin'; - $this->pluginModule->setHeaders(['PluginURI' => $uri]); - $this->assertEquals($uri, $this->pluginModule->getPluginUri()); - } - - public function test_it_returns_null_author_uri_by_default(): void - { - $this->assertNull($this->pluginModule->getAuthorUri()); - } - - public function test_it_returns_custom_author_uri_from_headers(): void - { - $uri = 'https://example.com'; - $this->pluginModule->setHeaders(['AuthorURI' => $uri]); - $this->assertEquals($uri, $this->pluginModule->getAuthorUri()); - } - - public function test_it_is_not_network_wide_by_default(): void - { - $this->assertFalse($this->pluginModule->isNetworkWide()); - } - - public function test_it_can_be_set_as_network_wide(): void - { - $this->pluginModule->setHeaders(['Network' => true]); - $this->assertTrue($this->pluginModule->isNetworkWide()); - } - - public function test_it_returns_plugin_name_as_text_domain_by_default(): void - { - $this->assertEquals($this->pluginName, $this->pluginModule->getTextDomain()); - } - - public function test_it_returns_custom_text_domain_from_headers(): void - { - $textDomain = 'custom-text-domain'; - $this->pluginModule->setHeaders(['TextDomain' => $textDomain]); - $this->assertEquals($textDomain, $this->pluginModule->getTextDomain()); - } - - public function test_it_returns_default_domain_path(): void - { - $this->assertEquals('/languages', $this->pluginModule->getDomainPath()); - } - - public function test_it_returns_custom_domain_path_from_headers(): void - { - $domainPath = '/lang'; - $this->pluginModule->setHeaders(['DomainPath' => $domainPath]); - $this->assertEquals($domainPath, $this->pluginModule->getDomainPath()); - } - - public function test_it_returns_empty_headers_by_default(): void - { - $this->assertEquals([], $this->pluginModule->getHeaders()); - } - - public function test_it_stores_and_returns_headers(): void - { - $headers = [ - 'Name' => 'Test Plugin', - 'Version' => '1.0.0', - 'Author' => 'Test Author', - ]; - - $this->pluginModule->setHeaders($headers); - $this->assertEquals($headers, $this->pluginModule->getHeaders()); - } - - public function test_it_returns_plugin_slug(): void - { - $this->assertEquals($this->pluginName, $this->pluginModule->getSlug()); - } - - public function test_it_returns_plugin_basename(): void - { - $expectedBasename = $this->pluginName.'/'.$this->pluginName.'.php'; - $this->assertEquals($expectedBasename, $this->pluginModule->getBasename()); - } - - public function test_it_returns_root_namespace(): void - { - $this->assertEquals('Plugin', $this->pluginModule->getRootNamespace()); - } - - public function test_it_returns_plugin_namespace(): void - { - $expectedNamespace = 'Plugin\\TestPlugin'; - $this->assertEquals($expectedNamespace, $this->pluginModule->getNamespace()); - } - - public function test_it_normalizes_plugin_name_for_namespace(): void - { + $data = $this->module->getPluginData(); + + expect($data)->toHaveKey('Name'); + expect($data)->toHaveKey('Description'); + expect($data)->toHaveKey('Version'); + expect($data)->toHaveKey('Author'); + expect($data['Name'])->toBe('Test Plugin'); + expect($data['Description'])->toBe('A test plugin'); + expect($data['Version'])->toBe('1.0.0'); + expect($data['Author'])->toBe('Test Author'); + }); + + it('returns correct main file path', function (): void { + expect($this->module->getMainFile())->toBe($this->pluginPath.'/'.$this->pluginName.'.php'); + }); + + it('returns default version', function (): void { + expect($this->module->getVersion())->toBe('1.0.0'); + }); + + it('returns custom version from headers', function (): void { + $this->module->setHeaders(['Version' => '2.1.0']); + expect($this->module->getVersion())->toBe('2.1.0'); + }); + + it('returns empty author by default', function (): void { + expect($this->module->getAuthor())->toBe(''); + }); + + it('returns custom author from headers', function (): void { + $this->module->setHeaders(['Author' => 'John Doe']); + expect($this->module->getAuthor())->toBe('John Doe'); + }); + + it('returns null plugin URI by default', function (): void { + expect($this->module->getPluginUri())->toBeNull(); + }); + + it('returns custom plugin URI from headers', function (): void { + $this->module->setHeaders(['PluginURI' => 'https://example.com/plugin']); + expect($this->module->getPluginUri())->toBe('https://example.com/plugin'); + }); + + it('returns null author URI by default', function (): void { + expect($this->module->getAuthorUri())->toBeNull(); + }); + + it('returns custom author URI from headers', function (): void { + $this->module->setHeaders(['AuthorURI' => 'https://example.com']); + expect($this->module->getAuthorUri())->toBe('https://example.com'); + }); + + it('is not network wide by default', function (): void { + expect($this->module->isNetworkWide())->toBeFalse(); + }); + + it('can be set as network wide', function (): void { + $this->module->setHeaders(['Network' => true]); + expect($this->module->isNetworkWide())->toBeTrue(); + }); + + it('returns plugin name as text domain by default', function (): void { + expect($this->module->getTextDomain())->toBe($this->pluginName); + }); + + it('returns custom text domain from headers', function (): void { + $this->module->setHeaders(['TextDomain' => 'custom-text-domain']); + expect($this->module->getTextDomain())->toBe('custom-text-domain'); + }); + + it('returns default domain path', function (): void { + expect($this->module->getDomainPath())->toBe('/languages'); + }); + + it('returns custom domain path from headers', function (): void { + $this->module->setHeaders(['DomainPath' => '/lang']); + expect($this->module->getDomainPath())->toBe('/lang'); + }); + + it('returns empty headers by default', function (): void { + expect($this->module->getHeaders())->toBe([]); + }); + + it('stores and returns headers', function (): void { + $headers = ['Name' => 'Test Plugin', 'Version' => '1.0.0', 'Author' => 'Test Author']; + $this->module->setHeaders($headers); + expect($this->module->getHeaders())->toBe($headers); + }); + + it('returns plugin slug', function (): void { + expect($this->module->getSlug())->toBe($this->pluginName); + }); + + it('returns plugin basename', function (): void { + expect($this->module->getBasename())->toBe($this->pluginName.'/'.$this->pluginName.'.php'); + }); + + it('returns root namespace', function (): void { + expect($this->module->getRootNamespace())->toBe('Plugin'); + }); + + it('returns plugin namespace', function (): void { + expect($this->module->getNamespace())->toBe('Plugin\\TestPlugin'); + }); + + it('normalizes plugin name for namespace', function (): void { $plugin = new PluginModule('my-awesome-plugin', '/path'); - $expectedNamespace = 'Plugin\\MyAwesomePlugin'; - $this->assertEquals($expectedNamespace, $plugin->getNamespace()); - } - - public function test_it_can_set_active_status(): void - { - $this->assertFalse($this->pluginModule->isActive()); - - $result = $this->pluginModule->setActive(true); - $this->assertTrue($this->pluginModule->isActive()); - $this->assertSame($this->pluginModule, $result); - - $this->pluginModule->setActive(false); - $this->assertFalse($this->pluginModule->isActive()); - } - - public function test_it_can_set_enabled_status(): void - { - $this->assertFalse($this->pluginModule->isEnabled()); - - $result = $this->pluginModule->setEnabled(true); - $this->assertTrue($this->pluginModule->isEnabled()); - $this->assertSame($this->pluginModule, $result); - - $this->pluginModule->setEnabled(false); - $this->assertFalse($this->pluginModule->isEnabled()); - } - - public function test_it_returns_null_for_optional_version_fields(): void - { - $this->assertNull($this->pluginModule->getRequiredWordPressVersion()); - $this->assertNull($this->pluginModule->getTestedWordPressVersion()); - $this->assertNull($this->pluginModule->getRequiredPhpVersion()); - } - - public function test_it_returns_custom_version_requirements_from_headers(): void - { - $this->pluginModule->setHeaders([ + expect($plugin->getNamespace())->toBe('Plugin\\MyAwesomePlugin'); + }); + + it('can set active status', function (): void { + expect($this->module->isActive())->toBeFalse(); + + $result = $this->module->setActive(true); + expect($this->module->isActive())->toBeTrue(); + expect($result)->toBe($this->module); + + $this->module->setActive(false); + expect($this->module->isActive())->toBeFalse(); + }); + + it('can set enabled status', function (): void { + expect($this->module->isEnabled())->toBeFalse(); + + $result = $this->module->setEnabled(true); + expect($this->module->isEnabled())->toBeTrue(); + expect($result)->toBe($this->module); + + $this->module->setEnabled(false); + expect($this->module->isEnabled())->toBeFalse(); + }); + + it('returns null for optional version fields', function (): void { + expect($this->module->getRequiredWordPressVersion())->toBeNull(); + expect($this->module->getTestedWordPressVersion())->toBeNull(); + expect($this->module->getRequiredPhpVersion())->toBeNull(); + }); + + it('returns custom version requirements from headers', function (): void { + $this->module->setHeaders([ 'RequiresWP' => '5.0', 'TestedUpTo' => '6.0', 'RequiresPHP' => '8.0', ]); - $this->assertEquals('5.0', $this->pluginModule->getRequiredWordPressVersion()); - $this->assertEquals('6.0', $this->pluginModule->getTestedWordPressVersion()); - $this->assertEquals('8.0', $this->pluginModule->getRequiredPhpVersion()); - } -} + expect($this->module->getRequiredWordPressVersion())->toBe('5.0'); + expect($this->module->getTestedWordPressVersion())->toBe('6.0'); + expect($this->module->getRequiredPhpVersion())->toBe('8.0'); + }); +}); diff --git a/tests/Unit/Plugins/WooCommerce/Domain/Models/TemplateTest.php b/tests/Unit/Plugins/WooCommerce/Domain/Models/TemplateTest.php index b79a03ff..149e2aeb 100644 --- a/tests/Unit/Plugins/WooCommerce/Domain/Models/TemplateTest.php +++ b/tests/Unit/Plugins/WooCommerce/Domain/Models/TemplateTest.php @@ -4,8 +4,8 @@ use Pollora\ThirdParty\WooCommerce\Domain\Models\Template; -describe('Template', function () { - test('can be created with basic parameters', function () { +describe('Template', function (): void { + test('can be created with basic parameters', function (): void { $template = new Template('/path/to/template.php', 'template', false); expect($template->path)->toBe('/path/to/template.php'); @@ -13,7 +13,7 @@ expect($template->isBladeTemplate)->toBeFalse(); }); - test('can be created from path', function () { + test('can be created from path', function (): void { $template = Template::fromPath('/path/to/single-product.php'); expect($template->path)->toBe('/path/to/single-product.php'); @@ -21,7 +21,7 @@ expect($template->isBladeTemplate)->toBeFalse(); }); - test('can detect blade templates when created from path', function () { + test('can detect blade templates when created from path', function (): void { $template = Template::fromPath('/path/to/single-product.blade.php'); expect($template->path)->toBe('/path/to/single-product.blade.php'); @@ -29,7 +29,7 @@ expect($template->isBladeTemplate)->toBeTrue(); }); - test('can get relative path', function () { + test('can get relative path', function (): void { $template = new Template('/plugin/templates/single-product.php'); $defaultPaths = ['/plugin/templates/']; @@ -38,7 +38,7 @@ expect($relativePath)->toBe('single-product.php'); }); - test('returns original path when no default paths match', function () { + test('returns original path when no default paths match', function (): void { $template = new Template('/theme/templates/single-product.php'); $defaultPaths = ['/plugin/templates/']; @@ -47,21 +47,21 @@ expect($relativePath)->toBe('/theme/templates/single-product.php'); }); - test('can detect woocommerce template', function () { + test('can detect woocommerce template', function (): void { $template = new Template('/plugin/templates/single-product.php'); $defaultPaths = ['/plugin/templates/']; expect($template->isWooCommerceTemplate($defaultPaths))->toBeTrue(); }); - test('can detect non-woocommerce template', function () { + test('can detect non-woocommerce template', function (): void { $template = new Template('/theme/templates/single-product.php'); $defaultPaths = ['/plugin/templates/']; expect($template->isWooCommerceTemplate($defaultPaths))->toBeFalse(); }); - test('can convert to blade template', function () { + test('can convert to blade template', function (): void { $template = new Template('/path/to/single-product.php', 'single-product', false); $bladeTemplate = $template->toBladeTemplate(); @@ -71,7 +71,7 @@ expect($bladeTemplate->isBladeTemplate)->toBeTrue(); }); - test('blade template conversion is idempotent', function () { + test('blade template conversion is idempotent', function (): void { $template = new Template('/path/to/single-product.blade.php', 'single-product', true); $bladeTemplate = $template->toBladeTemplate(); @@ -82,7 +82,7 @@ expect($bladeTemplate)->toBe($template); }); - test('non-php files are not converted to blade', function () { + test('non-php files are not converted to blade', function (): void { $template = new Template('/path/to/style.css', 'style', false); $bladeTemplate = $template->toBladeTemplate(); @@ -90,7 +90,7 @@ expect($bladeTemplate)->toBe($template); }); - test('can get view name for blade templates', function () { + test('can get view name for blade templates', function (): void { $template = new Template('woocommerce/single-product.blade.php', 'single-product', true); $viewName = $template->getViewName(); @@ -98,7 +98,7 @@ expect($viewName)->toBe('woocommerce.single-product'); }); - test('returns empty view name for non-blade templates', function () { + test('returns empty view name for non-blade templates', function (): void { $template = new Template('woocommerce/single-product.php', 'single-product', false); $viewName = $template->getViewName(); @@ -106,7 +106,7 @@ expect($viewName)->toBe(''); }); - test('handles complex path in view name', function () { + test('handles complex path in view name', function (): void { $template = new Template('woocommerce/cart/cart.blade.php', 'cart', true); $viewName = $template->getViewName(); @@ -114,7 +114,7 @@ expect($viewName)->toBe('woocommerce.cart.cart'); }); - test('trims dots from view name', function () { + test('trims dots from view name', function (): void { $template = new Template('/single-product.blade.php', 'single-product', true); $viewName = $template->getViewName(); diff --git a/tests/Unit/Plugins/WooCommerce/Domain/Services/WooCommerceServiceTest.php b/tests/Unit/Plugins/WooCommerce/Domain/Services/WooCommerceServiceTest.php index 290813c4..03112ed4 100644 --- a/tests/Unit/Plugins/WooCommerce/Domain/Services/WooCommerceServiceTest.php +++ b/tests/Unit/Plugins/WooCommerce/Domain/Services/WooCommerceServiceTest.php @@ -5,17 +5,17 @@ use Pollora\ThirdParty\WooCommerce\Domain\Models\Template; use Pollora\ThirdParty\WooCommerce\Domain\Services\WooCommerceService; -describe('WooCommerceService', function () { - beforeEach(function () { +describe('WooCommerceService', function (): void { + beforeEach(function (): void { setupWordPressMocks(); $this->service = new WooCommerceService; }); - afterEach(function () { + afterEach(function (): void { resetWordPressMocks(); }); - test('can get default template paths', function () { + test('can get default template paths', function (): void { // Mock WC_ABSPATH constant if (! defined('WC_ABSPATH')) { define('WC_ABSPATH', '/plugin/woocommerce/'); @@ -26,7 +26,7 @@ expect($paths)->toContain('/plugin/woocommerce/templates/'); }); - test('returns empty array when WC_ABSPATH not defined', function () { + test('returns empty array when WC_ABSPATH not defined', function (): void { // This test only works if WC_ABSPATH is not defined // Since we can't easily undefine constants in PHP, we'll skip if already defined if (defined('WC_ABSPATH')) { @@ -39,10 +39,10 @@ expect($paths)->toBe([]); }); - test('can get theme template paths for child themes', function () { + test('can get theme template paths for child themes', function (): void { // Mock WordPress functions - setWordPressFunction('is_child_theme', fn () => true); - setWordPressFunction('get_template_directory', fn () => '/themes/parent'); + setWordPressFunction('is_child_theme', fn (): true => true); + setWordPressFunction('get_template_directory', fn (): string => '/themes/parent'); // Mock WooCommerce function $mockWC = Mockery::mock(); @@ -54,15 +54,15 @@ expect($paths)->toContain('/themes/parent/woocommerce/'); }); - test('returns empty array for non-child themes', function () { - setWordPressFunction('is_child_theme', fn () => false); + test('returns empty array for non-child themes', function (): void { + setWordPressFunction('is_child_theme', fn (): false => false); $paths = $this->service->getThemeTemplatePaths(); expect($paths)->toBe([]); }); - test('can detect woocommerce status screen', function () { + test('can detect woocommerce status screen', function (): void { $screen = new stdClass; $screen->id = 'woocommerce_page_wc-status'; @@ -71,7 +71,7 @@ expect($result)->toBeTrue(); }); - test('returns false when not on woocommerce status screen', function () { + test('returns false when not on woocommerce status screen', function (): void { $screen = new stdClass; $screen->id = 'edit-post'; @@ -80,7 +80,7 @@ expect($result)->toBeFalse(); }); - test('returns false when doing ajax', function () { + test('returns false when doing ajax', function (): void { $screen = new stdClass; $screen->id = 'woocommerce_page_wc-status'; @@ -89,7 +89,7 @@ expect($result)->toBeFalse(); }); - test('returns false when not in admin', function () { + test('returns false when not in admin', function (): void { $screen = new stdClass; $screen->id = 'woocommerce_page_wc-status'; @@ -98,7 +98,7 @@ expect($result)->toBeFalse(); }); - test('can get woocommerce template path with WC available', function () { + test('can get woocommerce template path with WC available', function (): void { $mockWC = Mockery::mock(); $mockWC->shouldReceive('template_path')->andReturn('woocommerce/'); setWordPressFunction('WC', fn () => $mockWC); @@ -108,22 +108,22 @@ expect($path)->toBe('woocommerce/'); }); - test('returns default path when WC not available', function () { - setWordPressFunction('WC', fn () => null); + test('returns default path when WC not available', function (): void { + setWordPressFunction('WC', fn (): null => null); $path = $this->service->getWooCommerceTemplatePath(); expect($path)->toBe('woocommerce/'); }); - test('can get all template paths', function () { + test('can get all template paths', function (): void { // Mock WC_ABSPATH constant if (! defined('WC_ABSPATH')) { define('WC_ABSPATH', '/plugin/woocommerce/'); } - setWordPressFunction('is_child_theme', fn () => true); - setWordPressFunction('get_template_directory', fn () => '/themes/parent'); + setWordPressFunction('is_child_theme', fn (): true => true); + setWordPressFunction('get_template_directory', fn (): string => '/themes/parent'); $mockWC = Mockery::mock(); $mockWC->shouldReceive('template_path')->andReturn('woocommerce/'); @@ -135,14 +135,14 @@ expect($paths)->toContain('/themes/parent/woocommerce/'); }); - test('can create template from path', function () { + test('can create template from path', function (): void { $template = $this->service->createTemplate('/path/to/single-product.php'); expect($template)->toBeInstanceOf(Template::class); expect($template->path)->toBe('/path/to/single-product.php'); }); - test('can add blade variants to template list', function () { + test('can add blade variants to template list', function (): void { $templates = [ 'single-product.php', 'archive-product.php', @@ -159,7 +159,7 @@ expect($result)->not->toContain('style.blade.css'); }); - test('does not duplicate existing blade templates', function () { + test('does not duplicate existing blade templates', function (): void { $templates = [ 'single-product.blade.php', 'archive-product.php', @@ -176,7 +176,7 @@ expect(array_count_values($result)['single-product.blade.php'])->toBe(1); }); - test('handles empty template list', function () { + test('handles empty template list', function (): void { $templates = []; $result = $this->service->addBladeVariants($templates); diff --git a/tests/Unit/Plugins/WooCommerce/Infrastructure/Adapters/WordPressWooCommerceAdapterTest.php b/tests/Unit/Plugins/WooCommerce/Infrastructure/Adapters/WordPressWooCommerceAdapterTest.php index 6ddf7bae..699d0ce5 100644 --- a/tests/Unit/Plugins/WooCommerce/Infrastructure/Adapters/WordPressWooCommerceAdapterTest.php +++ b/tests/Unit/Plugins/WooCommerce/Infrastructure/Adapters/WordPressWooCommerceAdapterTest.php @@ -4,18 +4,18 @@ use Pollora\ThirdParty\WooCommerce\Infrastructure\Adapters\WordPressWooCommerceAdapter; -describe('WordPressWooCommerceAdapter', function () { - beforeEach(function () { +describe('WordPressWooCommerceAdapter', function (): void { + beforeEach(function (): void { setupWordPressMocks(); $this->adapter = new WordPressWooCommerceAdapter; }); - afterEach(function () { + afterEach(function (): void { resetWordPressMocks(); }); - test('can locate template using wordpress function', function () { - setWordPressFunction('locate_template', function ($templates, $load, $requireOnce) { + test('can locate template using wordpress function', function (): void { + setWordPressFunction('locate_template', function ($templates, $load, $requireOnce): string { expect($templates)->toBe('single-product.php'); expect($load)->toBeFalse(); expect($requireOnce)->toBeTrue(); @@ -28,8 +28,8 @@ expect($result)->toBe('/theme/single-product.php'); }); - test('can locate template with array of templates', function () { - setWordPressFunction('locate_template', function ($templates, $load, $requireOnce) { + test('can locate template with array of templates', function (): void { + setWordPressFunction('locate_template', function ($templates, $load, $requireOnce): string { expect($templates)->toBe(['single-product.blade.php', 'single-product.php']); return '/theme/single-product.php'; @@ -40,7 +40,7 @@ expect($result)->toBe('/theme/single-product.php'); }); - test('returns empty string when locate_template function not available', function () { + test('returns empty string when locate_template function not available', function (): void { // Don't set the function to simulate unavailability $adapter = new WordPressWooCommerceAdapter; @@ -49,8 +49,8 @@ expect($result)->toBe(''); }); - test('can add theme support', function () { - setWordPressFunction('add_theme_support', function ($feature, $options = null) { + test('can add theme support', function (): void { + setWordPressFunction('add_theme_support', function ($feature, $options = null): true { expect($feature)->toBe('woocommerce'); expect($options)->toBeNull(); @@ -62,8 +62,8 @@ expect($result)->toBeTrue(); }); - test('can add theme support with options', function () { - setWordPressFunction('add_theme_support', function ($feature, $options = null) { + test('can add theme support with options', function (): void { + setWordPressFunction('add_theme_support', function ($feature, $options = null): true { expect($feature)->toBe('woocommerce'); expect($options)->toBe(['gallery_thumbnail_image_width' => 150]); @@ -77,67 +77,67 @@ // Removed test for function availability since functions are always defined in our test environment - test('can detect child theme', function () { - setWordPressFunction('is_child_theme', fn () => true); + test('can detect child theme', function (): void { + setWordPressFunction('is_child_theme', fn (): true => true); $result = $this->adapter->isChildTheme(); expect($result)->toBeTrue(); }); - test('returns false when not child theme', function () { - setWordPressFunction('is_child_theme', fn () => false); + test('returns false when not child theme', function (): void { + setWordPressFunction('is_child_theme', fn (): false => false); $result = $this->adapter->isChildTheme(); expect($result)->toBeFalse(); }); - test('can get stylesheet directory', function () { - setWordPressFunction('get_stylesheet_directory', fn () => '/themes/child'); + test('can get stylesheet directory', function (): void { + setWordPressFunction('get_stylesheet_directory', fn (): string => '/themes/child'); $result = $this->adapter->getStylesheetDirectory(); expect($result)->toBe('/themes/child'); }); - test('can get template directory', function () { - setWordPressFunction('get_template_directory', fn () => '/themes/parent'); + test('can get template directory', function (): void { + setWordPressFunction('get_template_directory', fn (): string => '/themes/parent'); $result = $this->adapter->getTemplateDirectory(); expect($result)->toBe('/themes/parent'); }); - test('can detect admin area', function () { - setWordPressFunction('is_admin', fn () => true); + test('can detect admin area', function (): void { + setWordPressFunction('is_admin', fn (): true => true); $result = $this->adapter->isAdmin(); expect($result)->toBeTrue(); }); - test('can detect ajax request', function () { - setWordPressFunction('wp_doing_ajax', fn () => true); + test('can detect ajax request', function (): void { + setWordPressFunction('wp_doing_ajax', fn (): true => true); $result = $this->adapter->isDoingAjax(); expect($result)->toBeTrue(); }); - test('can get current screen', function () { + test('can get current screen', function (): void { $expectedScreen = new WP_Screen; $expectedScreen->id = 'woocommerce_page_wc-status'; - setWordPressFunction('get_current_screen', fn () => $expectedScreen); + setWordPressFunction('get_current_screen', fn (): WP_Screen => $expectedScreen); $result = $this->adapter->getCurrentScreen(); expect($result)->toBe($expectedScreen); }); - test('can detect doing action', function () { - setWordPressFunction('doing_action', function ($action) { + test('can detect doing action', function (): void { + setWordPressFunction('doing_action', function ($action): true { expect($action)->toBe('after_setup_theme'); return true; @@ -148,7 +148,7 @@ expect($result)->toBeTrue(); }); - test('can get woocommerce template path', function () { + test('can get woocommerce template path', function (): void { $mockWC = Mockery::mock(); $mockWC->shouldReceive('template_path')->andReturn('woocommerce/'); setWordPressFunction('WC', fn () => $mockWC); @@ -158,16 +158,16 @@ expect($result)->toBe('woocommerce/'); }); - test('returns default template path when WC not available', function () { - setWordPressFunction('WC', fn () => null); + test('returns default template path when WC not available', function (): void { + setWordPressFunction('WC', fn (): null => null); $result = $this->adapter->getWooCommerceTemplatePath(); expect($result)->toBe('woocommerce/'); }); - test('can apply filters', function () { - setWordPressFunction('apply_filters', function ($hook, $value, ...$args) { + test('can apply filters', function (): void { + setWordPressFunction('apply_filters', function ($hook, $value, ...$args): array { expect($hook)->toBe('pollora/woocommerce/template_paths'); expect($value)->toBe(['/default/path/']); expect($args)->toBe(['extra', 'args']); @@ -180,7 +180,7 @@ expect($result)->toBe(['/default/path/', '/custom/path/']); }); - test('returns original value when apply_filters not available', function () { + test('returns original value when apply_filters not available', function (): void { $adapter = new WordPressWooCommerceAdapter; $result = $adapter->applyFilters('test_hook', 'test_value'); @@ -188,12 +188,12 @@ expect($result)->toBe('test_value'); }); - test('can detect woocommerce availability', function () { + test('can detect woocommerce availability', function (): void { if (! defined('WC_ABSPATH')) { define('WC_ABSPATH', '/plugin/woocommerce/'); } - setWordPressFunction('WC', fn () => new stdClass); + setWordPressFunction('WC', fn (): stdClass => new stdClass); $result = $this->adapter->isWooCommerceAvailable(); diff --git a/tests/Unit/Plugins/WooCommerce/Infrastructure/Providers/WooCommerceServiceProviderTest.php b/tests/Unit/Plugins/WooCommerce/Infrastructure/Providers/WooCommerceServiceProviderTest.php index a45779c7..98343ba6 100644 --- a/tests/Unit/Plugins/WooCommerce/Infrastructure/Providers/WooCommerceServiceProviderTest.php +++ b/tests/Unit/Plugins/WooCommerce/Infrastructure/Providers/WooCommerceServiceProviderTest.php @@ -17,26 +17,26 @@ use Pollora\ThirdParty\WooCommerce\Infrastructure\Services\WooCommerceTemplateResolver; use Pollora\View\Domain\Contracts\TemplateFinderInterface; -describe('WooCommerceServiceProvider', function () { - beforeEach(function () { +describe('WooCommerceServiceProvider', function (): void { + beforeEach(function (): void { setupWordPressMocks(); $this->container = new WooCommerceTestContainer; $this->provider = new WooCommerceServiceProvider($this->container); }); - afterEach(function () { + afterEach(function (): void { resetWordPressMocks(); Mockery::close(); }); - test('can register domain services', function () { + test('can register domain services', function (): void { $this->provider->register(); expect($this->container->has(WooCommerceService::class))->toBeTrue(); }); - test('can register infrastructure services', function () { + test('can register infrastructure services', function (): void { // Mock required dependencies $this->container->instance(TemplateFinderInterface::class, Mockery::mock(TemplateFinderInterface::class)); $this->container->instance(ViewFactory::class, Mockery::mock(ViewFactory::class)); @@ -48,7 +48,7 @@ expect($this->container->has(TemplateResolverInterface::class))->toBeTrue(); }); - test('can register application services', function () { + test('can register application services', function (): void { // Mock required dependencies $this->container->instance(Action::class, Mockery::mock(Action::class)); $this->container->instance(Filter::class, Mockery::mock(Filter::class)); @@ -60,7 +60,7 @@ expect($this->container->has(RegisterWooCommerceHooksUseCase::class))->toBeTrue(); }); - test('maintains backward compatibility bindings', function () { + test('maintains backward compatibility bindings', function (): void { // Mock required dependencies $this->container->instance(TemplateFinderInterface::class, Mockery::mock(TemplateFinderInterface::class)); $this->container->instance(ViewFactory::class, Mockery::mock(ViewFactory::class)); @@ -71,7 +71,7 @@ expect($this->container->has(Pollora\ThirdParty\WooCommerce\View\WooCommerceTemplateResolver::class))->toBeTrue(); }); - test('can resolve woocommerce integration service', function () { + test('can resolve woocommerce integration service', function (): void { // Mock all required dependencies $this->container->instance(TemplateFinderInterface::class, Mockery::mock(TemplateFinderInterface::class)); $this->container->instance(ViewFactory::class, Mockery::mock(ViewFactory::class)); @@ -84,7 +84,7 @@ expect($service)->toBeInstanceOf(WooCommerce::class); }); - test('can resolve template resolver service', function () { + test('can resolve template resolver service', function (): void { // Mock all required dependencies $this->container->instance(TemplateFinderInterface::class, Mockery::mock(TemplateFinderInterface::class)); $this->container->instance(ViewFactory::class, Mockery::mock(ViewFactory::class)); @@ -96,7 +96,7 @@ expect($service)->toBeInstanceOf(WooCommerceTemplateResolver::class); }); - test('can resolve use case with all dependencies', function () { + test('can resolve use case with all dependencies', function (): void { // Mock all required dependencies $this->container->instance(Action::class, Mockery::mock(Action::class)); $this->container->instance(Filter::class, Mockery::mock(Filter::class)); @@ -111,7 +111,7 @@ expect($useCase)->toBeInstanceOf(RegisterWooCommerceHooksUseCase::class); }); - test('executes use case on boot', function () { + test('executes use case on boot', function (): void { // Create a mock use case that tracks execution $useCase = Mockery::mock(RegisterWooCommerceHooksUseCase::class); $useCase->shouldReceive('execute')->once(); @@ -129,15 +129,13 @@ class WooCommerceTestContainer extends TestContainer implements Container private array $services = []; - public function singleton($abstract, $concrete = null) + public function singleton($abstract, $concrete = null): void { if ($concrete instanceof Closure) { $this->singletons[$abstract] = $concrete; } elseif ($concrete === null) { // When no concrete is provided, Laravel auto-resolves the class - $this->singletons[$abstract] = function ($container) use ($abstract) { - return new $abstract; - }; + $this->singletons[$abstract] = (fn ($container): object => new $abstract); } else { $this->services[$abstract] = $concrete; } @@ -158,7 +156,7 @@ public function get(string $serviceClass): ?object return parent::get($serviceClass); } - public function make($abstract, array $parameters = []) + public function make($abstract, array $parameters = []): ?object { // Support both Laravel Container interface (mixed $abstract, array $parameters) // and TestContainer interface (string $serviceClass) @@ -243,9 +241,7 @@ public function when($concrete): ContextualBindingBuilder public function factory($abstract): Closure { - return function () use ($abstract) { - return $this->make($abstract); - }; + return fn (): ?object => $this->make($abstract); } public function flush(): void @@ -275,20 +271,21 @@ public function call($callback, array $parameters = [], $defaultMethod = null) if (is_callable($callback)) { return call_user_func_array($callback, $parameters); } + throw new Exception('call() not fully implemented in test container'); } - public function bindMethod($method, $callback) + public function bindMethod($method, $callback): void { // Simplified implementation } - public function addContextualBinding($concrete, $abstract, $implementation) + public function addContextualBinding($concrete, $abstract, $implementation): void { // Simplified implementation } - public function beforeResolving($abstract, $callback = null) + public function beforeResolving($abstract, $callback = null): void { // Simplified implementation } diff --git a/tests/Unit/PostType/Commands/PostTypeMakeCommandTest.php b/tests/Unit/PostType/Commands/PostTypeMakeCommandTest.php index dc7cea78..bdefb9b4 100644 --- a/tests/Unit/PostType/Commands/PostTypeMakeCommandTest.php +++ b/tests/Unit/PostType/Commands/PostTypeMakeCommandTest.php @@ -6,7 +6,7 @@ use Mockery as m; use Pollora\PostType\UI\Console\PostTypeMakeCommand; -beforeEach(function () { +beforeEach(function (): void { // Create a mock filesystem $this->files = m::mock(Filesystem::class); @@ -14,34 +14,31 @@ $this->command = new PostTypeMakeCommand($this->files); }); -afterEach(function () { +afterEach(function (): void { m::close(); }); -test('PostTypeMakeCommand generates correct slug from class name', function () { +test('PostTypeMakeCommand generates correct slug from class name', function (): void { // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(PostTypeMakeCommand::class, 'getSlugFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'EventRegistration'); expect($result)->toBe('event-registration'); }); -test('PostTypeMakeCommand generates correct singular name from class name', function () { +test('PostTypeMakeCommand generates correct singular name from class name', function (): void { // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(PostTypeMakeCommand::class, 'getNameFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'EventRegistration'); expect($result)->toBe('Event registration'); }); -test('PostTypeMakeCommand generates correct plural name from class name', function () { +test('PostTypeMakeCommand generates correct plural name from class name', function (): void { // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(PostTypeMakeCommand::class, 'getPluralNameFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'Event'); diff --git a/tests/Unit/PostType/PostTypeAttributeServiceProviderTest.php b/tests/Unit/PostType/PostTypeAttributeServiceProviderTest.php index 4562f098..2526e25f 100644 --- a/tests/Unit/PostType/PostTypeAttributeServiceProviderTest.php +++ b/tests/Unit/PostType/PostTypeAttributeServiceProviderTest.php @@ -9,7 +9,7 @@ // Define app_path function if it doesn't exist in the test environment if (! function_exists('app_path')) { - function app_path($path = '') + function app_path(string $path = ''): string { return '/path/to/app/'.$path; } @@ -17,7 +17,7 @@ function app_path($path = '') // Define is_dir function if needed for testing if (! function_exists('is_dir_mock')) { - function is_dir_mock($path) + function is_dir_mock($path): bool { return true; // Always return true for testing } @@ -25,13 +25,13 @@ function is_dir_mock($path) // Define mkdir function if needed for testing if (! function_exists('mkdir_mock')) { - function mkdir_mock($path, $mode = 0777, $recursive = false) + function mkdir_mock($path, $mode = 0777, $recursive = false): bool { return true; // Always return true for testing } } -beforeAll(function () { +beforeAll(function (): void { $app = new Container; Facade::setFacadeApplication($app); @@ -46,7 +46,7 @@ function mkdir_mock($path, $mode = 0777, $recursive = false) Action::setFacadeApplication($app); }); -afterAll(function () { +afterAll(function (): void { m::close(); Facade::clearResolvedInstances(); Facade::setFacadeApplication(null); diff --git a/tests/Unit/PostType/PostTypeFactoryTest.php b/tests/Unit/PostType/PostTypeFactoryTest.php index a398d52d..65fdc640 100755 --- a/tests/Unit/PostType/PostTypeFactoryTest.php +++ b/tests/Unit/PostType/PostTypeFactoryTest.php @@ -6,12 +6,12 @@ use Pollora\PostType\Infrastructure\Factories\PostTypeFactory; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); $this->factory = new PostTypeFactory; }); -test('make creates new PostType instance with correct parameters', function () { +test('make creates new PostType instance with correct parameters', function (): void { // Define test values $slug = 'test-post-type'; $singular = 'Test Post Type'; @@ -26,7 +26,7 @@ ->and($result->getSlug())->toBe($slug); }); -test('make handles null parameters correctly', function () { +test('make handles null parameters correctly', function (): void { // Define test values $slug = 'test-post-type'; diff --git a/tests/Unit/PostType/PostTypeServiceTest.php b/tests/Unit/PostType/PostTypeServiceTest.php index a9b217c1..3fae6437 100755 --- a/tests/Unit/PostType/PostTypeServiceTest.php +++ b/tests/Unit/PostType/PostTypeServiceTest.php @@ -8,13 +8,13 @@ use Pollora\PostType\Domain\Contracts\PostTypeFactoryInterface; use Pollora\PostType\Domain\Contracts\PostTypeRegistryInterface; -beforeEach(function () { +beforeEach(function (): void { $this->mockFactory = mock(PostTypeFactoryInterface::class); $this->mockRegistry = mock(PostTypeRegistryInterface::class); $this->postTypeService = new PostTypeService($this->mockFactory, $this->mockRegistry); }); -test('register calls make on factory', function () { +test('register calls make on factory', function (): void { // Define test values $slug = 'test-post-type'; $singular = 'Test Post Type'; @@ -39,7 +39,7 @@ expect($result)->toBe($mockPostType); }); -test('exists calls exists on registry', function () { +test('exists calls exists on registry', function (): void { // Define test values $slug = 'test-post-type'; @@ -57,7 +57,7 @@ expect($result)->toBeTrue(); }); -test('getRegistered calls getAll on registry', function () { +test('getRegistered calls getAll on registry', function (): void { // Define test values $registeredPostTypes = ['post' => [], 'page' => [], 'test-post-type' => []]; diff --git a/tests/Unit/Route/Domain/Models/RouteTest.php b/tests/Unit/Route/Domain/Models/RouteTest.php index a3750184..71de0ce1 100644 --- a/tests/Unit/Route/Domain/Models/RouteTest.php +++ b/tests/Unit/Route/Domain/Models/RouteTest.php @@ -2,58 +2,42 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Domain\Models; - use Illuminate\Http\Request; -use PHPUnit\Framework\TestCase; use Pollora\Route\Domain\Models\Route; -class RouteTest extends TestCase -{ - private Route $route; - - protected function setUp(): void - { - parent::setUp(); +describe('Route', function (): void { + beforeEach(function (): void { + $this->route = new Route(['GET'], '/test', fn (): string => 'test'); + }); - $this->route = new Route(['GET'], '/test', function () { - return 'test'; - }); - } - - public function test_it_can_set_and_check_wordpress_route_status(): void - { - $this->assertFalse($this->route->isWordPressRoute()); + it('can set and check WordPress route status', function (): void { + expect($this->route->isWordPressRoute())->toBeFalse(); $this->route->setIsWordPressRoute(true); - $this->assertTrue($this->route->isWordPressRoute()); - } + expect($this->route->isWordPressRoute())->toBeTrue(); + }); - public function test_it_can_set_and_get_condition(): void - { - $this->assertFalse($this->route->hasCondition()); - $this->assertEmpty($this->route->getCondition()); + it('can set and get condition', function (): void { + expect($this->route->hasCondition())->toBeFalse(); + expect($this->route->getCondition())->toBeEmpty(); $this->route->setCondition('is_single'); - $this->assertTrue($this->route->hasCondition()); - $this->assertEquals('is_single', $this->route->getCondition()); - } + expect($this->route->hasCondition())->toBeTrue(); + expect($this->route->getCondition())->toBe('is_single'); + }); - public function test_it_can_set_and_get_condition_parameters(): void - { - $this->assertEmpty($this->route->getConditionParameters()); + it('can set and get condition parameters', function (): void { + expect($this->route->getConditionParameters())->toBeEmpty(); $parameters = ['product', 123]; $this->route->setConditionParameters($parameters); - $this->assertEquals($parameters, $this->route->getConditionParameters()); - } + expect($this->route->getConditionParameters())->toBe($parameters); + }); - public function test_it_matches_wordpress_condition_when_function_exists(): void - { - // Mock a WordPress function + it('matches WordPress condition when function exists', function (): void { if (! function_exists('test_wp_function')) { eval('function test_wp_function($param = null) { return $param === "test"; }'); } @@ -64,12 +48,10 @@ public function test_it_matches_wordpress_condition_when_function_exists(): void $request = Request::create('/test'); - $this->assertTrue($this->route->matches($request)); - } + expect($this->route->matches($request))->toBeTrue(); + }); - public function test_it_does_not_match_when_wordpress_function_returns_false(): void - { - // Mock a WordPress function that returns false + it('does not match when WordPress function returns false', function (): void { if (! function_exists('test_wp_function_false')) { eval('function test_wp_function_false() { return false; }'); } @@ -79,39 +61,33 @@ public function test_it_does_not_match_when_wordpress_function_returns_false(): $request = Request::create('/test'); - $this->assertFalse($this->route->matches($request)); - } + expect($this->route->matches($request))->toBeFalse(); + }); - public function test_it_falls_back_to_laravel_matching_for_non_wordpress_routes(): void - { + it('falls back to Laravel matching for non-WordPress routes', function (): void { $request = Request::create('/test', 'GET'); + expect($this->route->matches($request))->toBeTrue(); - // This should use Laravel's default matching behavior - $this->assertTrue($this->route->matches($request)); - - // Different URI should not match $wrongRequest = Request::create('/different', 'GET'); - $this->assertFalse($this->route->matches($wrongRequest)); - } + expect($this->route->matches($wrongRequest))->toBeFalse(); + }); - public function test_it_returns_false_when_wordpress_function_does_not_exist(): void - { + it('returns false when WordPress function does not exist', function (): void { $this->route->setIsWordPressRoute(true); $this->route->setCondition('non_existent_function'); $request = Request::create('/test'); - $this->assertFalse($this->route->matches($request)); - } + expect($this->route->matches($request))->toBeFalse(); + }); - public function test_chaining_methods_return_route_instance(): void - { + it('chaining methods return route instance', function (): void { $result = $this->route ->setIsWordPressRoute(true) ->setCondition('is_single') ->setConditionParameters(['test']); - $this->assertInstanceOf(Route::class, $result); - $this->assertSame($this->route, $result); - } -} + expect($result)->toBeInstanceOf(Route::class); + expect($result)->toBe($this->route); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Middleware/WordPressBindingsTest.php b/tests/Unit/Route/Infrastructure/Middleware/WordPressBindingsTest.php index 08661857..6d573d96 100644 --- a/tests/Unit/Route/Infrastructure/Middleware/WordPressBindingsTest.php +++ b/tests/Unit/Route/Infrastructure/Middleware/WordPressBindingsTest.php @@ -2,103 +2,66 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Middleware; - use Illuminate\Http\Request; -use PHPUnit\Framework\TestCase; use Pollora\Route\Domain\Models\Route; use Pollora\Route\Infrastructure\Middleware\WordPressBindings; use Pollora\Route\Infrastructure\Services\ExtendedRouter; -class WordPressBindingsTest extends TestCase -{ - private WordPressBindings $middleware; - - private ExtendedRouter $router; - - protected function setUp(): void - { - parent::setUp(); - - $this->router = $this->createMock(ExtendedRouter::class); +describe('WordPressBindings middleware', function (): void { + beforeEach(function (): void { + $this->router = Mockery::mock(ExtendedRouter::class); $this->middleware = new WordPressBindings($this->router); - } + }); - public function test_it_adds_bindings_for_wordpress_routes(): void - { - $route = $this->createMock(Route::class); - $route->method('hasCondition')->willReturn(true); + it('adds bindings for WordPress routes', function (): void { + $route = Mockery::mock(Route::class); + $route->shouldReceive('hasCondition')->andReturn(true); $request = Request::create('/test'); $request->setRouteResolver(fn () => $route); - $this->router->expects($this->once()) - ->method('addWordPressBindings') - ->with($route) - ->willReturn($route); - - $next = function ($req) { - return 'response'; - }; + $this->router->shouldReceive('addWordPressBindings')->once()->with($route)->andReturn($route); - $result = $this->middleware->handle($request, $next); + $result = $this->middleware->handle($request, fn ($req): string => 'response'); - $this->assertEquals('response', $result); - } + expect($result)->toBe('response'); + }); - public function test_it_skips_bindings_for_non_wordpress_routes(): void - { - $route = $this->createMock(Route::class); - $route->method('hasCondition')->willReturn(false); + it('skips bindings for non-WordPress routes', function (): void { + $route = Mockery::mock(Route::class); + $route->shouldReceive('hasCondition')->andReturn(false); $request = Request::create('/test'); $request->setRouteResolver(fn () => $route); - $this->router->expects($this->never()) - ->method('addWordPressBindings'); + $this->router->shouldNotReceive('addWordPressBindings'); - $next = function ($req) { - return 'response'; - }; + $result = $this->middleware->handle($request, fn ($req): string => 'response'); - $result = $this->middleware->handle($request, $next); + expect($result)->toBe('response'); + }); - $this->assertEquals('response', $result); - } - - public function test_it_handles_request_without_route(): void - { + it('handles request without route', function (): void { $request = Request::create('/test'); - $request->setRouteResolver(fn () => null); - - $this->router->expects($this->never()) - ->method('addWordPressBindings'); + $request->setRouteResolver(fn (): null => null); - $next = function ($req) { - return 'response'; - }; + $this->router->shouldNotReceive('addWordPressBindings'); - $result = $this->middleware->handle($request, $next); + $result = $this->middleware->handle($request, fn ($req): string => 'response'); - $this->assertEquals('response', $result); - } + expect($result)->toBe('response'); + }); - public function test_it_handles_route_without_condition_method(): void - { - $route = new \stdClass; // Route without hasCondition method + it('handles route without condition method', function (): void { + $route = new stdClass; $request = Request::create('/test'); - $request->setRouteResolver(fn () => $route); - - $this->router->expects($this->never()) - ->method('addWordPressBindings'); + $request->setRouteResolver(fn (): stdClass => $route); - $next = function ($req) { - return 'response'; - }; + $this->router->shouldNotReceive('addWordPressBindings'); - $result = $this->middleware->handle($request, $next); + $result = $this->middleware->handle($request, fn ($req): string => 'response'); - $this->assertEquals('response', $result); - } -} + expect($result)->toBe('response'); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Services/ExtendedRouterDependencyInjectionTest.php b/tests/Unit/Route/Infrastructure/Services/ExtendedRouterDependencyInjectionTest.php index 84132132..a8f5fb51 100644 --- a/tests/Unit/Route/Infrastructure/Services/ExtendedRouterDependencyInjectionTest.php +++ b/tests/Unit/Route/Infrastructure/Services/ExtendedRouterDependencyInjectionTest.php @@ -2,31 +2,17 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - use Illuminate\Container\Container; use Illuminate\Events\Dispatcher; -use PHPUnit\Framework\Attributes\CoversClass; use Pollora\Route\Infrastructure\Services\ExtendedRouter; use Pollora\Route\Infrastructure\Services\Resolvers\WordPressTypeResolver; use Pollora\Route\Infrastructure\Services\WordPressConditionManager; -use Tests\TestCase; - -#[CoversClass(ExtendedRouter::class)] -class ExtendedRouterDependencyInjectionTest extends TestCase -{ - private Container $container; - - private ExtendedRouter $router; - - protected function setUp(): void - { - parent::setUp(); +describe('ExtendedRouter dependency injection', function (): void { + beforeEach(function (): void { $this->container = new Container; $dispatcher = new Dispatcher($this->container); - // Register dependencies $conditionManager = new WordPressConditionManager($this->container); $typeResolver = new WordPressTypeResolver; @@ -34,32 +20,26 @@ protected function setUp(): void $dispatcher, $this->container, $conditionManager, - $typeResolver, - null // no logger for tests + $typeResolver ); - } + }); - public function test_wordpress_types_are_registered_in_container(): void - { + it('registers WordPress types in the container', function (): void { $expectedTypes = ['WP_Post', 'WP_Term', 'WP_User', 'WP_Query', 'WP']; foreach ($expectedTypes as $type) { - $this->assertTrue( - $this->container->bound($type), - "WordPress type {$type} should be bound in the container" - ); + expect($this->container->bound($type))->toBeTrue(sprintf('WordPress type %s should be bound in the container', $type)); } - } + }); - public function test_router_can_resolve_conditions(): void - { + it('can resolve conditions', function (): void { $conditions = $this->router->getConditions(); - $this->assertIsArray($conditions); - $this->assertArrayHasKey('home', $conditions); - $this->assertEquals('is_home', $conditions['home']); + expect($conditions)->toBeArray(); + expect($conditions)->toHaveKey('home'); + expect($conditions['home'])->toBe('is_home'); - $this->assertEquals('is_single', $this->router->resolveCondition('single')); - $this->assertEquals('unknown', $this->router->resolveCondition('unknown')); - } -} + expect($this->router->resolveCondition('single'))->toBe('is_single'); + expect($this->router->resolveCondition('unknown'))->toBe('unknown'); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Services/ExtendedRouterTest.php b/tests/Unit/Route/Infrastructure/Services/ExtendedRouterTest.php index eaf8db54..acba5ff6 100644 --- a/tests/Unit/Route/Infrastructure/Services/ExtendedRouterTest.php +++ b/tests/Unit/Route/Infrastructure/Services/ExtendedRouterTest.php @@ -2,123 +2,84 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - use Illuminate\Config\Repository; use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; -use PHPUnit\Framework\TestCase; use Pollora\Route\Domain\Models\Route; use Pollora\Route\Infrastructure\Services\ExtendedRouter; use Pollora\Route\Infrastructure\Services\Resolvers\WordPressTypeResolver; use Pollora\Route\Infrastructure\Services\WordPressConditionManager; -class ExtendedRouterTest extends TestCase -{ - private ExtendedRouter $router; - - private Container $container; - - protected function setUp(): void - { - parent::setUp(); - +describe('ExtendedRouter', function (): void { + beforeEach(function (): void { $this->container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); - - // Mock config - $config = $this->createMock(Repository::class); - $config->method('get') - ->willReturnCallback(function ($key, $default = null) { - if ($key === 'wordpress.conditions') { - return [ - 'is_single' => 'single', - 'is_page' => 'page', - 'is_category' => 'category', - ]; - } - if ($key === 'wordpress.plugin_conditions') { - return []; - } - - return $default; - }); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); + + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->andReturnUsing(function ($key, $default = null) { + if ($key === 'wordpress.conditions') { + return [ + 'is_single' => 'single', + 'is_page' => 'page', + 'is_category' => 'category', + ]; + } + + if ($key === 'wordpress.plugin_conditions') { + return []; + } + + return $default; + }); $this->container->instance('config', $config); - // Create dependencies with the mocked config $conditionManager = new WordPressConditionManager($this->container); $typeResolver = new WordPressTypeResolver; - $this->router = new ExtendedRouter( - $dispatcher, - $this->container, - $conditionManager, - $typeResolver, - null // no logger for tests - ); - } - - public function test_it_creates_route_objects_of_correct_type(): void - { - $route = $this->router->get('/test', function () { - return 'test'; - }); + $this->router = new ExtendedRouter($dispatcher, $this->container, $conditionManager, $typeResolver); + }); - $this->assertInstanceOf(Route::class, $route); - } + it('creates route objects of correct type', function (): void { + $route = $this->router->get('/test', fn (): string => 'test'); - public function test_it_loads_wordpress_conditions_from_config(): void - { - $conditions = $this->router->getConditions(); - - $this->assertArrayHasKey('single', $conditions); - $this->assertEquals('is_single', $conditions['single']); - $this->assertArrayHasKey('page', $conditions); - $this->assertEquals('is_page', $conditions['page']); - $this->assertArrayHasKey('category', $conditions); - $this->assertEquals('is_category', $conditions['category']); - } + expect($route)->toBeInstanceOf(Route::class); + }); - public function test_it_resolves_condition_aliases(): void - { - $this->assertEquals('is_single', $this->router->resolveCondition('single')); - $this->assertEquals('is_page', $this->router->resolveCondition('page')); + it('loads WordPress conditions from config', function (): void { + $conditions = $this->router->getConditions(); - // Non-aliased conditions should return as-is - $this->assertEquals('is_custom', $this->router->resolveCondition('is_custom')); - } + expect($conditions)->toHaveKey('single'); + expect($conditions['single'])->toBe('is_single'); + expect($conditions)->toHaveKey('page'); + expect($conditions['page'])->toBe('is_page'); + expect($conditions)->toHaveKey('category'); + expect($conditions['category'])->toBe('is_category'); + }); - public function test_it_adds_wordpress_bindings_to_route(): void - { - // Test that the addWordPressBindings method runs without error - $closure = function (\WP_Post $post, \WP_Query $wp_query) { - return [$post, $wp_query]; - }; + it('resolves condition aliases', function (): void { + expect($this->router->resolveCondition('single'))->toBe('is_single'); + expect($this->router->resolveCondition('page'))->toBe('is_page'); + expect($this->router->resolveCondition('is_custom'))->toBe('is_custom'); + }); - $route = new Route(['GET'], '/test', $closure); + it('adds WordPress bindings to route', function (): void { + $route = new Route(['GET'], '/test', fn (WP_Post $post, WP_Query $wp_query): array => [$post, $wp_query]); $result = $this->router->addWordPressBindings($route); + expect($result)->toBe($route); - // The method should return the same route instance - $this->assertSame($route, $result); + $nonWpRoute = new Route(['GET'], '/other', fn (string $name, int $id): array => [$name, $id]); - // Test with non-WordPress types (should not cause errors) - $nonWpClosure = function (string $name, int $id) { - return [$name, $id]; - }; - - $nonWpRoute = new Route(['GET'], '/other', $nonWpClosure); $nonWpResult = $this->router->addWordPressBindings($nonWpRoute); + expect($nonWpResult)->toBe($nonWpRoute); + }); - $this->assertSame($nonWpRoute, $nonWpResult); - } - - public function test_it_handles_missing_config_gracefully(): void - { - // Create router without config + it('handles missing config gracefully', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); $conditionManager = new WordPressConditionManager($container); $typeResolver = new WordPressTypeResolver; @@ -126,9 +87,8 @@ public function test_it_handles_missing_config_gracefully(): void $router = new ExtendedRouter($dispatcher, $container, $conditionManager, $typeResolver); $conditions = $router->getConditions(); - $this->assertIsArray($conditions); - // Should have default conditions even without config - $this->assertArrayHasKey('home', $conditions); - $this->assertEquals('is_home', $conditions['home']); - } -} + expect($conditions)->toBeArray(); + expect($conditions)->toHaveKey('home'); + expect($conditions['home'])->toBe('is_home'); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Services/LazyConfigLoadingTest.php b/tests/Unit/Route/Infrastructure/Services/LazyConfigLoadingTest.php index 9c55b8fc..b29f0a5f 100644 --- a/tests/Unit/Route/Infrastructure/Services/LazyConfigLoadingTest.php +++ b/tests/Unit/Route/Infrastructure/Services/LazyConfigLoadingTest.php @@ -2,169 +2,140 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - use Illuminate\Config\Repository; use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; -use PHPUnit\Framework\TestCase; use Pollora\Route\Infrastructure\Services\ExtendedRouter; -/** - * Tests for lazy configuration loading in ExtendedRouter. - * - * This test verifies that the router can handle situations where - * the configuration is not available during construction but becomes - * available later during the application lifecycle. - */ -class LazyConfigLoadingTest extends TestCase -{ - public function test_router_works_without_config_during_construction(): void - { - // Create a container without config service bound +describe('LazyConfigLoading', function (): void { + it('router works without config during construction', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); - // This should not throw an exception even though config is not available $router = new ExtendedRouter($dispatcher, $container); - // Router should still have default conditions $conditions = $router->getConditions(); - $this->assertArrayHasKey('single', $conditions); - $this->assertEquals('is_single', $conditions['single']); - } + expect($conditions)->toHaveKey('single'); + expect($conditions['single'])->toBe('is_single'); + }); - public function test_router_loads_config_when_available_later(): void - { - // Create a container without config initially + it('router loads config when available later', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); $router = new ExtendedRouter($dispatcher, $container); - // Now bind the config service (simulating Laravel config being loaded later) - $config = $this->createMock(Repository::class); - $config->method('get') - ->willReturnCallback(function ($key, $default = null) { - if ($key === 'wordpress.conditions') { - return [ - 'is_custom_condition' => 'custom', - 'is_special_condition' => 'special', - ]; - } - if ($key === 'wordpress.plugin_conditions') { - return []; - } - - return $default; - }); + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->andReturnUsing(function ($key, $default = null) { + if ($key === 'wordpress.conditions') { + return [ + 'is_custom_condition' => 'custom', + 'is_special_condition' => 'special', + ]; + } + + if ($key === 'wordpress.plugin_conditions') { + return []; + } + + return $default; + }); $container->instance('config', $config); - // Now when we call resolveCondition, it should load the config $result = $router->resolveCondition('custom'); - $this->assertEquals('is_custom_condition', $result); + expect($result)->toBe('is_custom_condition'); - // And it should have merged with defaults $conditions = $router->getConditions(); - $this->assertArrayHasKey('single', $conditions); // Default condition - $this->assertArrayHasKey('custom', $conditions); // Config condition - $this->assertEquals('is_single', $conditions['single']); - $this->assertEquals('is_custom_condition', $conditions['custom']); - } - - public function test_config_is_only_loaded_once(): void - { + expect($conditions)->toHaveKey('single'); + expect($conditions)->toHaveKey('custom'); + expect($conditions['single'])->toBe('is_single'); + expect($conditions['custom'])->toBe('is_custom_condition'); + }); + + it('config is only loaded once', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); $router = new ExtendedRouter($dispatcher, $container); - // Mock config - expects to be called exactly twice (once for conditions, once for plugin_conditions) - $config = $this->createMock(Repository::class); - $config->expects($this->exactly(2)) - ->method('get') - ->willReturnCallback(function ($key, $default = null) { - if ($key === 'wordpress.conditions') { - return ['is_test' => 'test']; - } - if ($key === 'wordpress.plugin_conditions') { - return []; - } - - return $default; - }); + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->twice()->andReturnUsing(function ($key, $default = null) { + if ($key === 'wordpress.conditions') { + return ['is_test' => 'test']; + } + + if ($key === 'wordpress.plugin_conditions') { + return []; + } + + return $default; + }); $container->instance('config', $config); - // Call multiple times - config should only be loaded once $router->resolveCondition('test'); $router->resolveCondition('test'); $router->getConditions(); $router->resolveCondition('another'); - } + }); - public function test_router_handles_config_exceptions_gracefully(): void - { + it('handles config exceptions gracefully', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); $router = new ExtendedRouter($dispatcher, $container); - // Mock config that throws an exception - $config = $this->createMock(Repository::class); - $config->method('get') - ->willThrowException(new \Exception('Config not ready')); + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->andThrow(new Exception('Config not ready')); $container->instance('config', $config); - // Router should handle the exception gracefully and still work $result = $router->resolveCondition('single'); - $this->assertEquals('is_single', $result); + expect($result)->toBe('is_single'); $conditions = $router->getConditions(); - $this->assertArrayHasKey('single', $conditions); - } + expect($conditions)->toHaveKey('single'); + }); - public function test_config_merges_with_defaults_correctly(): void - { + it('config merges with defaults correctly', function (): void { $container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); $router = new ExtendedRouter($dispatcher, $container); - // Mock config with limited conditions to test merge behavior - $config = $this->createMock(Repository::class); - $config->method('get') - ->willReturnCallback(function ($key, $default = null) { - if ($key === 'wordpress.conditions') { - return [ - 'is_front_page' => 'front', - 'is_custom_condition' => 'custom', - ]; - } - if ($key === 'wordpress.plugin_conditions') { - return []; - } - - return $default; - }); + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->andReturnUsing(function ($key, $default = null) { + if ($key === 'wordpress.conditions') { + return [ + 'is_front_page' => 'front', + 'is_custom_condition' => 'custom', + ]; + } - $container->instance('config', $config); + if ($key === 'wordpress.plugin_conditions') { + return []; + } - // Test that config conditions work - $this->assertEquals('is_front_page', $router->resolveCondition('front')); - $this->assertEquals('is_custom_condition', $router->resolveCondition('custom')); + return $default; + }); + + $container->instance('config', $config); - // Test that default conditions are preserved - $this->assertEquals('is_single', $router->resolveCondition('single')); - $this->assertEquals('is_date', $router->resolveCondition('date')); + expect($router->resolveCondition('front'))->toBe('is_front_page'); + expect($router->resolveCondition('custom'))->toBe('is_custom_condition'); + expect($router->resolveCondition('single'))->toBe('is_single'); + expect($router->resolveCondition('date'))->toBe('is_date'); - // Test that all conditions are available $conditions = $router->getConditions(); - $this->assertArrayHasKey('front', $conditions); // From config - $this->assertArrayHasKey('custom', $conditions); // From config - $this->assertArrayHasKey('single', $conditions); // From defaults - $this->assertArrayHasKey('date', $conditions); // From defaults - } -} + expect($conditions)->toHaveKey('front'); + expect($conditions)->toHaveKey('custom'); + expect($conditions)->toHaveKey('single'); + expect($conditions)->toHaveKey('date'); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Services/WordPressConditionManagerTest.php b/tests/Unit/Route/Infrastructure/Services/WordPressConditionManagerTest.php index 42ba124e..9c0c8040 100644 --- a/tests/Unit/Route/Infrastructure/Services/WordPressConditionManagerTest.php +++ b/tests/Unit/Route/Infrastructure/Services/WordPressConditionManagerTest.php @@ -2,57 +2,43 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - use Illuminate\Container\Container; -use PHPUnit\Framework\Attributes\CoversClass; use Pollora\Route\Infrastructure\Services\WordPressConditionManager; -use Tests\TestCase; - -#[CoversClass(WordPressConditionManager::class)] -class WordPressConditionManagerTest extends TestCase -{ - private WordPressConditionManager $manager; - protected function setUp(): void - { - parent::setUp(); +describe('WordPressConditionManager', function (): void { + beforeEach(function (): void { $this->manager = new WordPressConditionManager(new Container); - } + }); - public function test_loads_default_conditions(): void - { + it('loads default conditions', function (): void { $conditions = $this->manager->getConditions(); - $this->assertIsArray($conditions); - $this->assertArrayHasKey('home', $conditions); - $this->assertEquals('is_home', $conditions['home']); - $this->assertArrayHasKey('single', $conditions); - $this->assertEquals('is_single', $conditions['single']); - $this->assertArrayHasKey('archive', $conditions); - $this->assertEquals('is_archive', $conditions['archive']); - } - - public function test_can_resolve_known_conditions(): void - { - $this->assertEquals('is_home', $this->manager->resolveCondition('home')); - $this->assertEquals('is_single', $this->manager->resolveCondition('single')); - $this->assertEquals('is_archive', $this->manager->resolveCondition('archive')); - } - - public function test_returns_original_condition_for_unknown_aliases(): void - { - $this->assertEquals('unknown_condition', $this->manager->resolveCondition('unknown_condition')); - } - - public function test_can_add_custom_conditions(): void - { + expect($conditions)->toBeArray(); + expect($conditions)->toHaveKey('home'); + expect($conditions['home'])->toBe('is_home'); + expect($conditions)->toHaveKey('single'); + expect($conditions['single'])->toBe('is_single'); + expect($conditions)->toHaveKey('archive'); + expect($conditions['archive'])->toBe('is_archive'); + }); + + it('can resolve known conditions', function (): void { + expect($this->manager->resolveCondition('home'))->toBe('is_home'); + expect($this->manager->resolveCondition('single'))->toBe('is_single'); + expect($this->manager->resolveCondition('archive'))->toBe('is_archive'); + }); + + it('returns original condition for unknown aliases', function (): void { + expect($this->manager->resolveCondition('unknown_condition'))->toBe('unknown_condition'); + }); + + it('can add custom conditions', function (): void { $this->manager->addCondition('custom', 'is_custom'); - $this->assertEquals('is_custom', $this->manager->resolveCondition('custom')); + expect($this->manager->resolveCondition('custom'))->toBe('is_custom'); $conditions = $this->manager->getConditions(); - $this->assertArrayHasKey('custom', $conditions); - $this->assertEquals('is_custom', $conditions['custom']); - } -} + expect($conditions)->toHaveKey('custom'); + expect($conditions['custom'])->toBe('is_custom'); + }); +}); diff --git a/tests/Unit/Route/Infrastructure/Services/WordPressRouteResolutionTest.php b/tests/Unit/Route/Infrastructure/Services/WordPressRouteResolutionTest.php index 2e51692d..45f96fb0 100644 --- a/tests/Unit/Route/Infrastructure/Services/WordPressRouteResolutionTest.php +++ b/tests/Unit/Route/Infrastructure/Services/WordPressRouteResolutionTest.php @@ -2,297 +2,190 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - use Illuminate\Config\Repository; use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Http\Request; -use PHPUnit\Framework\TestCase; use Pollora\Route\Domain\Models\Route; use Pollora\Route\Infrastructure\Services\ExtendedRouter; -/** - * Tests for WordPress route resolution based on conditions. - * - * This test suite replicates the route definitions from web.php - * and verifies that each WordPress condition correctly matches - * the appropriate route using the existing WordPress mock system. - */ -class WordPressRouteResolutionTest extends TestCase -{ - private ExtendedRouter $router; - - private Container $container; - - protected function setUp(): void - { - parent::setUp(); +require_once dirname(__DIR__, 3).'/helpers.php'; - // Initialize WordPress mocks system +describe('WordPressRouteResolution', function (): void { + beforeEach(function (): void { setupWordPressMocks(); $this->container = new Container; - $dispatcher = $this->createMock(Dispatcher::class); - - // Mock config for WordPress routing conditions - using the new format - $config = $this->createMock(Repository::class); - $config->method('get') - ->willReturnCallback(function ($key, $default = null) { - if ($key === 'wordpress.conditions') { - return [ - 'is_front_page' => 'front', - 'is_home' => 'home', - 'is_page' => 'page', - 'is_single' => 'single', - 'is_author' => 'author', - 'is_category' => 'archive', - 'is_page_template' => 'template', - 'is_404' => ['404', 'not_found'], - ]; - } - if ($key === 'wordpress.plugin_conditions') { - return []; - } - - return $default; - }); + $dispatcher = Mockery::mock(Dispatcher::class); + $dispatcher->shouldReceive('dispatch')->andReturn(null); + + $config = Mockery::mock(Repository::class); + $config->shouldReceive('get')->andReturnUsing(function ($key, $default = null) { + if ($key === 'wordpress.conditions') { + return [ + 'is_front_page' => 'front', + 'is_home' => 'home', + 'is_page' => 'page', + 'is_single' => 'single', + 'is_author' => 'author', + 'is_category' => 'archive', + 'is_page_template' => 'template', + 'is_404' => ['404', 'not_found'], + ]; + } + + if ($key === 'wordpress.plugin_conditions') { + return []; + } + + return $default; + }); $this->container->instance('config', $config); $this->router = new ExtendedRouter($dispatcher, $this->container); - } + }); - protected function tearDown(): void - { - // Reset WordPress mocks after each test + afterEach(function (): void { resetWordPressMocks(); - parent::tearDown(); - } - - /** - * Helper to create a WordPress route with mocked functions - */ - private function createWordPressRoute(string $condition, array $parameters = [], ?callable $action = null): Route - { - $action = $action ?: function () { - return 'matched'; - }; - $resolvedCondition = $this->router->resolveCondition($condition); - - $route = new Route(['GET'], $condition, $action); - $route->setIsWordPressRoute(true); - $route->setCondition($resolvedCondition); - $route->setConditionParameters($parameters); - - return $route; - } - - public function test_route_condition_aliases_are_resolved_correctly(): void - { - // Test that aliases are correctly resolved - replicating the exact conditions from web.php - $this->assertEquals('is_front_page', $this->router->resolveCondition('front')); - $this->assertEquals('is_home', $this->router->resolveCondition('home')); - $this->assertEquals('is_page', $this->router->resolveCondition('page')); - $this->assertEquals('is_single', $this->router->resolveCondition('single')); - $this->assertEquals('is_author', $this->router->resolveCondition('author')); - $this->assertEquals('is_category', $this->router->resolveCondition('archive')); - $this->assertEquals('is_page_template', $this->router->resolveCondition('template')); - $this->assertEquals('is_404', $this->router->resolveCondition('404')); - - // Test that direct WordPress function names pass through unchanged - $this->assertEquals('is_singular', $this->router->resolveCondition('is_singular')); - } - - public function test_wordpress_route_is_marked_correctly(): void - { - // Define a WordPress route - $wpRoute = $this->createWordPressRoute('front'); - - // Define a regular Laravel route for comparison - $laravelRoute = new Route(['GET'], '/test', function () { - return 'test'; - }); - - // Test that the WordPress route is correctly marked - $this->assertTrue($wpRoute->isWordPressRoute()); - $this->assertFalse($laravelRoute->isWordPressRoute()); - } - - public function test_wordpress_route_has_correct_condition(): void - { - $route = $this->createWordPressRoute('front'); - - $this->assertEquals('is_front_page', $route->getCondition()); - $this->assertTrue($route->hasCondition()); - } - - public function test_wordpress_route_with_parameters(): void - { - $route = $this->createWordPressRoute('is_singular', ['realisations']); - - $this->assertEquals('is_singular', $route->getCondition()); - $this->assertEquals(['realisations'], $route->getConditionParameters()); - } - - public function test_route_methods_and_properties(): void - { - $route = $this->createWordPressRoute('front'); - - // Test basic route properties (Laravel automatically adds HEAD for GET routes) - $this->assertEquals(['GET', 'HEAD'], $route->methods()); - $this->assertEquals('front', $route->uri()); - $this->assertTrue($route->isWordPressRoute()); - $this->assertTrue($route->hasCondition()); - $this->assertEquals('is_front_page', $route->getCondition()); - $this->assertEquals([], $route->getConditionParameters()); - } - - public function test_all_web_routes_have_correct_conditions(): void - { - // Test all route definitions from web.php + }); + + it('resolves condition aliases correctly', function (): void { + expect($this->router->resolveCondition('front'))->toBe('is_front_page'); + expect($this->router->resolveCondition('home'))->toBe('is_home'); + expect($this->router->resolveCondition('page'))->toBe('is_page'); + expect($this->router->resolveCondition('single'))->toBe('is_single'); + expect($this->router->resolveCondition('author'))->toBe('is_author'); + expect($this->router->resolveCondition('archive'))->toBe('is_category'); + expect($this->router->resolveCondition('template'))->toBe('is_page_template'); + expect($this->router->resolveCondition('404'))->toBe('is_404'); + expect($this->router->resolveCondition('is_singular'))->toBe('is_singular'); + }); + + it('marks WordPress route correctly', function (): void { + $wpRoute = createWpRoute($this->router, 'front'); + $laravelRoute = new Route(['GET'], '/test', fn (): string => 'test'); + + expect($wpRoute->isWordPressRoute())->toBeTrue(); + expect($laravelRoute->isWordPressRoute())->toBeFalse(); + }); + + it('has correct condition on WordPress route', function (): void { + $route = createWpRoute($this->router, 'front'); + + expect($route->getCondition())->toBe('is_front_page'); + expect($route->hasCondition())->toBeTrue(); + }); + + it('supports route with parameters', function (): void { + $route = createWpRoute($this->router, 'is_singular', ['realisations']); + + expect($route->getCondition())->toBe('is_singular'); + expect($route->getConditionParameters())->toBe(['realisations']); + }); + + it('has correct methods and properties', function (): void { + $route = createWpRoute($this->router, 'front'); + + expect($route->methods())->toBe(['GET', 'HEAD']); + expect($route->uri())->toBe('front'); + expect($route->isWordPressRoute())->toBeTrue(); + expect($route->hasCondition())->toBeTrue(); + expect($route->getCondition())->toBe('is_front_page'); + expect($route->getConditionParameters())->toBe([]); + }); + + it('resolves all web route conditions correctly', function (): void { $webRoutes = [ - 'front' => 'is_front_page', // Route::wp('front', ...) - 'is_singular' => 'is_singular', // Route::wp('is_singular', 'realisations', ...) - 'home' => 'is_home', // Route::wp('home', ...) - 'template' => 'is_page_template', // Route::wp('template', ...) - 'single' => 'is_single', // Route::wp('single', ...) - 'page' => 'is_page', // Route::wp('page', ...) - 'author' => 'is_author', // Route::wp('author', ...) - 'archive' => 'is_category', // Route::wp('archive', ...) - // Skip '404' route as it may not have a proper alias configured + 'front' => 'is_front_page', + 'is_singular' => 'is_singular', + 'home' => 'is_home', + 'template' => 'is_page_template', + 'single' => 'is_single', + 'page' => 'is_page', + 'author' => 'is_author', + 'archive' => 'is_category', ]; foreach ($webRoutes as $alias => $expectedCondition) { - $route = $this->createWordPressRoute($alias); - $this->assertEquals($expectedCondition, $route->getCondition(), - "Route alias '{$alias}' should resolve to '{$expectedCondition}'"); + $route = createWpRoute($this->router, $alias); + expect($route->getCondition())->toBe($expectedCondition, sprintf("Route alias '%s' should resolve to '%s'", $alias, $expectedCondition)); } - } - - public function test_route_with_condition_parameters_from_web_routes(): void - { - // Test the specific route from web.php: Route::wp('is_singular', 'realisations', ...) - $route = $this->createWordPressRoute('is_singular', ['realisations']); - - $this->assertEquals('is_singular', $route->getCondition()); - $this->assertEquals(['realisations'], $route->getConditionParameters()); - $this->assertTrue($route->isWordPressRoute()); - } - - public function test_404_route_condition(): void - { - // Test the 404 route - it now has an alias configured - $resolvedCondition = $this->router->resolveCondition('404'); - - // '404' should resolve to 'is_404' based on our configuration - $this->assertEquals('is_404', $resolvedCondition); - - // Create route with '404' condition - $route = $this->createWordPressRoute('404'); - $this->assertEquals('is_404', $route->getCondition()); - $this->assertTrue($route->isWordPressRoute()); - } - - public function test_route_matches_with_wordpress_condition_mocking(): void - { - // Test specific route matching using the WordPress mocking system - - // Test 1: front page route with is_front_page() = true - setWordPressFunction('is_front_page', fn () => true); - $frontRoute = $this->createWordPressRoute('front'); - $request = Request::create('/', 'GET'); - $this->assertTrue($frontRoute->matches($request)); + }); - // Test 2: front page route with is_front_page() = false - setWordPressFunction('is_front_page', fn () => false); - $this->assertFalse($frontRoute->matches($request)); + it('resolves 404 route condition', function (): void { + expect($this->router->resolveCondition('404'))->toBe('is_404'); - // Test 3: single post route with is_single() = true - setWordPressFunction('is_single', fn () => true); - $singleRoute = $this->createWordPressRoute('single'); - $singleRequest = Request::create('/blog/article', 'GET'); - $this->assertTrue($singleRoute->matches($singleRequest)); + $route = createWpRoute($this->router, '404'); + expect($route->getCondition())->toBe('is_404'); + expect($route->isWordPressRoute())->toBeTrue(); + }); - // Test 4: category route with is_category() = true - setWordPressFunction('is_category', fn () => true); - $categoryRoute = $this->createWordPressRoute('archive'); - $categoryRequest = Request::create('/category/news', 'GET'); - $this->assertTrue($categoryRoute->matches($categoryRequest)); - } - - public function test_route_with_parameters_using_wordpress_mocks(): void - { - // Test route with parameters - like Route::wp('is_singular', 'realisations', ...) - // Note: The existing mock system doesn't fully support parameterized WordPress functions - // so we'll test the route structure and basic condition matching - - $route = $this->createWordPressRoute('is_singular', ['realisations']); + it('matches routes with WordPress condition mocking', function (): void { + setWordPressFunction('is_front_page', fn (): true => true); + $frontRoute = createWpRoute($this->router, 'front'); + $request = Request::create('/', 'GET'); + expect($frontRoute->matches($request))->toBeTrue(); - // Test that the route correctly stores the parameters - $this->assertEquals(['realisations'], $route->getConditionParameters()); - $this->assertEquals('is_singular', $route->getCondition()); - $this->assertTrue($route->isWordPressRoute()); + setWordPressFunction('is_front_page', fn (): false => false); + expect($frontRoute->matches($request))->toBeFalse(); - // Mock is_singular to return true (simulating a match) - setWordPressFunction('is_singular', fn () => true); + setWordPressFunction('is_single', fn (): true => true); + $singleRoute = createWpRoute($this->router, 'single'); + expect($singleRoute->matches(Request::create('/blog/article', 'GET')))->toBeTrue(); - $request = Request::create('/realisations/campus-vert', 'GET'); + setWordPressFunction('is_category', fn (): true => true); + $categoryRoute = createWpRoute($this->router, 'archive'); + expect($categoryRoute->matches(Request::create('/category/news', 'GET')))->toBeTrue(); + }); - // The route should match because our mock returns true - $this->assertTrue($route->matches($request)); + it('matches route with parameters using WordPress mocks', function (): void { + $route = createWpRoute($this->router, 'is_singular', ['realisations']); - // Test with mock returning false - setWordPressFunction('is_singular', fn () => false); - $this->assertFalse($route->matches($request)); - } + setWordPressFunction('is_singular', fn (): true => true); + expect($route->matches(Request::create('/realisations/campus-vert', 'GET')))->toBeTrue(); - public function test_multiple_conditions_simulation(): void - { - // Simulate different WordPress context scenarios from web.php + setWordPressFunction('is_singular', fn (): false => false); + expect($route->matches(Request::create('/realisations/campus-vert', 'GET')))->toBeFalse(); + }); - // Scenario 1: Homepage + it('simulates multiple conditions correctly', function (): void { setWordPressConditions([ - 'is_front_page' => true, - 'is_home' => false, - 'is_page' => false, - 'is_single' => false, - 'is_category' => false, - 'is_404' => false, + 'is_front_page' => true, 'is_home' => false, 'is_page' => false, + 'is_single' => false, 'is_category' => false, 'is_404' => false, ]); - $frontRoute = $this->createWordPressRoute('front'); - $homeRoute = $this->createWordPressRoute('home'); - + $frontRoute = createWpRoute($this->router, 'front'); + $homeRoute = createWpRoute($this->router, 'home'); $request = Request::create('/', 'GET'); - $this->assertTrue($frontRoute->matches($request)); - $this->assertFalse($homeRoute->matches($request)); - // Scenario 2: Blog archive + expect($frontRoute->matches($request))->toBeTrue(); + expect($homeRoute->matches($request))->toBeFalse(); + setWordPressConditions([ - 'is_front_page' => false, - 'is_home' => true, - 'is_page' => false, - 'is_single' => false, - 'is_category' => false, - 'is_404' => false, + 'is_front_page' => false, 'is_home' => true, 'is_page' => false, + 'is_single' => false, 'is_category' => false, 'is_404' => false, ]); - $blogRequest = Request::create('/blog', 'GET'); - $this->assertFalse($frontRoute->matches($blogRequest)); - $this->assertTrue($homeRoute->matches($blogRequest)); + expect($frontRoute->matches(Request::create('/blog', 'GET')))->toBeFalse(); + expect($homeRoute->matches(Request::create('/blog', 'GET')))->toBeTrue(); - // Scenario 3: Category page setWordPressConditions([ - 'is_front_page' => false, - 'is_home' => false, - 'is_page' => false, - 'is_single' => false, - 'is_category' => true, - 'is_404' => false, + 'is_front_page' => false, 'is_home' => false, 'is_page' => false, + 'is_single' => false, 'is_category' => true, 'is_404' => false, ]); - $categoryRoute = $this->createWordPressRoute('archive'); - $categoryRequest = Request::create('/blog/category/actus', 'GET'); - $this->assertTrue($categoryRoute->matches($categoryRequest)); - } + $categoryRoute = createWpRoute($this->router, 'archive'); + expect($categoryRoute->matches(Request::create('/blog/category/actus', 'GET')))->toBeTrue(); + }); +}); + +function createWpRoute(ExtendedRouter $router, string $condition, array $parameters = []): Route +{ + $resolvedCondition = $router->resolveCondition($condition); + $route = new Route(['GET'], $condition, fn (): string => 'matched'); + $route->setIsWordPressRoute(true); + $route->setCondition($resolvedCondition); + $route->setConditionParameters($parameters); + + return $route; } diff --git a/tests/Unit/Route/Infrastructure/Services/WordPressTypeResolverTest.php b/tests/Unit/Route/Infrastructure/Services/WordPressTypeResolverTest.php index 7034b47d..28ffac3a 100644 --- a/tests/Unit/Route/Infrastructure/Services/WordPressTypeResolverTest.php +++ b/tests/Unit/Route/Infrastructure/Services/WordPressTypeResolverTest.php @@ -2,36 +2,21 @@ declare(strict_types=1); -namespace Tests\Unit\Route\Infrastructure\Services; - -use PHPUnit\Framework\Attributes\CoversClass; use Pollora\Route\Infrastructure\Services\Contracts\WordPressTypeResolverInterface; use Pollora\Route\Infrastructure\Services\Resolvers\WordPressTypeResolver; -use Tests\TestCase; - -#[CoversClass(WordPressTypeResolver::class)] -class WordPressTypeResolverTest extends TestCase -{ - private WordPressTypeResolver $resolver; - protected function setUp(): void - { - parent::setUp(); +describe('WordPressTypeResolver', function (): void { + beforeEach(function (): void { $this->resolver = new WordPressTypeResolver; - } + }); - public function test_returns_null_for_unsupported_types(): void - { + it('returns null for unsupported types', function (): void { $result = $this->resolver->resolve('UnsupportedType'); - $this->assertNull($result); - } - public function test_resolver_has_correct_interface(): void - { - // Test that the resolver implements the correct interface - $this->assertInstanceOf( - WordPressTypeResolverInterface::class, - $this->resolver - ); - } -} + expect($result)->toBeNull(); + }); + + it('implements the correct interface', function (): void { + expect($this->resolver)->toBeInstanceOf(WordPressTypeResolverInterface::class); + }); +}); diff --git a/tests/Unit/Route/RouteTest.php b/tests/Unit/Route/RouteTest.php index 353c1271..8ed3ac1f 100644 --- a/tests/Unit/Route/RouteTest.php +++ b/tests/Unit/Route/RouteTest.php @@ -9,7 +9,7 @@ /** * Setup function to create test environment for route tests */ -function setupRouteTest() +function setupRouteTest(): array { // Define simulated WordPress functions mockWordPressFunctionsForRoute(); @@ -30,20 +30,18 @@ function mockWordPressFunctionsForRoute(): void /** * Clean up after each test */ -afterEach(function () { +afterEach(function (): void { m::close(); }); /** * Test that the route is correctly marked as a WordPress route. */ -test('route can be marked as WordPress route', function () { +test('route can be marked as WordPress route', function (): void { setupRouteTest(); // Create a route - $route = new Route(['GET'], 'test', function () { - return 'test'; - }); + $route = new Route(['GET'], 'test', fn (): string => 'test'); // By default, it's not a WordPress route expect($route->isWordPressRoute())->toBeFalse(); @@ -58,13 +56,11 @@ function mockWordPressFunctionsForRoute(): void /** * Test that the WordPress condition is correctly defined and retrieved. */ -test('route can define and retrieve WordPress condition', function () { +test('route can define and retrieve WordPress condition', function (): void { setupRouteTest(); // Create a WordPress route - $route = new Route(['GET'], 'is_page', function () { - return 'page'; - }); + $route = new Route(['GET'], 'is_page', fn (): string => 'page'); $route->setIsWordPressRoute(true); // Define the condition @@ -78,13 +74,11 @@ function mockWordPressFunctionsForRoute(): void /** * Test that condition parameters are correctly defined and retrieved. */ -test('route can define and retrieve condition parameters', function () { +test('route can define and retrieve condition parameters', function (): void { setupRouteTest(); // Create a WordPress route - $route = new Route(['GET'], 'is_page', function () { - return 'page'; - }); + $route = new Route(['GET'], 'is_page', fn (): string => 'page'); $route->setIsWordPressRoute(true); // Define condition parameters @@ -98,13 +92,11 @@ function mockWordPressFunctionsForRoute(): void /** * Test that the matches method works correctly for WordPress routes. */ -test('route matches correctly for WordPress conditions', function () { +test('route matches correctly for WordPress conditions', function (): void { setupRouteTest(); // Create a WordPress route - $route = new Route(['GET'], 'test_condition', function () { - return 'test'; - }); + $route = new Route(['GET'], 'test_condition', fn (): string => 'test'); $route->setIsWordPressRoute(true); $route->setCondition('test_condition'); @@ -126,13 +118,11 @@ function mockWordPressFunctionsForRoute(): void /** * Test that route can check if it has WordPress conditions. */ -test('route can check if it has WordPress conditions', function () { +test('route can check if it has WordPress conditions', function (): void { setupRouteTest(); // Create a route without conditions - $route = new Route(['GET'], 'test', function () { - return 'test'; - }); + $route = new Route(['GET'], 'test', fn (): string => 'test'); // Should not have condition initially expect($route->hasCondition())->toBeFalse(); @@ -147,13 +137,11 @@ function mockWordPressFunctionsForRoute(): void /** * Test that the route correctly evaluates WordPress conditions during matching. */ -test('route evaluates WordPress conditions correctly', function () { +test('route evaluates WordPress conditions correctly', function (): void { setupRouteTest(); // Create a WordPress route with a condition that exists - $route = new Route(['GET'], 'is_page', function () { - return 'page'; - }); + $route = new Route(['GET'], 'is_page', fn (): string => 'page'); $route->setIsWordPressRoute(true); $route->setCondition('is_page'); diff --git a/tests/Unit/Route/RouterTest.php b/tests/Unit/Route/RouterTest.php index 70312a7e..8aab3f42 100644 --- a/tests/Unit/Route/RouterTest.php +++ b/tests/Unit/Route/RouterTest.php @@ -11,7 +11,7 @@ /** * Setup function to create mocks and the router instance for all tests */ -function setupRouterTest() +function setupRouterTest(): array { // Initialize WordPress functions from helpers.php setupWordPressMocks(); @@ -66,8 +66,8 @@ function mockWordPressClasses(): void /** * Clean up after each test */ -afterEach(function () { - Container::setInstance(null); +afterEach(function (): void { + Container::setInstance(); WP::$wpFunctions = null; m::close(); }); @@ -75,14 +75,12 @@ function mockWordPressClasses(): void /** * Test that the Router correctly creates new Route instances. */ -test('router creates new route instances', function () { +test('router creates new route instances', function (): void { $setup = setupRouterTest(); $router = $setup['router']; // Create a route using the router - $route = $router->get('/test', function () { - return 'test'; - }); + $route = $router->get('/test', fn (): string => 'test'); // Verify that the route is an instance of our extended Route class expect($route)->toBeInstanceOf(Route::class); @@ -93,7 +91,7 @@ function mockWordPressClasses(): void /** * Test that the Router can handle WordPress conditions. */ -test('router manages WordPress conditions', function () { +test('router manages WordPress conditions', function (): void { $setup = setupRouterTest(); $router = $setup['router']; @@ -111,14 +109,12 @@ function mockWordPressClasses(): void /** * Test that the Router can add WordPress bindings to routes. */ -test('router adds WordPress bindings to routes', function () { +test('router adds WordPress bindings to routes', function (): void { $setup = setupRouterTest(); $router = $setup['router']; // Create a route with a closure that has WordPress dependencies - $route = new Route(['GET'], 'test', function (WP_Post $post) { - return 'test'; - }); + $route = new Route(['GET'], 'test', fn (WP_Post $post): string => 'test'); // Add WordPress bindings $enhancedRoute = $router->addWordPressBindings($route); @@ -131,14 +127,12 @@ function mockWordPressClasses(): void /** * Test that the Router creates routes with proper configuration. */ -test('router creates WordPress-compatible routes', function () { +test('router creates WordPress-compatible routes', function (): void { $setup = setupRouterTest(); $router = $setup['router']; // Create a WordPress route - $route = $router->get('/page', function () { - return 'page'; - }); + $route = $router->get('/page', fn (): string => 'page'); // Set it as a WordPress route with condition $route->setIsWordPressRoute(true); @@ -153,14 +147,12 @@ function mockWordPressClasses(): void /** * Test that the Router can create new Route instances with newRoute method. */ -test('router newRoute method creates proper Route instances', function () { +test('router newRoute method creates proper Route instances', function (): void { $setup = setupRouterTest(); $router = $setup['router']; // Use the newRoute method - $route = $router->newRoute(['GET'], 'test', function () { - return 'test'; - }); + $route = $router->newRoute(['GET'], 'test', fn (): string => 'test'); // Verify that it returns our extended Route class expect($route)->toBeInstanceOf(Route::class); diff --git a/tests/Unit/Support/UriTest.php b/tests/Unit/Support/UriTest.php index 794cbaae..de2d6f00 100644 --- a/tests/Unit/Support/UriTest.php +++ b/tests/Unit/Support/UriTest.php @@ -4,14 +4,14 @@ use Pollora\Support\Uri; -describe('Uri helper', function () { - it('removes trailing slash from valid urls', function () { +describe('Uri helper', function (): void { + it('removes trailing slash from valid urls', function (): void { $uri = new Uri; expect($uri->removeTrailingSlash('https://example.com/foo/'))->toBe('https://example.com/foo'); expect($uri->removeTrailingSlash('/foo/bar/'))->toBe('/foo/bar'); }); - it('handles malformed urls gracefully', function () { + it('handles malformed urls gracefully', function (): void { $uri = new Uri; $malformed = ':///foo/'; expect($uri->removeTrailingSlash($malformed))->toBe(':///foo'); diff --git a/tests/Unit/Taxonomy/Commands/TaxonomyMakeCommandTest.php b/tests/Unit/Taxonomy/Commands/TaxonomyMakeCommandTest.php index fd26c286..7768df8c 100644 --- a/tests/Unit/Taxonomy/Commands/TaxonomyMakeCommandTest.php +++ b/tests/Unit/Taxonomy/Commands/TaxonomyMakeCommandTest.php @@ -6,7 +6,7 @@ use Mockery as m; use Pollora\Taxonomy\UI\Console\TaxonomyMakeCommand; -beforeEach(function () { +beforeEach(function (): void { // Create a mock filesystem $this->files = m::mock(Filesystem::class); @@ -17,11 +17,11 @@ } }); -afterEach(function () { +afterEach(function (): void { m::close(); }); -test('TaxonomyMakeCommand generates correct slug from class name', function () { +test('TaxonomyMakeCommand generates correct slug from class name', function (): void { // Skip if command doesn't exist yet if (! class_exists(TaxonomyMakeCommand::class)) { $this->markTestSkipped('TaxonomyMakeCommand class does not exist yet'); @@ -29,14 +29,13 @@ // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(TaxonomyMakeCommand::class, 'getSlugFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'ProductCategory'); expect($result)->toBe('product-category'); }); -test('TaxonomyMakeCommand generates correct singular name from class name', function () { +test('TaxonomyMakeCommand generates correct singular name from class name', function (): void { // Skip if command doesn't exist yet if (! class_exists(TaxonomyMakeCommand::class)) { $this->markTestSkipped('TaxonomyMakeCommand class does not exist yet'); @@ -44,14 +43,13 @@ // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(TaxonomyMakeCommand::class, 'getNameFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'ProductCategory'); expect($result)->toBe('Product category'); }); -test('TaxonomyMakeCommand generates correct plural name from class name', function () { +test('TaxonomyMakeCommand generates correct plural name from class name', function (): void { // Skip if command doesn't exist yet if (! class_exists(TaxonomyMakeCommand::class)) { $this->markTestSkipped('TaxonomyMakeCommand class does not exist yet'); @@ -59,7 +57,6 @@ // Test the protected method via reflection $reflectionMethod = new ReflectionMethod(TaxonomyMakeCommand::class, 'getPluralNameFromClassName'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command, 'Category'); @@ -70,7 +67,7 @@ expect($result)->toBe('Tags'); }); -test('TaxonomyMakeCommand generates default object type for taxonomy', function () { +test('TaxonomyMakeCommand generates default object type for taxonomy', function (): void { // Skip if command doesn't exist yet if (! class_exists(TaxonomyMakeCommand::class)) { $this->markTestSkipped('TaxonomyMakeCommand class does not exist yet'); @@ -79,7 +76,6 @@ // Test object type generation if method exists if (method_exists(TaxonomyMakeCommand::class, 'getDefaultObjectType')) { $reflectionMethod = new ReflectionMethod(TaxonomyMakeCommand::class, 'getDefaultObjectType'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invoke($this->command); @@ -90,7 +86,7 @@ } }); -test('TaxonomyMakeCommand handles hierarchical flag correctly', function () { +test('TaxonomyMakeCommand handles hierarchical flag correctly', function (): void { // Skip if command doesn't exist yet if (! class_exists(TaxonomyMakeCommand::class)) { $this->markTestSkipped('TaxonomyMakeCommand class does not exist yet'); @@ -99,7 +95,6 @@ // Test hierarchical option handling if method exists if (method_exists(TaxonomyMakeCommand::class, 'shouldBeHierarchical')) { $reflectionMethod = new ReflectionMethod(TaxonomyMakeCommand::class, 'shouldBeHierarchical'); - $reflectionMethod->setAccessible(true); // Test with different class names that might suggest hierarchy $result = $reflectionMethod->invoke($this->command, 'Category'); @@ -114,7 +109,7 @@ }); // Placeholder test for when the command is fully implemented -test('TaxonomyMakeCommand can be instantiated', function () { +test('TaxonomyMakeCommand can be instantiated', function (): void { if (class_exists(TaxonomyMakeCommand::class)) { expect($this->command)->toBeInstanceOf(TaxonomyMakeCommand::class); } else { diff --git a/tests/Unit/Taxonomy/TaxonomyAttributeServiceProviderTest.php b/tests/Unit/Taxonomy/TaxonomyAttributeServiceProviderTest.php index b9b51a5d..7234ded7 100644 --- a/tests/Unit/Taxonomy/TaxonomyAttributeServiceProviderTest.php +++ b/tests/Unit/Taxonomy/TaxonomyAttributeServiceProviderTest.php @@ -9,7 +9,7 @@ // Define app_path function if it doesn't exist in the test environment if (! function_exists('app_path')) { - function app_path($path = '') + function app_path(string $path = ''): string { return '/path/to/app/'.$path; } @@ -17,7 +17,7 @@ function app_path($path = '') // Define is_dir function if needed for testing if (! function_exists('is_dir_mock')) { - function is_dir_mock($path) + function is_dir_mock($path): bool { return true; // Always return true for testing } @@ -25,13 +25,13 @@ function is_dir_mock($path) // Define mkdir function if needed for testing if (! function_exists('mkdir_mock')) { - function mkdir_mock($path, $mode = 0777, $recursive = false) + function mkdir_mock($path, $mode = 0777, $recursive = false): bool { return true; // Always return true for testing } } -beforeAll(function () { +beforeAll(function (): void { $app = new Container; Facade::setFacadeApplication($app); @@ -46,13 +46,13 @@ function mkdir_mock($path, $mode = 0777, $recursive = false) Action::setFacadeApplication($app); }); -afterAll(function () { +afterAll(function (): void { m::close(); Facade::clearResolvedInstances(); Facade::setFacadeApplication(null); }); -it('can be instantiated', function () { +it('can be instantiated', function (): void { expect(true)->toBeTrue(); }); diff --git a/tests/Unit/Taxonomy/TaxonomyFactoryTest.php b/tests/Unit/Taxonomy/TaxonomyFactoryTest.php index d1ea297d..a4768068 100644 --- a/tests/Unit/Taxonomy/TaxonomyFactoryTest.php +++ b/tests/Unit/Taxonomy/TaxonomyFactoryTest.php @@ -6,12 +6,12 @@ use Pollora\Taxonomy\Infrastructure\Factories\TaxonomyFactory; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); $this->factory = new TaxonomyFactory; }); -test('make creates new Taxonomy instance with correct parameters', function () { +test('make creates new Taxonomy instance with correct parameters', function (): void { // Define test values $slug = 'test-taxonomy'; $objectType = ['post', 'page']; @@ -28,7 +28,7 @@ ->and($result->getObjectType())->toBe($objectType); }); -test('make handles null parameters correctly', function () { +test('make handles null parameters correctly', function (): void { // Define test values $slug = 'test-taxonomy'; $objectType = 'post'; @@ -43,7 +43,7 @@ ->and($result->getObjectType())->toBe($objectType); }); -test('make generates singular name from slug when not provided', function () { +test('make generates singular name from slug when not provided', function (): void { // Define test values $slug = 'product_category'; $objectType = 'product'; @@ -57,7 +57,7 @@ ->and($result->getSlug())->toBe($slug); }); -test('make generates plural name from singular when not provided', function () { +test('make generates plural name from singular when not provided', function (): void { // Define test values $slug = 'category'; $objectType = 'post'; @@ -72,7 +72,7 @@ ->and($result->getSlug())->toBe($slug); }); -test('make handles string object type', function () { +test('make handles string object type', function (): void { // Define test values $slug = 'tag'; $objectType = 'post'; @@ -87,7 +87,7 @@ ->and($result->getObjectType())->toBe($objectType); }); -test('make handles array object type', function () { +test('make handles array object type', function (): void { // Define test values $slug = 'category'; $objectType = ['post', 'page', 'product']; @@ -102,7 +102,7 @@ ->and($result->getObjectType())->toBe($objectType); }); -test('make applies additional arguments when provided', function () { +test('make applies additional arguments when provided', function (): void { // Define test values $slug = 'test-taxonomy'; $objectType = 'post'; diff --git a/tests/Unit/Taxonomy/TaxonomyServiceTest.php b/tests/Unit/Taxonomy/TaxonomyServiceTest.php index cdd3dbe9..33f667a3 100755 --- a/tests/Unit/Taxonomy/TaxonomyServiceTest.php +++ b/tests/Unit/Taxonomy/TaxonomyServiceTest.php @@ -9,14 +9,14 @@ use Pollora\Taxonomy\Domain\Contracts\TaxonomyFactoryInterface; use Pollora\Taxonomy\Domain\Contracts\TaxonomyRegistryInterface; -beforeEach(function () { +beforeEach(function (): void { $this->mockFactory = mock(TaxonomyFactoryInterface::class); $this->mockRegistry = mock(TaxonomyRegistryInterface::class); $this->mockTaxonomy = mock(Taxonomy::class); $this->taxonomyService = new TaxonomyService($this->mockFactory, $this->mockRegistry); }); -test('register calls make on factory', function () { +test('register calls make on factory', function (): void { // Define test values $slug = 'test-taxonomy'; $objectType = 'post'; @@ -40,7 +40,7 @@ expect($result)->toBe($this->mockTaxonomy); }); -test('exists calls exists on registry', function () { +test('exists calls exists on registry', function (): void { // Define test values $slug = 'test-taxonomy'; @@ -58,7 +58,7 @@ expect($result)->toBeTrue(); }); -test('getRegistered calls getAll on registry', function () { +test('getRegistered calls getAll on registry', function (): void { // Define test values $registeredTaxonomies = ['category', 'tag', 'test-taxonomy']; diff --git a/tests/Unit/Theme/ComponentFactoryTest.php b/tests/Unit/Theme/ComponentFactoryTest.php index 839b8616..4ca8e339 100755 --- a/tests/Unit/Theme/ComponentFactoryTest.php +++ b/tests/Unit/Theme/ComponentFactoryTest.php @@ -8,16 +8,16 @@ require_once __DIR__.'/../helpers.php'; -describe('ComponentFactory', function () { +describe('ComponentFactory', function (): void { it(/** * @throws ReflectionException - */ 'injects ServiceLocator into ThemeComponent (without constructor)', function () { + */ 'injects ServiceLocator into ThemeComponent (without constructor)', function (): void { $mockApp = m::mock(ContainerInterface::class); $ref = new ReflectionClass(ThemeInitializer::class); $instance = $ref->newInstanceWithoutConstructor(); $refProp = $ref->getProperty('app'); - $refProp->setAccessible(true); $refProp->setValue($instance, $mockApp); + expect($refProp->getValue($instance))->toBe($mockApp); }); }); diff --git a/tests/Unit/Theme/ImageSizeTest.php b/tests/Unit/Theme/ImageSizeTest.php index e3c3ae4c..35b5d27f 100755 --- a/tests/Unit/Theme/ImageSizeTest.php +++ b/tests/Unit/Theme/ImageSizeTest.php @@ -10,8 +10,8 @@ require_once __DIR__.'/../helpers.php'; -describe('ImageSize', function () { - it('resolves Action from Laravel container', function () { +describe('ImageSize', function (): void { + it('resolves Action from Laravel container', function (): void { $mockAction = m::mock(Action::class); $mockContainer = m::mock(ContainerInterface::class); $mockConfig = m::mock(ConfigRepositoryInterface::class); diff --git a/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderIntegrationTest.php b/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderIntegrationTest.php index 96b637d7..ffcc6f6d 100644 --- a/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderIntegrationTest.php +++ b/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderIntegrationTest.php @@ -6,7 +6,7 @@ use Pollora\Theme\Domain\Models\ThemeModule; use Pollora\Theme\Infrastructure\Services\ThemeAutoloader; -beforeEach(function () { +beforeEach(function (): void { $this->app = new Container; $this->autoloader = new ThemeAutoloader($this->app); @@ -35,20 +35,20 @@ public function getPath(): string }; }); -afterEach(function () { +afterEach(function (): void { // Clean up temporary directory if (is_dir($this->tempDir)) { exec('rm -rf '.escapeshellarg($this->tempDir)); } }); -it('generates correct namespace for theme', function () { +it('generates correct namespace for theme', function (): void { $namespace = $this->autoloader->getThemeNamespace('TestTheme'); expect($namespace)->toBe('Theme\\TestTheme\\'); }); -it('tracks theme registration status', function () { +it('tracks theme registration status', function (): void { // Initially not registered expect($this->autoloader->isThemeRegistered('TestTheme'))->toBeFalse(); @@ -59,7 +59,7 @@ public function getPath(): string expect($this->autoloader->isThemeRegistered('TestTheme'))->toBeTrue(); }); -it('generates correct namespace for different theme names', function () { +it('generates correct namespace for different theme names', function (): void { $testCases = [ 'Solidarmonde' => 'Theme\\Solidarmonde\\', 'MyCustomTheme' => 'Theme\\MyCustomTheme\\', @@ -71,7 +71,7 @@ public function getPath(): string } }); -it('can register multiple themes without conflicts', function () { +it('can register multiple themes without conflicts', function (): void { // Create directory structure for additional themes $theme1Path = $this->tempDir.'/themes/theme-one'; $theme2Path = $this->tempDir.'/themes/theme-two'; @@ -128,7 +128,7 @@ public function getPath(): string expect($this->autoloader->getThemeNamespace('ThemeTwo'))->toBe('Theme\\ThemeTwo\\'); }); -it('handles theme registration idempotently', function () { +it('handles theme registration idempotently', function (): void { // Register the same theme multiple times $this->autoloader->registerThemeModule($this->theme); $this->autoloader->registerThemeModule($this->theme); @@ -139,14 +139,12 @@ public function getPath(): string // Should have only one namespace registration $registeredNamespaces = $this->autoloader->getRegisteredNamespaces(); - $themeNamespaces = array_filter(array_keys($registeredNamespaces), function ($ns) { - return str_starts_with($ns, 'Theme\\TestTheme\\'); - }); + $themeNamespaces = array_filter(array_keys($registeredNamespaces), fn (int|string $ns): bool => str_starts_with((string) $ns, 'Theme\\TestTheme\\')); expect(count($themeNamespaces))->toBe(1); }); -it('can register themes using batch method', function () { +it('can register themes using batch method', function (): void { // Create directory structure for batch themes $batch1Path = $this->tempDir.'/themes/batch-theme-1'; $batch2Path = $this->tempDir.'/themes/batch-theme-2'; @@ -197,13 +195,13 @@ public function getPath(): string expect($this->autoloader->isThemeRegistered('BatchTheme2'))->toBeTrue(); }); -it('returns empty registered namespaces initially', function () { +it('returns empty registered namespaces initially', function (): void { $namespaces = $this->autoloader->getRegisteredNamespaces(); expect($namespaces)->toBeArray()->toBeEmpty(); }); -it('tracks registered namespaces after registration', function () { +it('tracks registered namespaces after registration', function (): void { $this->autoloader->registerThemeModule($this->theme); $namespaces = $this->autoloader->getRegisteredNamespaces(); diff --git a/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderTest.php b/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderTest.php index 367207bb..b2d78006 100644 --- a/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderTest.php +++ b/tests/Unit/Theme/Infrastructure/Services/ThemeAutoloaderTest.php @@ -4,115 +4,82 @@ use Composer\Autoload\ClassLoader; use Illuminate\Container\Container; -use PHPUnit\Framework\TestCase; use Pollora\Theme\Domain\Models\ThemeModule; use Pollora\Theme\Infrastructure\Services\ThemeAutoloader; -class ThemeAutoloaderTest extends TestCase -{ - private Container $app; - - private ClassLoader $classLoader; - - private ThemeAutoloader $autoloader; - - protected function setUp(): void - { +describe('ThemeAutoloader', function (): void { + beforeEach(function (): void { $this->app = new Container; - $this->classLoader = $this->createMock(ClassLoader::class); - - // Bind the class loader to the container + $this->classLoader = Mockery::mock(ClassLoader::class)->shouldIgnoreMissing(); $this->app->instance(ClassLoader::class, $this->classLoader); - $this->autoloader = new ThemeAutoloader($this->app); - } + }); - public function test_it_registers_theme_module(): void - { + it('registers theme module', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_register_'.uniqid(); $appDir = $tempDir.'/app'; mkdir($appDir, 0777, true); - $theme = $this->createMockTheme('Solidarmonde', $tempDir); + $theme = createMockTheme('Solidarmonde', $tempDir); - $this->classLoader - ->expects($this->once()) - ->method('addPsr4') + $this->classLoader->shouldReceive('addPsr4') + ->once() ->with('Theme\\Solidarmonde\\', $tempDir.'/app'); $this->autoloader->registerThemeModule($theme); - // Cleanup rmdir($appDir); rmdir($tempDir); - } + }); - public function test_it_gets_theme_namespace(): void - { - $namespace = $this->autoloader->getThemeNamespace('TestTheme'); + it('gets theme namespace', function (): void { + expect($this->autoloader->getThemeNamespace('TestTheme'))->toBe('Theme\\TestTheme\\'); + }); - $this->assertEquals('Theme\\TestTheme\\', $namespace); - } - - public function test_it_checks_if_theme_is_registered(): void - { + it('checks if theme is registered', function (): void { $tempDir = sys_get_temp_dir().'/test_theme_check_'.uniqid(); $appDir = $tempDir.'/app'; mkdir($appDir, 0777, true); - $theme = $this->createMockTheme('TestTheme', $tempDir); + $theme = createMockTheme('TestTheme', $tempDir); - $this->assertFalse($this->autoloader->isThemeRegistered('TestTheme')); + expect($this->autoloader->isThemeRegistered('TestTheme'))->toBeFalse(); $this->autoloader->registerThemeModule($theme); - $this->assertTrue($this->autoloader->isThemeRegistered('TestTheme')); + expect($this->autoloader->isThemeRegistered('TestTheme'))->toBeTrue(); - // Cleanup rmdir($appDir); rmdir($tempDir); - } + }); - public function test_it_registers_multiple_themes(): void - { + it('registers multiple themes', function (): void { $tempDir1 = sys_get_temp_dir().'/test_theme_multiple1_'.uniqid(); - $appDir1 = $tempDir1.'/app'; - mkdir($appDir1, 0777, true); + mkdir($tempDir1.'/app', 0777, true); $tempDir2 = sys_get_temp_dir().'/test_theme_multiple2_'.uniqid(); - $appDir2 = $tempDir2.'/app'; - mkdir($appDir2, 0777, true); - - $theme1 = $this->createMockTheme('ThemeOne', $tempDir1); - $theme2 = $this->createMockTheme('ThemeTwo', $tempDir2); + mkdir($tempDir2.'/app', 0777, true); - $this->classLoader - ->expects($this->exactly(2)) - ->method('addPsr4'); + $theme1 = createMockTheme('ThemeOne', $tempDir1); + $theme2 = createMockTheme('ThemeTwo', $tempDir2); - $this->classLoader - ->expects($this->once()) - ->method('register'); + $this->classLoader->shouldReceive('addPsr4')->twice(); + $this->classLoader->shouldReceive('register')->once(); $this->autoloader->registerThemes([$theme1, $theme2]); - // Cleanup - rmdir($appDir1); + rmdir($tempDir1.'/app'); rmdir($tempDir1); - rmdir($appDir2); + rmdir($tempDir2.'/app'); rmdir($tempDir2); - } - - private function createMockTheme(string $name, string $path): ThemeModule - { - $theme = $this->createMock(ThemeModule::class); + }); +}); - $theme->method('getStudlyName') - ->willReturn($name); - - $theme->method('getPath') - ->willReturn($path); +function createMockTheme(string $name, string $path): ThemeModule +{ + $theme = Mockery::mock(ThemeModule::class); + $theme->shouldReceive('getStudlyName')->andReturn($name); + $theme->shouldReceive('getPath')->andReturn($path); - return $theme; - } + return $theme; } diff --git a/tests/Unit/Theme/MenusTest.php b/tests/Unit/Theme/MenusTest.php index 1fb9bf13..10ab51b9 100755 --- a/tests/Unit/Theme/MenusTest.php +++ b/tests/Unit/Theme/MenusTest.php @@ -11,8 +11,8 @@ require_once __DIR__.'/../helpers.php'; -describe('Menus', function () { - it('resolves Action and Filter from container', function () { +describe('Menus', function (): void { + it('resolves Action and Filter from container', function (): void { $mockAction = m::mock(Action::class); $mockFilter = m::mock(Filter::class); $mockContainer = m::mock(ContainerInterface::class); @@ -33,7 +33,7 @@ $component = new Menus($mockContainer, $mockConfig); $ref = new ReflectionProperty($component, 'app'); - $ref->setAccessible(true); + expect($ref->getValue($component))->toBe($mockContainer); }); }); diff --git a/tests/Unit/Theme/PatternComponentTest.php b/tests/Unit/Theme/PatternComponentTest.php index bd2cb4fb..88245f2d 100755 --- a/tests/Unit/Theme/PatternComponentTest.php +++ b/tests/Unit/Theme/PatternComponentTest.php @@ -10,8 +10,8 @@ require_once __DIR__.'/../helpers.php'; -describe('PatternComponent', function () { - it('resolves PatternServiceInterface from Laravel container', function () { +describe('PatternComponent', function (): void { + it('resolves PatternServiceInterface from Laravel container', function (): void { $mockPatternService = m::mock(PatternServiceInterface::class); $mockAction = m::mock(Action::class); $mockContainer = m::mock(ContainerInterface::class); diff --git a/tests/Unit/Theme/SidebarTest.php b/tests/Unit/Theme/SidebarTest.php index 8231da93..451b3fc9 100755 --- a/tests/Unit/Theme/SidebarTest.php +++ b/tests/Unit/Theme/SidebarTest.php @@ -10,8 +10,8 @@ require_once __DIR__.'/../helpers.php'; -describe('Sidebar', function () { - it('resolves Application from Laravel container', function () { +describe('Sidebar', function (): void { + it('resolves Application from Laravel container', function (): void { $mockAction = m::mock(Action::class); $mockContainer = m::mock(ContainerInterface::class); $mockConfig = m::mock(ConfigRepositoryInterface::class); @@ -23,7 +23,7 @@ $sidebar = new Sidebar($mockContainer, $mockConfig); $ref = new ReflectionProperty($sidebar, 'app'); - $ref->setAccessible(true); + expect($ref->getValue($sidebar))->toBe($mockContainer); }); }); diff --git a/tests/Unit/Theme/SupportTest.php b/tests/Unit/Theme/SupportTest.php index 8892256d..5a162ac6 100755 --- a/tests/Unit/Theme/SupportTest.php +++ b/tests/Unit/Theme/SupportTest.php @@ -10,8 +10,8 @@ require_once __DIR__.'/../helpers.php'; -describe('Support', function () { - it('resolves Action from container', function () { +describe('Support', function (): void { + it('resolves Action from container', function (): void { $mockAction = m::mock(Action::class); $mockContainer = m::mock(ContainerInterface::class); $mockConfig = m::mock(ConfigRepositoryInterface::class); @@ -27,7 +27,7 @@ $component = new Support($mockContainer, $mockConfig); $ref = new ReflectionProperty($component, 'app'); - $ref->setAccessible(true); + expect($ref->getValue($component))->toBe($mockContainer); }); }); diff --git a/tests/Unit/Theme/TemplatesTest.php b/tests/Unit/Theme/TemplatesTest.php index 89aa12e7..71f549b7 100755 --- a/tests/Unit/Theme/TemplatesTest.php +++ b/tests/Unit/Theme/TemplatesTest.php @@ -10,8 +10,8 @@ require_once __DIR__.'/../helpers.php'; -describe('Templates', function () { - it('resolves Action from container', function () { +describe('Templates', function (): void { + it('resolves Action from container', function (): void { $mockAction = m::mock(Action::class); $mockContainer = m::mock(ContainerInterface::class); $mockConfig = m::mock(ConfigRepositoryInterface::class); @@ -27,7 +27,7 @@ $component = new Templates($mockContainer, $mockConfig); $ref = new ReflectionProperty($component, 'app'); - $ref->setAccessible(true); + expect($ref->getValue($component))->toBe($mockContainer); }); }); diff --git a/tests/Unit/Theme/ThemeComponentProviderTest.php b/tests/Unit/Theme/ThemeComponentProviderTest.php index 75648127..3e0383d1 100755 --- a/tests/Unit/Theme/ThemeComponentProviderTest.php +++ b/tests/Unit/Theme/ThemeComponentProviderTest.php @@ -21,7 +21,7 @@ // For simplicity with Mockery, we'll define them in `beforeEach` and access them via `$this`. // Pest will make $this available in tests if the closure in beforeEach uses $this. -beforeEach(function () { +beforeEach(function (): void { // parent::setUp() from TestCase is usually not needed directly in Pest unless it does something critical. // If TestCase::setUp() has essential mocking or app bootstrapping, it might need to be replicated or called. // Assuming Tests\TestCase::setUp() is for general Laravel testing setup, which Pest handles via uses(Tests\TestCase::class). @@ -52,7 +52,7 @@ // it should be included with `uses(Tests\TestCase::class);` at the top level of the file. // For now, assuming it's not strictly necessary for these specific tests beyond Mockery setup. -it('registers component factory (verifies all core components registration)', function () { +it('registers component factory (verifies all core components registration)', function (): void { // This test is named component_factory, but ThemeComponentProvider doesn't deal with ComponentFactory directly. // It registers individual components. Let's assume this test is actually about verifying that // all components defined in the provider are registered correctly. @@ -70,7 +70,7 @@ expect($this->provider)->toBeInstanceOf(ThemeComponentProvider::class); }); -it('registers core components', function () { +it('registers core components', function (): void { $mockComponent = m::mock(ThemeComponent::class); $mockComponent->shouldReceive('register')->byDefault(); // All components have a register method @@ -90,12 +90,12 @@ expect($this->provider)->toBeInstanceOf(ThemeComponentProvider::class); }); -it('can be instantiated', function () { +it('can be instantiated', function (): void { expect($this->provider)->toBeInstanceOf(ThemeComponentProvider::class); }); // tearDown with m::close() is usually handled by Pest's Mockery plugin or a global helper if needed. // If not using the plugin, m::close() might be called in an `afterEach`. -afterEach(function () { +afterEach(function (): void { m::close(); }); diff --git a/tests/Unit/Theme/ThemeInitializerTest.php b/tests/Unit/Theme/ThemeInitializerTest.php index 05f3909f..01028ce8 100755 --- a/tests/Unit/Theme/ThemeInitializerTest.php +++ b/tests/Unit/Theme/ThemeInitializerTest.php @@ -8,14 +8,14 @@ require_once __DIR__.'/../helpers.php'; -describe('ThemeInitializer', function () { - it('injects ContainerInterface mock (plus ServiceLocator legacy)', function () { +describe('ThemeInitializer', function (): void { + it('injects ContainerInterface mock (plus ServiceLocator legacy)', function (): void { $mockContainer = m::mock(ContainerInterface::class); $ref = new ReflectionClass(ThemeInitializer::class); $instance = $ref->newInstanceWithoutConstructor(); $refProp = $ref->getProperty('app'); - $refProp->setAccessible(true); $refProp->setValue($instance, $mockContainer); + expect($refProp->getValue($instance))->toBe($mockContainer); }); }); diff --git a/tests/Unit/Theme/ThemeManagerTest.php b/tests/Unit/Theme/ThemeManagerTest.php index 12f869d1..9767bccb 100755 --- a/tests/Unit/Theme/ThemeManagerTest.php +++ b/tests/Unit/Theme/ThemeManagerTest.php @@ -12,7 +12,7 @@ use Pollora\Theme\Domain\Exceptions\ThemeException; use Pollora\Theme\Domain\Models\ThemeMetadata; -beforeEach(function () { +beforeEach(function (): void { // Create mock container with config property $this->app = Mockery::mock(Container::class); $this->config = Mockery::mock('config'); @@ -29,7 +29,7 @@ $this->themeManager = new ThemeManager($this->app, $this->viewFinder, $this->localeLoader, $this->moduleRepository, $this->themeRegistrar, $this->consoleDetectionService); }); -test('loads a valid theme', function () { +test('loads a valid theme', function (): void { $testPath = '/path/to/themes'; $themeName = 'testTheme'; $app = Mockery::mock(Container::class); @@ -48,6 +48,7 @@ [$app, $this->viewFinder, $this->localeLoader, $moduleRepository, $themeRegistrar, $consoleDetectionService] )->makePartial(); $manager->shouldAllowMockingProtectedMethods(); + $themeMetadata = Mockery::mock(ThemeMetadata::class); $themeMetadata->shouldReceive('getName')->andReturn($themeName); $themeMetadata->shouldReceive('getBasePath')->andReturn($testPath.'/'.$themeName); @@ -62,12 +63,12 @@ expect($manager->instance())->toBeInstanceOf(ThemeManager::class); }); -test('throws an exception if theme name is empty', function () { +test('throws an exception if theme name is empty', function (): void { expect(fn () => $this->themeManager->load(''))->toThrow(ThemeException::class) ->and(fn () => $this->themeManager->load('0'))->toThrow(ThemeException::class); }); -test('throws an exception if theme directory does not exist', function () { +test('throws an exception if theme directory does not exist', function (): void { $themeName = 'nonexistent'; $app = Mockery::mock(Container::class); $config = Mockery::mock('config'); @@ -84,15 +85,16 @@ [$app, $this->viewFinder, $this->localeLoader, $moduleRepository, $themeRegistrar, $consoleDetectionService] )->makePartial(); $manager->shouldAllowMockingProtectedMethods(); + $themeMetadata = Mockery::mock(ThemeMetadata::class); $themeMetadata->shouldReceive('getName')->andReturn($themeName); $themeMetadata->shouldReceive('getBasePath')->andReturn('/path/to/themes/'.$themeName); $manager->shouldReceive('createThemeMetadata')->andReturn($themeMetadata); $manager->shouldReceive('getThemesPath')->andReturn('/path/to/themes'); expect(fn () => $manager->load($themeName)) - ->toThrow(ThemeException::class, "Theme directory {$themeName} not found."); + ->toThrow(ThemeException::class, sprintf('Theme directory %s not found.', $themeName)); }); -test('instance returns self', function () { +test('instance returns self', function (): void { expect($this->themeManager->instance())->toBeInstanceOf(ThemeManager::class); }); diff --git a/tests/Unit/Theme/ThemeRegistrarTest.php b/tests/Unit/Theme/ThemeRegistrarTest.php index 4ff06aca..5221be04 100644 --- a/tests/Unit/Theme/ThemeRegistrarTest.php +++ b/tests/Unit/Theme/ThemeRegistrarTest.php @@ -2,174 +2,83 @@ declare(strict_types=1); -namespace Tests\Unit\Theme; - use Illuminate\Container\Container; -use PHPUnit\Framework\TestCase; -use Pollora\Modules\Domain\Contracts\ModuleDiscoveryOrchestratorInterface; -use Pollora\Modules\Domain\Contracts\ModuleRepositoryInterface; -use Pollora\Modules\Infrastructure\Services\ModuleAssetManager; -use Pollora\Modules\Infrastructure\Services\ModuleComponentManager; -use Pollora\Modules\Infrastructure\Services\ModuleConfigurationLoader; use Pollora\Theme\Application\Services\ThemeRegistrar; use Pollora\Theme\Domain\Contracts\ThemeModuleInterface; use Pollora\Theme\Infrastructure\Services\WordPressThemeParser; -use Psr\Container\ContainerInterface; -/** - * Test suite for the theme self-registration system. - */ -class ThemeRegistrarTest extends TestCase +function setupRegistrarMocks(Container $container, $parser): void { - private ThemeRegistrar $registrar; - - private ContainerInterface $container; - - private WordPressThemeParser $parser; - - protected function setUp(): void - { - parent::setUp(); - - $this->container = $this->createMock(ContainerInterface::class); - $this->parser = $this->createMock(WordPressThemeParser::class); + $parser->shouldReceive('parseThemeHeaders')->andReturn(['Name' => 'Test Theme', 'Version' => '1.0.0']); +} +describe('ThemeRegistrar', function (): void { + beforeEach(function (): void { + $this->container = new Container; + $this->parser = Mockery::mock(WordPressThemeParser::class)->shouldIgnoreMissing(); $this->registrar = new ThemeRegistrar($this->container, $this->parser); - } + }); - public function test_can_register_active_theme(): void - { - // Arrange - $this->parser->expects($this->once()) - ->method('parseThemeHeaders') + it('can register active theme', function (): void { + $this->parser->shouldReceive('parseThemeHeaders') + ->once() ->with('/path/to/theme/style.css') - ->willReturn(['Name' => 'Test Theme', 'Version' => '1.0.0']); - - $this->container->expects($this->any()) - ->method('has') - ->willReturnMap([ - ['app', true], - [ModuleRepositoryInterface::class, false], - [ModuleDiscoveryOrchestratorInterface::class, false], - [ModuleConfigurationLoader::class, false], - [ModuleComponentManager::class, false], - [ModuleAssetManager::class, false], - ]); - - $mockApp = $this->createMock(Container::class); - $this->container->expects($this->any()) - ->method('get') - ->with('app') - ->willReturn($mockApp); - - // Act - $theme = $this->registrar->register(); + ->andReturn(['Name' => 'Test Theme', 'Version' => '1.0.0']); - // Assert - $this->assertInstanceOf(ThemeModuleInterface::class, $theme); - $this->assertEquals('test-theme', $theme->getName()); - $this->assertEquals('/path/to/theme', $theme->getPath()); - $this->assertTrue($theme->isEnabled()); - } + $theme = $this->registrar->register(); - public function test_can_get_active_theme(): void - { - // Arrange - $this->setupMocksForRegistration(); + expect($theme)->toBeInstanceOf(ThemeModuleInterface::class); + expect($theme->getName())->toBe('test-theme'); + expect($theme->getPath())->toBe('/path/to/theme'); + expect($theme->isEnabled())->toBeTrue(); + }); - // Act + it('can get active theme', function (): void { + setupRegistrarMocks($this->container, $this->parser); $registeredTheme = $this->registrar->register(); $activeTheme = $this->registrar->getActiveTheme(); - // Assert - $this->assertSame($registeredTheme, $activeTheme); - } - - public function test_returns_null_when_no_theme_registered(): void - { - // Act - $activeTheme = $this->registrar->getActiveTheme(); + expect($activeTheme)->toBe($registeredTheme); + }); - // Assert - $this->assertNull($activeTheme); - } + it('returns null when no theme registered', function (): void { + expect($this->registrar->getActiveTheme())->toBeNull(); + }); - public function test_can_check_if_theme_is_active(): void - { - // Arrange - $this->setupMocksForRegistration(); + it('can check if theme is active', function (): void { + setupRegistrarMocks($this->container, $this->parser); - // Act $this->registrar->register(); - // Assert - $this->assertTrue($this->registrar->isThemeActive('test-theme')); - $this->assertTrue($this->registrar->isThemeActive('TEST-THEME')); // Case insensitive - $this->assertFalse($this->registrar->isThemeActive('other-theme')); - } + expect($this->registrar->isThemeActive('test-theme'))->toBeTrue(); + expect($this->registrar->isThemeActive('TEST-THEME'))->toBeTrue(); + expect($this->registrar->isThemeActive('other-theme'))->toBeFalse(); + }); - public function test_returns_false_when_no_theme_registered_for_is_active_check(): void - { - // Act & Assert - $this->assertFalse($this->registrar->isThemeActive('any-theme')); - } + it('returns false when no theme registered for isActive check', function (): void { + expect($this->registrar->isThemeActive('any-theme'))->toBeFalse(); + }); - public function test_can_reset_active_theme(): void - { - // Arrange - $this->setupMocksForRegistration(); + it('can reset active theme', function (): void { + setupRegistrarMocks($this->container, $this->parser); $this->registrar->register(); - - // Act $this->registrar->resetActiveTheme(); - // Assert - $this->assertNull($this->registrar->getActiveTheme()); - $this->assertFalse($this->registrar->isThemeActive('test-theme')); - } + expect($this->registrar->getActiveTheme())->toBeNull(); + expect($this->registrar->isThemeActive('test-theme'))->toBeFalse(); + }); - public function test_parses_theme_headers_when_no_data_provided(): void - { - // Arrange - $expectedStylePath = '/path/to/theme/style.css'; + it('parses theme headers when no data provided', function (): void { $parsedData = ['Name' => 'Parsed Theme', 'Version' => '2.0.0']; - $this->parser->expects($this->once()) - ->method('parseThemeHeaders') - ->with($expectedStylePath) - ->willReturn($parsedData); - - $this->setupMocksForRegistration(); + $this->parser->shouldReceive('parseThemeHeaders') + ->once() + ->with('/path/to/theme/style.css') + ->andReturn($parsedData); - // Act $theme = $this->registrar->register(); - // Assert - $this->assertEquals($parsedData, $theme->getHeaders()); - } - - private function setupMocksForRegistration(): void - { - $this->parser->expects($this->any()) - ->method('parseThemeHeaders') - ->willReturn(['Name' => 'Test Theme', 'Version' => '1.0.0']); - - $this->container->expects($this->any()) - ->method('has') - ->willReturnMap([ - ['app', true], - [ModuleRepositoryInterface::class, false], - [ModuleDiscoveryOrchestratorInterface::class, false], - [ModuleConfigurationLoader::class, false], - [ModuleComponentManager::class, false], - [ModuleAssetManager::class, false], - ]); - - $mockApp = $this->createMock(Container::class); - $this->container->expects($this->any()) - ->method('get') - ->with('app') - ->willReturn($mockApp); - } -} + expect($theme->getHeaders())->toBe($parsedData); + }); +}); diff --git a/tests/Unit/VersionCheck/AdminNoticeTest.php b/tests/Unit/VersionCheck/AdminNoticeTest.php index a158f2ff..d67c734d 100644 --- a/tests/Unit/VersionCheck/AdminNoticeTest.php +++ b/tests/Unit/VersionCheck/AdminNoticeTest.php @@ -8,12 +8,12 @@ require_once dirname(__DIR__).'/helpers.php'; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); }); -describe('AdminNotice', function () { - it('renders nothing when no update is available', function () { +describe('AdminNotice', function (): void { + it('renders nothing when no update is available', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.3.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -27,14 +27,14 @@ expect($output)->toBeEmpty(); }); - it('renders a warning notice when update is available', function () { + it('renders a warning notice when update is available', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); - setWordPressFunction('get_current_user_id', fn () => 1); - setWordPressFunction('get_user_meta', fn () => ''); - setWordPressFunction('wp_create_nonce', fn () => 'test-nonce'); + setWordPressFunction('get_current_user_id', fn (): int => 1); + setWordPressFunction('get_user_meta', fn (): string => ''); + setWordPressFunction('wp_create_nonce', fn (): string => 'test-nonce'); $notice = new AdminNotice(new VersionComparator($checker)); @@ -49,13 +49,13 @@ expect($output)->toContain('changelog'); }); - it('renders nothing when notice is dismissed for current version', function () { + it('renders nothing when notice is dismissed for current version', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); - setWordPressFunction('get_current_user_id', fn () => 1); - setWordPressFunction('get_user_meta', fn () => '13.3.0'); + setWordPressFunction('get_current_user_id', fn (): int => 1); + setWordPressFunction('get_user_meta', fn (): string => '13.3.0'); $notice = new AdminNotice(new VersionComparator($checker)); diff --git a/tests/Unit/VersionCheck/PackagistVersionCheckerTest.php b/tests/Unit/VersionCheck/PackagistVersionCheckerTest.php index 59833f39..6bde7779 100644 --- a/tests/Unit/VersionCheck/PackagistVersionCheckerTest.php +++ b/tests/Unit/VersionCheck/PackagistVersionCheckerTest.php @@ -6,12 +6,12 @@ require_once dirname(__DIR__).'/helpers.php'; -beforeEach(function () { +beforeEach(function (): void { setupWordPressMocks(); }); -describe('PackagistVersionChecker', function () { - it('returns the currently installed version', function () { +describe('PackagistVersionChecker', function (): void { + it('returns the currently installed version', function (): void { $checker = new PackagistVersionChecker; $version = $checker->getCurrentVersion(); @@ -19,25 +19,25 @@ expect($version)->toBeString(); }); - it('returns cached version from transient', function () { - setWordPressFunction('get_transient', fn () => '99.0.0'); + it('returns cached version from transient', function (): void { + setWordPressFunction('get_transient', fn (): string => '99.0.0'); $checker = new PackagistVersionChecker; expect($checker->getLatestVersion())->toBe('99.0.0'); }); - it('fetches from packagist when no cache and stores in transient', function () { - setWordPressFunction('get_transient', fn () => false); + it('fetches from packagist when no cache and stores in transient', function (): void { + setWordPressFunction('get_transient', fn (): false => false); $storedVersion = null; - setWordPressFunction('set_transient', function ($key, $value, $ttl) use (&$storedVersion) { + setWordPressFunction('set_transient', function ($key, $value, $ttl) use (&$storedVersion): true { $storedVersion = $value; return true; }); - setWordPressFunction('wp_remote_get', fn () => [ + setWordPressFunction('wp_remote_get', fn (): array => [ 'body' => json_encode([ 'packages' => [ 'pollora/framework' => [ @@ -50,7 +50,7 @@ ]); setWordPressFunction('wp_remote_retrieve_body', fn ($response) => $response['body']); - setWordPressFunction('is_wp_error', fn () => false); + setWordPressFunction('is_wp_error', fn (): false => false); $checker = new PackagistVersionChecker; @@ -58,11 +58,11 @@ expect($storedVersion)->toBe('13.3.0'); }); - it('skips dev and pre-release versions', function () { - setWordPressFunction('get_transient', fn () => false); - setWordPressFunction('set_transient', fn () => true); + it('skips dev and pre-release versions', function (): void { + setWordPressFunction('get_transient', fn (): false => false); + setWordPressFunction('set_transient', fn (): true => true); - setWordPressFunction('wp_remote_get', fn () => [ + setWordPressFunction('wp_remote_get', fn (): array => [ 'body' => json_encode([ 'packages' => [ 'pollora/framework' => [ @@ -77,17 +77,17 @@ ]); setWordPressFunction('wp_remote_retrieve_body', fn ($response) => $response['body']); - setWordPressFunction('is_wp_error', fn () => false); + setWordPressFunction('is_wp_error', fn (): false => false); $checker = new PackagistVersionChecker; expect($checker->getLatestVersion())->toBe('13.3.0'); }); - it('returns null on API error', function () { - setWordPressFunction('get_transient', fn () => false); - setWordPressFunction('is_wp_error', fn () => true); - setWordPressFunction('wp_remote_get', fn () => new stdClass); + it('returns null on API error', function (): void { + setWordPressFunction('get_transient', fn (): false => false); + setWordPressFunction('is_wp_error', fn (): true => true); + setWordPressFunction('wp_remote_get', fn (): stdClass => new stdClass); $checker = new PackagistVersionChecker; diff --git a/tests/Unit/VersionCheck/SiteHealthCheckTest.php b/tests/Unit/VersionCheck/SiteHealthCheckTest.php index 3ad74592..94c10877 100644 --- a/tests/Unit/VersionCheck/SiteHealthCheckTest.php +++ b/tests/Unit/VersionCheck/SiteHealthCheckTest.php @@ -6,8 +6,8 @@ use Pollora\VersionCheck\Domain\Services\VersionComparator; use Pollora\VersionCheck\UI\Http\SiteHealthCheck; -describe('SiteHealthCheck', function () { - it('adds Pollora section to debug information', function () { +describe('SiteHealthCheck', function (): void { + it('adds Pollora section to debug information', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.3.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -22,7 +22,7 @@ expect($info['pollora']['fields']['up_to_date']['value'])->toBe('Yes'); }); - it('shows not up to date when update available', function () { + it('shows not up to date when update available', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -33,7 +33,7 @@ expect($info['pollora']['fields']['up_to_date']['value'])->toBe('No'); }); - it('adds version test to site status tests', function () { + it('adds version test to site status tests', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $health = new SiteHealthCheck(new VersionComparator($checker)); @@ -43,7 +43,7 @@ expect($tests['direct']['pollora_update']['test'])->toBeCallable(); }); - it('returns good status when up to date', function () { + it('returns good status when up to date', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.3.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -55,7 +55,7 @@ expect($result['badge']['color'])->toBe('blue'); }); - it('returns recommended status when update available', function () { + it('returns recommended status when update available', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -68,7 +68,7 @@ expect($result['label'])->toContain('13.3.0'); }); - it('returns recommended status when version cannot be determined', function () { + it('returns recommended status when version cannot be determined', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn(null); $checker->shouldReceive('getLatestVersion')->andReturn(null); diff --git a/tests/Unit/VersionCheck/VersionComparatorTest.php b/tests/Unit/VersionCheck/VersionComparatorTest.php index ae9b578a..6084e10a 100644 --- a/tests/Unit/VersionCheck/VersionComparatorTest.php +++ b/tests/Unit/VersionCheck/VersionComparatorTest.php @@ -5,8 +5,8 @@ use Pollora\VersionCheck\Domain\Contracts\VersionCheckerInterface; use Pollora\VersionCheck\Domain\Services\VersionComparator; -describe('VersionComparator', function () { - it('detects update available when latest is newer', function () { +describe('VersionComparator', function (): void { + it('detects update available when latest is newer', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -16,7 +16,7 @@ expect($comparator->isUpdateAvailable())->toBeTrue(); }); - it('reports no update when versions match', function () { + it('reports no update when versions match', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.3.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -26,7 +26,7 @@ expect($comparator->isUpdateAvailable())->toBeFalse(); }); - it('reports no update when current is newer', function () { + it('reports no update when current is newer', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('14.0.0'); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -36,7 +36,7 @@ expect($comparator->isUpdateAvailable())->toBeFalse(); }); - it('reports no update when current version is null', function () { + it('reports no update when current version is null', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn(null); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); @@ -46,7 +46,7 @@ expect($comparator->isUpdateAvailable())->toBeFalse(); }); - it('reports no update when latest version is null', function () { + it('reports no update when latest version is null', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.3.0'); $checker->shouldReceive('getLatestVersion')->andReturn(null); @@ -56,7 +56,7 @@ expect($comparator->isUpdateAvailable())->toBeFalse(); }); - it('delegates getCurrentVersion to checker', function () { + it('delegates getCurrentVersion to checker', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getCurrentVersion')->andReturn('13.2.0'); @@ -65,7 +65,7 @@ expect($comparator->getCurrentVersion())->toBe('13.2.0'); }); - it('delegates getLatestVersion to checker', function () { + it('delegates getLatestVersion to checker', function (): void { $checker = Mockery::mock(VersionCheckerInterface::class); $checker->shouldReceive('getLatestVersion')->andReturn('13.3.0'); diff --git a/tests/Unit/WpRest/Infrastructure/Providers/WpRestAttributeServiceProviderTest.php b/tests/Unit/WpRest/Infrastructure/Providers/WpRestAttributeServiceProviderTest.php index 112dc2c5..0ac21541 100644 --- a/tests/Unit/WpRest/Infrastructure/Providers/WpRestAttributeServiceProviderTest.php +++ b/tests/Unit/WpRest/Infrastructure/Providers/WpRestAttributeServiceProviderTest.php @@ -2,110 +2,61 @@ declare(strict_types=1); -namespace Tests\Unit\WpRest\Infrastructure\Providers; - use Illuminate\Container\Container; use Pollora\Discovery\Domain\Contracts\DiscoveryEngineInterface; use Pollora\WpRest\Infrastructure\Providers\WpRestAttributeServiceProvider; use Pollora\WpRest\Infrastructure\Services\WpRestDiscovery; -use Tests\TestCase as BaseTestCase; - -/** - * Test suite for WpRestAttributeServiceProvider. - * - * Tests the service provider functionality for discovering and registering - * WordPress REST API routes through the new discovery system. - */ -final class WpRestAttributeServiceProviderTest extends BaseTestCase -{ - private Container $container; - - private WpRestAttributeServiceProvider $provider; - - private DiscoveryEngineInterface $discoveryEngine; - - protected function setUp(): void - { - parent::setUp(); +describe('WpRestAttributeServiceProvider', function (): void { + beforeEach(function (): void { $this->container = new Container; $this->provider = new WpRestAttributeServiceProvider($this->container); - // Create mock discovery engine - $this->discoveryEngine = $this->createMock(DiscoveryEngineInterface::class); + $this->discoveryEngine = Mockery::mock(DiscoveryEngineInterface::class)->shouldIgnoreMissing(); $this->container->instance(DiscoveryEngineInterface::class, $this->discoveryEngine); - } + }); - public function test_register_registers_wprest_discovery(): void - { - // The register method should register WpRestDiscovery as singleton + it('registers WpRestDiscovery as singleton', function (): void { $initialBindings = count($this->container->getBindings()); $this->provider->register(); - // Should have registered WpRestDiscovery - $this->assertEquals($initialBindings + 1, count($this->container->getBindings())); - $this->assertTrue($this->container->bound(WpRestDiscovery::class)); - } + expect(count($this->container->getBindings()))->toBe($initialBindings + 1); + expect($this->container->bound(WpRestDiscovery::class))->toBeTrue(); + }); - public function test_boot_registers_discovered_wp_rest_routes(): void - { - // This test verifies that the boot method processes discovered REST routes - $this->expectNotToPerformAssertions(); - - // Should not throw an exception + it('boot processes discovered REST routes without error', function (): void { $this->provider->boot(); - } - - public function test_boot_handles_empty_discovery_gracefully(): void - { - $this->expectNotToPerformAssertions(); + })->throwsNoExceptions(); - // Should not throw an exception even with empty discovery + it('boot handles empty discovery gracefully', function (): void { $this->provider->boot(); - } - - public function test_boot_handles_discovery_failure_gracefully(): void - { - $this->expectNotToPerformAssertions(); + })->throwsNoExceptions(); - // Should not throw an exception even if discovery fails + it('boot handles discovery failure gracefully', function (): void { $this->provider->boot(); - } + })->throwsNoExceptions(); - public function test_boot_registers_wprest_discovery_with_engine(): void - { - // Test that boot method registers WpRestDiscovery with the discovery engine - $this->discoveryEngine->expects($this->once()) - ->method('addDiscovery') - ->with('wp_rest_routes', $this->isInstanceOf(WpRestDiscovery::class)); + it('boot registers WpRestDiscovery with engine', function (): void { + $this->discoveryEngine->shouldReceive('addDiscovery') + ->once() + ->with('wp_rest_routes', Mockery::type(WpRestDiscovery::class)); $this->provider->register(); $this->provider->boot(); - } + }); - public function test_boot_handles_no_discovery_engine_gracefully(): void - { - // Remove discovery engine from container + it('boot handles no discovery engine gracefully', function (): void { unset($this->container[DiscoveryEngineInterface::class]); - // Should not throw an exception when no discovery engine is bound - $this->expectNotToPerformAssertions(); $this->provider->boot(); - } + })->throwsNoExceptions(); - public function test_boot_registers_discovered_wp_rest_routes_with_valid_data(): void - { - $this->expectNotToPerformAssertions(); - - // Should not throw an exception when processing valid routes + it('boot handles valid route data without error', function (): void { $this->provider->boot(); - } -} + })->throwsNoExceptions(); +}); -/** - * Test WordPress REST route class for testing purposes. - */ class TestWpRestRoute { public string $namespace = 'test/v1'; diff --git a/tests/Unit/WpRest/WpRestDiscoveryTest.php b/tests/Unit/WpRest/WpRestDiscoveryTest.php index fb227189..ed292d5b 100644 --- a/tests/Unit/WpRest/WpRestDiscoveryTest.php +++ b/tests/Unit/WpRest/WpRestDiscoveryTest.php @@ -11,17 +11,17 @@ // Mock WordPress functions if they don't exist if (! function_exists('register_rest_route')) { - function register_rest_route($namespace, $route, $args) + function register_rest_route($namespace, $route, $args): bool { global $registered_routes; - $registered_routes[] = compact('namespace', 'route', 'args'); + $registered_routes[] = ['namespace' => $namespace, 'route' => $route, 'args' => $args]; return true; } } if (! function_exists('add_action')) { - function add_action($hook, $callback, $priority = 10, $accepted_args = 1) + function add_action($hook, $callback, $priority = 10, $accepted_args = 1): bool { global $wp_actions; $wp_actions[$hook][] = $callback; @@ -90,15 +90,15 @@ public function __construct( ) {} } -describe('WpRestDiscovery', function () { +describe('WpRestDiscovery', function (): void { - beforeEach(function () { + beforeEach(function (): void { global $registered_routes, $wp_actions; $registered_routes = []; $wp_actions = []; }); - test('discover method processes only DiscoveredClass instances', function () { + test('discover method processes only DiscoveredClass instances', function (): void { $discovery = new WpRestDiscovery; // Test that discovery starts empty @@ -111,7 +111,7 @@ public function __construct( expect($discovery->getItems()->all())->toHaveCount(1); }); - test('basic discovery functionality works', function () { + test('basic discovery functionality works', function (): void { $discovery = new WpRestDiscovery; // Test that we can manually add items (simulating discovery) @@ -131,7 +131,7 @@ class: WpRestRoute::class, expect($discovery->getItems()->all())->toHaveCount(1); }); - test('registers REST routes when applying discovered items', function () { + test('registers REST routes when applying discovered items', function (): void { global $registered_routes, $wp_actions; $discovery = new WpRestDiscovery; @@ -161,7 +161,7 @@ class: WpRestRoute::class, expect($discovery->getItems()->all())->toHaveCount(1); }); - test('handles reflection errors gracefully', function () { + test('handles reflection errors gracefully', function (): void { $discovery = new WpRestDiscovery; $location = new DiscoveryLocation('', '/test/path'); $reflectionCache = new ReflectionCache; @@ -186,19 +186,19 @@ class: WpRestRoute::class, expect(fn () => $discovery->apply())->not->toThrow(Exception::class); }); - test('returns correct identifier', function () { + test('returns correct identifier', function (): void { $discovery = new WpRestDiscovery; expect($discovery->getIdentifier())->toBe('wp_rest_routes'); }); - test('wrapper system works with non-Attributable classes', function () { + test('wrapper system works with non-Attributable classes', function (): void { // Test the core wrapper functionality separately $className = 'TestDocumentAPI'; $namespace = 'api/v1'; $route = '/documents/(?P\\d+)'; // Create wrapper like WpRestDiscovery does - $wrapper = new class($className, $namespace, $route, null) implements Attributable + $wrapper = new class($className, $namespace, $route) implements Attributable { private mixed $realInstance = null; diff --git a/tests/Unit/helpers.php b/tests/Unit/helpers.php index 8c1a3bc5..a6c3601f 100644 --- a/tests/Unit/helpers.php +++ b/tests/Unit/helpers.php @@ -17,7 +17,7 @@ class WP /** * Setup WordPress mock functions for tests */ -function setupWordPressMocks() +function setupWordPressMocks(): void { // Initialize WP::$wpFunctions if not already set if (! isset(WP::$wpFunctions) || ! WP::$wpFunctions) { @@ -26,20 +26,7 @@ function setupWordPressMocks() // Mock WordPress hook functions with specific handlers for common filters WP::$wpFunctions->shouldReceive('add_filter') - ->withArgs(function ($hook, $callback, $priority = 10, $accepted_args = 1) { - // Allow any template_include filter to be registered - if ($hook === 'template_include') { - return true; - } - - // Allow template_redirect action to be registered - if ($hook === 'template_redirect') { - return true; - } - - // Default behavior for other hooks - return true; - }) + ->withArgs(fn ($hook, $callback, $priority = 10, $accepted_args = 1): bool => true) ->andReturn(true) ->byDefault(); @@ -72,9 +59,7 @@ function setupWordPressMocks() // Mock WordPress option functions WP::$wpFunctions->shouldReceive('get_option') ->withAnyArgs() - ->andReturnUsing(function ($option, $default = false) { - return $default; - }) + ->andReturnUsing(fn ($option, $default = false) => $default) ->byDefault(); WP::$wpFunctions->shouldReceive('add_option') @@ -125,9 +110,7 @@ function setupWordPressMocks() WP::$wpFunctions->shouldReceive('apply_filters') ->withAnyArgs() - ->andReturnUsing(function ($tag, $value) { - return $value; - }) + ->andReturnUsing(fn ($tag, $value) => $value) ->byDefault(); // Default WordPress conditional functions behavior @@ -299,7 +282,7 @@ function setupWordPressMocks() WP::$wpFunctions->shouldReceive('get_queried_object') ->withAnyArgs() - ->andReturnUsing(function () { + ->andReturnUsing(function (): stdClass { $obj = new stdClass; $obj->post_type = 'page'; $obj->post_name = 'test-page'; @@ -311,7 +294,7 @@ function setupWordPressMocks() WP::$wpFunctions->shouldReceive('get_post') ->withAnyArgs() - ->andReturnUsing(function () { + ->andReturnUsing(function (): stdClass { $post = new stdClass; $post->post_name = 'parent-page'; $post->post_parent = 0; @@ -322,16 +305,14 @@ function setupWordPressMocks() WP::$wpFunctions->shouldReceive('get_query_var') ->withAnyArgs() - ->andReturnUsing(function ($var) { - return $var === 'post_type' ? 'page' : ''; - }) + ->andReturnUsing(fn ($var): string => $var === 'post_type' ? 'page' : '') ->byDefault(); } /** * Convenience function to set mock WordPress condition values */ -function setWordPressConditions(array $conditions = []) +function setWordPressConditions(array $conditions = []): void { // Make sure WP::$wpFunctions is initialized if (! isset(WP::$wpFunctions) || ! WP::$wpFunctions) { @@ -374,11 +355,8 @@ function app($abstract = null, array $parameters = []) if (! function_exists('app_path')) { /** * Get the path to the application folder. - * - * @param string $path - * @return string */ - function app_path($path = '') + function app_path(?string $path = ''): string { return __DIR__.'/../../app/'.($path ? DIRECTORY_SEPARATOR.$path : $path); } @@ -387,11 +365,8 @@ function app_path($path = '') if (! function_exists('config_path')) { /** * Get the path to the config folder. - * - * @param string $path - * @return string */ - function config_path($path = '') + function config_path(?string $path = ''): string { return __DIR__.'/../../config/'.($path ? DIRECTORY_SEPARATOR.$path : $path); } @@ -400,11 +375,8 @@ function config_path($path = '') if (! function_exists('base_path')) { /** * Get the path to the base of the install. - * - * @param string $path - * @return string */ - function base_path($path = '') + function base_path(?string $path = ''): string { return __DIR__.'/../..'.($path ? DIRECTORY_SEPARATOR.$path : $path); } @@ -553,14 +525,14 @@ function wp_is_block_theme() } if (! function_exists('__return_true')) { - function __return_true() + function __return_true(): bool { return true; } } if (! function_exists('__return_false')) { - function __return_false() + function __return_false(): bool { return false; } @@ -570,7 +542,7 @@ function __return_false() * Helper function for route condition testing */ if (! function_exists('route_condition_test')) { - function route_condition_test($param = null) + function route_condition_test($param = null): bool { return false; // Default implementation, will be mocked in tests } @@ -610,7 +582,7 @@ function translate_with_gettext_context($text, $context, $domain = null) } if (! function_exists('abort')) { - function abort($code, $message = '') + function abort($code, $message = ''): void { throw new HttpException($code, $message); } @@ -991,14 +963,14 @@ function translate($text, $domain = 'default') if (! function_exists('_cleanup_header_comment')) { function _cleanup_header_comment($str) { - return isset(WP::$wpFunctions) ? WP::$wpFunctions->_cleanup_header_comment($str) : trim($str); + return isset(WP::$wpFunctions) ? WP::$wpFunctions->_cleanup_header_comment($str) : trim((string) $str); } } if (! function_exists('sanitize_key')) { function sanitize_key($key) { - return isset(WP::$wpFunctions) ? WP::$wpFunctions->sanitize_key($key) : strtolower(trim($key)); + return isset(WP::$wpFunctions) ? WP::$wpFunctions->sanitize_key($key) : strtolower(trim((string) $key)); } } @@ -1040,12 +1012,7 @@ public function add($hook, $callback): void if (! class_exists('TestContainer')) { class TestContainer { - private array $services; - - public function __construct(array $services = []) - { - $this->services = $services; - } + public function __construct(private array $services = []) {} public function get(string $serviceClass): ?object { @@ -1053,7 +1020,7 @@ public function get(string $serviceClass): ?object } // Added for compatibility with attribute tests - public function make($abstract, array $parameters = []) + public function make(string $abstract, array $parameters = []): ?object { return $this->get($abstract); } @@ -1068,7 +1035,7 @@ public function has(string $serviceClass): bool return isset($this->services[$serviceClass]); } - public function instance($abstract, $instance) + public function instance($abstract, $instance): void { $this->services[$abstract] = $instance; } @@ -1103,7 +1070,7 @@ public function __construct() // Laravel response helper function if (! function_exists('response')) { - function response($content = '', $status = 200, array $headers = []) + function response($content = '', $status = 200, array $headers = []): Response { return new Response($content, $status, $headers); } @@ -1293,7 +1260,7 @@ function check_ajax_referer($action = -1, $query_arg = false, $stop = true) if (! function_exists('sanitize_text_field')) { function sanitize_text_field($str) { - return isset(WP::$wpFunctions) ? WP::$wpFunctions->sanitize_text_field($str) : trim(strip_tags($str)); + return isset(WP::$wpFunctions) ? WP::$wpFunctions->sanitize_text_field($str) : trim(strip_tags((string) $str)); } } @@ -1306,14 +1273,14 @@ function wp_die($message = '', $title = '', $args = []) // WordPress escaping functions if (! function_exists('esc_attr')) { - function esc_attr($text) + function esc_attr($text): string { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); } } if (! function_exists('esc_html')) { - function esc_html($text) + function esc_html($text): string { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); }