diff --git a/tests/Casts/MetaValueTest.php b/tests/Casts/MetaValueTest.php new file mode 100644 index 00000000..c4550722 --- /dev/null +++ b/tests/Casts/MetaValueTest.php @@ -0,0 +1,94 @@ +cast = new MetaValue(); + + $this->model = new class extends Model + { + protected $guarded = []; + }; + } + + public function test_it_returns_null_when_value_is_null(): void + { + $result = $this->cast->get($this->model, 'test_key', null, []); + + $this->assertNull($result); + } + + public function test_it_decodes_json_value(): void + { + $jsonValue = json_encode(['foo' => 'bar', 'baz' => 'qux']); + + $result = $this->cast->get($this->model, 'test_key', $jsonValue, []); + + $this->assertSame(['foo' => 'bar', 'baz' => 'qux'], $result); + } + + public function test_it_returns_string_value_when_json_decode_fails(): void + { + $result = $this->cast->get($this->model, 'test_key', 'plain string', []); + + $this->assertSame('plain string', $result); + } + + public function test_it_returns_null_for_storage_when_value_is_null(): void + { + $result = $this->cast->set($this->model, 'test_key', null, []); + + $this->assertNull($result); + } + + public function test_it_returns_string_value_for_storage(): void + { + $result = $this->cast->set($this->model, 'test_key', 'test value', []); + + $this->assertSame('test value', $result); + } + + public function test_it_returns_numeric_value_as_string_for_storage(): void + { + $result = $this->cast->set($this->model, 'test_key', 123, []); + + $this->assertSame('123', $result); + } + + public function test_it_encodes_array_to_json_for_storage(): void + { + $result = $this->cast->set($this->model, 'test_key', ['foo' => 'bar'], []); + + $this->assertSame('{"foo":"bar"}', $result); + } + + public function test_it_converts_stringable_object_for_storage(): void + { + $stringable = new class + { + public function __toString(): string + { + return 'stringable value'; + } + }; + + $result = $this->cast->set($this->model, 'test_key', $stringable, []); + + $this->assertSame('stringable value', $result); + } +} diff --git a/tests/Enums/ArrayableTest.php b/tests/Enums/ArrayableTest.php new file mode 100644 index 00000000..66b5d7fa --- /dev/null +++ b/tests/Enums/ArrayableTest.php @@ -0,0 +1,40 @@ +assertSame([ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'pending' => 'Pending', + ], $array); + } +} + +enum TestEnum: string +{ + use Arrayable; + + case ACTIVE = 'active'; + case INACTIVE = 'inactive'; + case PENDING = 'pending'; + + public function label(): string + { + return match ($this) { + self::ACTIVE => 'Active', + self::INACTIVE => 'Inactive', + self::PENDING => 'Pending', + }; + } +} diff --git a/tests/Exceptions/ExceptionsTest.php b/tests/Exceptions/ExceptionsTest.php new file mode 100644 index 00000000..b1123f23 --- /dev/null +++ b/tests/Exceptions/ExceptionsTest.php @@ -0,0 +1,59 @@ +expectException(QueryResolutionException::class); + $this->expectExceptionMessage('Test message'); + + throw new QueryResolutionException('Test message'); + } + + public function test_query_resolution_exception_extends_exception(): void + { + $exception = new QueryResolutionException('Test'); + + $this->assertInstanceOf(Exception::class, $exception); + } + + public function test_resource_resolution_exception_can_be_thrown(): void + { + $this->expectException(ResourceResolutionException::class); + $this->expectExceptionMessage('Test message'); + + throw new ResourceResolutionException('Test message'); + } + + public function test_resource_resolution_exception_extends_exception(): void + { + $exception = new ResourceResolutionException('Test'); + + $this->assertInstanceOf(Exception::class, $exception); + } + + public function test_save_form_data_exception_can_be_thrown(): void + { + $this->expectException(SaveFormDataException::class); + $this->expectExceptionMessage('Test message'); + + throw new SaveFormDataException('Test message'); + } + + public function test_save_form_data_exception_extends_exception(): void + { + $exception = new SaveFormDataException('Test'); + + $this->assertInstanceOf(Exception::class, $exception); + } +} diff --git a/tests/Jobs/MoveFileTest.php b/tests/Jobs/MoveFileTest.php new file mode 100644 index 00000000..50df4757 --- /dev/null +++ b/tests/Jobs/MoveFileTest.php @@ -0,0 +1,88 @@ +make(); + $path = '/tmp/test-file.jpg'; + + MoveFile::dispatch($medium, $path, false); + + Queue::assertPushed(MoveFile::class); + } + + public function test_move_file_job_moves_file_to_storage(): void + { + Storage::fake('public'); + + $file = UploadedFile::fake()->image('test.jpg'); + $tempPath = $file->getRealPath(); + + $medium = Medium::factory()->make([ + 'disk' => 'public', + 'path' => 'media/test.jpg', + ]); + + $job = new MoveFile($medium, $tempPath, true); + $job->handle(); + + Storage::disk('public')->assertExists('media/test.jpg'); + } + + public function test_move_file_job_deletes_original_when_not_preserved(): void + { + Storage::fake('public'); + + $tempDir = Storage::disk('local')->path('root-tmp'); + $tempPath = $tempDir.'/test-file.jpg'; + File::ensureDirectoryExists($tempDir); + File::put($tempPath, 'test content'); + + $medium = Medium::factory()->make([ + 'disk' => 'public', + 'path' => 'media/test.jpg', + ]); + + $job = new MoveFile($medium, $tempPath, false); + $job->handle(); + + Storage::disk('public')->assertExists('media/test.jpg'); + $this->assertFalse(File::exists($tempPath)); + } + + public function test_move_file_job_preserves_original_when_preserve_is_true(): void + { + Storage::fake('public'); + + $tempDir = Storage::disk('local')->path('root-tmp'); + $tempPath = $tempDir.'/test-file.jpg'; + File::ensureDirectoryExists($tempDir); + File::put($tempPath, 'test content'); + + $medium = Medium::factory()->make([ + 'disk' => 'public', + 'path' => 'media/test.jpg', + ]); + + $job = new MoveFile($medium, $tempPath, true); + $job->handle(); + + Storage::disk('public')->assertExists('media/test.jpg'); + $this->assertTrue(File::exists($tempPath)); + } +} diff --git a/tests/Jobs/PerformConversionsTest.php b/tests/Jobs/PerformConversionsTest.php new file mode 100644 index 00000000..6df13369 --- /dev/null +++ b/tests/Jobs/PerformConversionsTest.php @@ -0,0 +1,33 @@ +make(); + + PerformConversions::dispatch($medium); + + Queue::assertPushed(PerformConversions::class); + } + + public function test_perform_conversions_job_calls_convert_on_medium(): void + { + $medium = $this->createMock(Medium::class); + $medium->expects($this->once())->method('convert'); + + $job = new PerformConversions($medium); + $job->handle(); + } +} diff --git a/tests/Listeners/FormatRootStubsTest.php b/tests/Listeners/FormatRootStubsTest.php new file mode 100644 index 00000000..868d85a4 --- /dev/null +++ b/tests/Listeners/FormatRootStubsTest.php @@ -0,0 +1,72 @@ +handle($event); + + $contents = file_get_contents($tempFile); + + $this->assertStringContainsString(App::getNamespace(), $contents); + $this->assertStringNotContainsString('{{ namespace }}', $contents); + + unlink($tempFile); + } + + public function test_listener_ignores_non_root_stubs_tag(): void + { + $tempFile = tempnam(sys_get_temp_dir(), 'stub_'); + file_put_contents($tempFile, 'handle($event); + + $contents = file_get_contents($tempFile); + + $this->assertStringContainsString('{{ namespace }}', $contents); + + unlink($tempFile); + } + + public function test_listener_handles_multiple_files(): void + { + $tempFile1 = tempnam(sys_get_temp_dir(), 'stub_'); + $tempFile2 = tempnam(sys_get_temp_dir(), 'stub_'); + + file_put_contents($tempFile1, 'handle($event); + + $contents1 = file_get_contents($tempFile1); + $contents2 = file_get_contents($tempFile2); + + $this->assertStringNotContainsString('{{ namespace }}', $contents1); + $this->assertStringNotContainsString('{{ namespace }}', $contents2); + + unlink($tempFile1); + unlink($tempFile2); + } +} diff --git a/tests/Notifications/RootChannelTest.php b/tests/Notifications/RootChannelTest.php new file mode 100644 index 00000000..f6e9cd34 --- /dev/null +++ b/tests/Notifications/RootChannelTest.php @@ -0,0 +1,56 @@ +create(); + + $notification = new TestRootNotification(); + + (new RootChannel())->send($user, $notification); + + $this->assertDatabaseHas('root_notifications', [ + 'notifiable_type' => User::class, + 'notifiable_id' => $user->id, + 'subject' => 'Test Subject', + 'message' => 'Test Message', + ]); + } + + public function test_root_channel_includes_notification_type(): void + { + $user = User::factory()->create(); + + $notification = new TestRootNotification(); + + (new RootChannel())->send($user, $notification); + + $this->assertDatabaseHas('root_notifications', [ + 'type' => TestRootNotification::class, + ]); + } +} + +class TestRootNotification extends RootNotification +{ + public function toRoot(object $notifiable): RootMessage + { + return (new RootMessage()) + ->subject('Test Subject') + ->message('Test Message'); + } +} diff --git a/tests/Notifications/RootMessageTest.php b/tests/Notifications/RootMessageTest.php new file mode 100644 index 00000000..a856c905 --- /dev/null +++ b/tests/Notifications/RootMessageTest.php @@ -0,0 +1,65 @@ +assertInstanceOf(RootMessage::class, $message); + } + + public function test_root_message_can_set_subject(): void + { + $message = new RootMessage(); + $message->subject('Test Subject'); + + $this->assertSame('Test Subject', $message->toArray()['subject']); + } + + public function test_root_message_can_set_message(): void + { + $message = new RootMessage(); + $message->message('Test Message'); + + $this->assertSame('Test Message', $message->toArray()['message']); + } + + public function test_root_message_can_set_data(): void + { + $message = new RootMessage(); + $message->data(['key' => 'value']); + + $this->assertSame(['key' => 'value'], $message->toArray()['data']); + } + + public function test_root_message_can_be_converted_to_array(): void + { + $message = new RootMessage('Subject', 'Message'); + $message->data(['foo' => 'bar']); + + $this->assertSame([ + 'subject' => 'Subject', + 'message' => 'Message', + 'data' => ['foo' => 'bar'], + ], $message->toArray()); + } + + public function test_root_message_methods_are_fluent(): void + { + $message = new RootMessage(); + + $result = $message->subject('Test') + ->message('Message') + ->data(['key' => 'value']); + + $this->assertSame($message, $result); + } +} diff --git a/tests/Policies/MediumPolicyTest.php b/tests/Policies/MediumPolicyTest.php new file mode 100644 index 00000000..fde2c988 --- /dev/null +++ b/tests/Policies/MediumPolicyTest.php @@ -0,0 +1,63 @@ +policy = new MediumPolicy(); + $this->user = User::factory()->create(); + $this->medium = Medium::factory()->create(); + } + + public function test_user_can_view_any_media(): void + { + $this->assertTrue($this->policy->viewAny($this->user)); + } + + public function test_user_can_view_medium(): void + { + $this->assertTrue($this->policy->view($this->user, $this->medium)); + } + + public function test_user_can_create_medium(): void + { + $this->assertTrue($this->policy->create($this->user)); + } + + public function test_user_can_update_medium(): void + { + $this->assertTrue($this->policy->update($this->user, $this->medium)); + } + + public function test_user_can_delete_medium(): void + { + $this->assertTrue($this->policy->delete($this->user, $this->medium)); + } + + public function test_user_can_restore_medium(): void + { + $this->assertTrue($this->policy->restore($this->user, $this->medium)); + } + + public function test_user_can_force_delete_medium(): void + { + $this->assertTrue($this->policy->forceDelete($this->user, $this->medium)); + } +} diff --git a/tests/Support/AlertTest.php b/tests/Support/AlertTest.php new file mode 100644 index 00000000..08553ebd --- /dev/null +++ b/tests/Support/AlertTest.php @@ -0,0 +1,67 @@ +assertSame(['message' => 'Test message', 'type' => Alert::INFO], $alert->toArray()); + } + + public function test_an_alert_can_be_info(): void + { + $alert = Alert::info('Info message'); + + $this->assertSame(['message' => 'Info message', 'type' => Alert::INFO], $alert); + } + + public function test_an_alert_can_be_success(): void + { + $alert = Alert::success('Success message'); + + $this->assertSame(['message' => 'Success message', 'type' => Alert::SUCCESS], $alert); + } + + public function test_an_alert_can_be_error(): void + { + $alert = Alert::error('Error message'); + + $this->assertSame(['message' => 'Error message', 'type' => Alert::ERROR], $alert); + } + + public function test_an_alert_can_be_warning(): void + { + $alert = Alert::warning('Warning message'); + + $this->assertSame(['message' => 'Warning message', 'type' => Alert::WARNING], $alert); + } + + public function test_an_alert_can_be_converted_to_json(): void + { + $alert = new Alert('Test message', Alert::INFO); + + $this->assertSame('{"message":"Test message","type":"info"}', $alert->toJson()); + } + + public function test_an_alert_can_be_converted_to_html(): void + { + $alert = new Alert('Test message', Alert::INFO); + + $this->assertSame('Test message', $alert->toHtml()); + } + + public function test_an_alert_can_be_converted_to_string(): void + { + $alert = new Alert('Test message', Alert::INFO); + + $this->assertSame('Test message', (string) $alert); + } +} diff --git a/tests/Support/ClassListTest.php b/tests/Support/ClassListTest.php new file mode 100644 index 00000000..8cbc4779 --- /dev/null +++ b/tests/Support/ClassListTest.php @@ -0,0 +1,103 @@ +assertSame(['foo', 'bar'], $classList->toArray()); + } + + public function test_a_class_list_can_add_classes(): void + { + $classList = new ClassList(); + + $classList->add('foo'); + $this->assertSame(['foo'], $classList->toArray()); + + $classList->add('bar baz'); + $this->assertSame(['foo', 'bar', 'baz'], $classList->toArray()); + + $classList->add(['qux', 'quux']); + $this->assertSame(['foo', 'bar', 'baz', 'qux', 'quux'], $classList->toArray()); + } + + public function test_a_class_list_removes_duplicates_when_adding(): void + { + $classList = new ClassList(['foo', 'bar']); + + $classList->add('foo'); + $this->assertSame(['foo', 'bar'], $classList->toArray()); + } + + public function test_a_class_list_can_remove_classes(): void + { + $classList = new ClassList(['foo', 'bar', 'baz']); + + $classList->remove('bar'); + $this->assertSame(['foo', 'baz'], $classList->toArray()); + + $classList->remove(['foo', 'baz']); + $this->assertSame([], $classList->toArray()); + } + + public function test_a_class_list_can_replace_classes(): void + { + $classList = new ClassList(['foo', 'bar', 'baz']); + + $classList->replace('bar', 'qux'); + $this->assertSame(['foo', 'qux', 'baz'], $classList->toArray()); + + $classList->replace('nonexistent', 'test'); + $this->assertSame(['foo', 'qux', 'baz'], $classList->toArray()); + } + + public function test_a_class_list_can_toggle_classes(): void + { + $classList = new ClassList(['foo', 'bar']); + + $classList->toggle('foo'); + $this->assertSame(['bar'], $classList->toArray()); + + $classList->toggle('foo'); + $this->assertSame(['bar', 'foo'], $classList->toArray()); + + $classList->toggle('baz', true); + $this->assertSame(['bar', 'foo', 'baz'], $classList->toArray()); + + $classList->toggle('bar', false); + $this->assertSame(['foo', 'baz'], $classList->toArray()); + } + + public function test_a_class_list_can_check_if_contains_class(): void + { + $classList = new ClassList(['foo', 'bar']); + + $this->assertTrue($classList->contains('foo')); + $this->assertTrue($classList->contains('bar')); + $this->assertFalse($classList->contains('baz')); + } + + public function test_a_class_list_can_be_cleared(): void + { + $classList = new ClassList(['foo', 'bar', 'baz']); + + $classList->clear(); + $this->assertSame([], $classList->toArray()); + } + + public function test_a_class_list_can_be_converted_to_string(): void + { + $classList = new ClassList(['foo', 'bar', 'baz']); + + $this->assertSame('foo bar baz', (string) $classList); + } +} diff --git a/tests/Support/CopyableTest.php b/tests/Support/CopyableTest.php new file mode 100644 index 00000000..bd305294 --- /dev/null +++ b/tests/Support/CopyableTest.php @@ -0,0 +1,62 @@ +assertInstanceOf(Copyable::class, $copyable); + } + + public function test_a_copyable_can_be_made_with_same_text_and_value(): void + { + $copyable = Copyable::make('Test'); + + $this->assertInstanceOf(Copyable::class, $copyable); + } + + public function test_a_copyable_can_be_made_with_different_text_and_value(): void + { + $copyable = Copyable::make('Display', 'Copy Value'); + + $this->assertInstanceOf(Copyable::class, $copyable); + } + + public function test_a_copyable_can_be_rendered(): void + { + $copyable = Copyable::make('Test Text', 'Test Value'); + + $rendered = $copyable->render(); + + $this->assertStringContainsString('Test Text', $rendered); + $this->assertStringContainsString('Test Value', $rendered); + } + + public function test_a_copyable_can_be_converted_to_html(): void + { + $copyable = Copyable::make('Test Text', 'Test Value'); + + $html = $copyable->toHtml(); + + $this->assertStringContainsString('Test Text', $html); + $this->assertStringContainsString('Test Value', $html); + } + + public function test_a_copyable_can_be_converted_to_string(): void + { + $copyable = Copyable::make('Test Text', 'Test Value'); + + $string = (string) $copyable; + + $this->assertStringContainsString('Test Text', $string); + $this->assertStringContainsString('Test Value', $string); + } +} diff --git a/tests/Traits/MakeableTest.php b/tests/Traits/MakeableTest.php new file mode 100644 index 00000000..9cdc31c5 --- /dev/null +++ b/tests/Traits/MakeableTest.php @@ -0,0 +1,47 @@ +assertInstanceOf(TestMakeable::class, $instance); + $this->assertSame('param1', $instance->param1); + $this->assertSame('param2', $instance->param2); + } + + public function test_makeable_works_without_parameters(): void + { + $instance = TestMakeableNoParams::make(); + + $this->assertInstanceOf(TestMakeableNoParams::class, $instance); + } +} + +class TestMakeable +{ + use Makeable; + + public function __construct( + public string $param1, + public string $param2 + ) { + } +} + +class TestMakeableNoParams +{ + use Makeable; + + public function __construct() + { + } +} diff --git a/tests/View/Components/AlertComponentTest.php b/tests/View/Components/AlertComponentTest.php new file mode 100644 index 00000000..b9466c60 --- /dev/null +++ b/tests/View/Components/AlertComponentTest.php @@ -0,0 +1,58 @@ +assertInstanceOf(AlertComponent::class, $component); + } + + public function test_alert_component_renders(): void + { + $component = new AlertComponent(); + + $view = $component->render(); + + $this->assertSame('root::components.alert', $view->name()); + $this->assertSame('info', $view->getData()['type']); + $this->assertFalse($view->getData()['closable']); + } + + public function test_alert_component_accepts_type(): void + { + $component = new AlertComponent('success'); + + $view = $component->render(); + + $this->assertSame('success', $view->getData()['type']); + $this->assertSame('alert--success', $view->getData()['class']); + } + + public function test_alert_component_handles_error_type(): void + { + $component = new AlertComponent('error'); + + $view = $component->render(); + + $this->assertSame('error', $view->getData()['type']); + $this->assertSame('alert--danger', $view->getData()['class']); + } + + public function test_alert_component_can_be_closable(): void + { + $component = new AlertComponent('info', true); + + $view = $component->render(); + + $this->assertTrue($view->getData()['closable']); + } +} diff --git a/tests/View/Components/ChartTest.php b/tests/View/Components/ChartTest.php new file mode 100644 index 00000000..39375a17 --- /dev/null +++ b/tests/View/Components/ChartTest.php @@ -0,0 +1,35 @@ +render(); + + $this->assertSame('root::components.chart', $view->name()); + $this->assertSame([], $view->getData()['config']); + } + + public function test_chart_component_accepts_config(): void + { + $config = [ + 'type' => 'bar', + 'data' => ['values' => [1, 2, 3]], + ]; + + $component = new Chart($config); + + $view = $component->render(); + + $this->assertSame($config, $view->getData()['config']); + } +} diff --git a/tests/View/Components/CopyableComponentTest.php b/tests/View/Components/CopyableComponentTest.php new file mode 100644 index 00000000..83340402 --- /dev/null +++ b/tests/View/Components/CopyableComponentTest.php @@ -0,0 +1,22 @@ +render(); + + $this->assertSame('root::components.copyable', $view->name()); + $this->assertSame('Display Text', $view->getData()['text']); + $this->assertSame('Copy Value', $view->getData()['value']); + } +} diff --git a/tests/View/Components/IconTest.php b/tests/View/Components/IconTest.php new file mode 100644 index 00000000..1bbe78a0 --- /dev/null +++ b/tests/View/Components/IconTest.php @@ -0,0 +1,30 @@ +render(); + + $this->assertSame('root::components.icon', $view->name()); + $this->assertSame('root::icons.check', $view->getData()['icon']); + } + + public function test_icon_component_accepts_different_icons(): void + { + $component = new Icon('arrow-right'); + + $view = $component->render(); + + $this->assertSame('root::icons.arrow-right', $view->getData()['icon']); + } +} diff --git a/tests/View/Components/ModalTest.php b/tests/View/Components/ModalTest.php new file mode 100644 index 00000000..0845be5f --- /dev/null +++ b/tests/View/Components/ModalTest.php @@ -0,0 +1,76 @@ +render(); + + $this->assertSame('root::components.modal', $view->name()); + $this->assertSame('Test Title', $view->getData()['title']); + } + + public function test_modal_component_accepts_subtitle(): void + { + $component = new Modal('Test Title', 'Test Subtitle'); + + $view = $component->render(); + + $this->assertSame('Test Subtitle', $view->getData()['subtitle']); + } + + public function test_modal_component_generates_key_when_not_provided(): void + { + $component = new Modal('Test Title'); + + $view = $component->render(); + + $this->assertNotEmpty($view->getData()['key']); + $this->assertIsString($view->getData()['key']); + } + + public function test_modal_component_uses_provided_key(): void + { + $component = new Modal('Test Title', null, 'custom-key'); + + $view = $component->render(); + + $this->assertSame('custom-key', $view->getData()['key']); + } + + public function test_modal_component_normalizes_key_to_lowercase(): void + { + $component = new Modal('Test Title', null, 'CUSTOM-KEY'); + + $view = $component->render(); + + $this->assertSame('custom-key', $view->getData()['key']); + } + + public function test_modal_component_defaults_to_closed(): void + { + $component = new Modal('Test Title'); + + $view = $component->render(); + + $this->assertFalse($view->getData()['open']); + } + + public function test_modal_component_can_be_opened(): void + { + $component = new Modal('Test Title', null, null, true); + + $view = $component->render(); + + $this->assertTrue($view->getData()['open']); + } +}