diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e8da656..80d02e8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -58,7 +58,7 @@ jobs: run: vendor/bin/pint --test - name: Execute tests - run: vendor/bin/pest --ci --coverage --min=0 + run: vendor/bin/pest --ci --coverage --min=96 - name: Upload coverage to Codecov if: matrix.os == 'ubuntu-latest' && matrix.php == '8.4' && matrix.laravel == '13.*' && matrix.stability == 'prefer-stable' diff --git a/composer.json b/composer.json index 6e5b88e..7971866 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", "analyse": "vendor/bin/phpstan analyse", "test": "vendor/bin/pest", - "test-coverage": "vendor/bin/pest --coverage", + "test-coverage": "vendor/bin/pest --coverage --min=96", "format": "vendor/bin/pint" }, "config": { diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stubs/flatpickr-field.stub b/stubs/flatpickr-field.stub new file mode 100644 index 0000000..6b9d03c --- /dev/null +++ b/stubs/flatpickr-field.stub @@ -0,0 +1,3 @@ +{{ namespace }} + +// Stub for publishing custom flatpickr field scaffolding. diff --git a/tests/FlatpickrAttributesTest.php b/tests/FlatpickrAttributesTest.php new file mode 100644 index 0000000..4e64e20 --- /dev/null +++ b/tests/FlatpickrAttributesTest.php @@ -0,0 +1,170 @@ +displayFormat('d/m/Y') + ->altInput(true) + ->altInputClass('custom-class') + ->allowInput(true) + ->allowInvalidPreload(true) + ->appendTo('#app') + ->ariaDateFormat('F j, Y') + ->conjunction('|') + ->rangeSeparator(' - ') + ->clickOpens(false) + ->format('Y-m-d') + ->defaultDate('2024-01-01') + ->defaultHour(9) + ->defaultMinute(30) + ->disableDates(['2024-12-25']) + ->disableMobile(true) + ->enableDates(['2024-01-01']) + ->time(true) + ->seconds(true) + ->hourIncrement(2) + ->minuteIncrement(15) + ->mode(FlatpickrMode::SINGLE) + ->position(FlatpickrPosition::ABOVE) + ->prevArrow('<') + ->nextArrow('>') + ->shorthandCurrentMonth(true) + ->showMonths(2) + ->time24hr(false) + ->weekNumbers(true) + ->monthSelectorType(FlatpickrMonthSelectorType::STATIC_SELECTOR) + ->inline(true) + ->locale('en') + ->minDate('2024-01-01') + ->maxDate('2024-12-31'); + + $attributes = $component->getFlatpickrAttributes(); + + expect($attributes)->toMatchArray([ + 'altFormat' => 'd/m/Y', + 'altInput' => true, + 'altInputClass' => 'custom-class', + 'allowInput' => true, + 'allowInvalidPreload' => true, + 'appendTo' => '#app', + 'ariaDateFormat' => 'F j, Y', + 'conjunction' => '|', + 'rangeSeparator' => ' - ', + 'clickOpens' => false, + 'dateFormat' => 'Y-m-d', + 'defaultDate' => '2024-01-01', + 'defaultHour' => 9, + 'defaultMinute' => 30, + 'disable' => ['2024-12-25'], + 'disableMobile' => true, + 'enable' => ['2024-01-01'], + 'enableTime' => true, + 'enableSeconds' => true, + 'hourIncrement' => 2, + 'minuteIncrement' => 15, + 'mode' => FlatpickrMode::SINGLE->value, + 'position' => FlatpickrPosition::ABOVE->value, + 'prevArrow' => '<', + 'nextArrow' => '>', + 'shorthandCurrentMonth' => true, + 'showMonths' => 2, + 'time_24hr' => false, + 'weekNumbers' => true, + 'monthSelectorType' => FlatpickrMonthSelectorType::STATIC_SELECTOR->value, + 'inline' => true, + 'locale' => 'en', + 'minDate' => '2024-01-01', + 'maxDate' => '2024-12-31', + ]); +}); + +it('maps year picker attributes including alt format fallback', function () { + $component = Flatpickr::make('year')->yearPicker()->displayFormat('Y'); + + expect($component->getFlatpickrAttributes())->toMatchArray([ + 'yearPicker' => true, + 'dateFormat' => 'Y', + 'altFormat' => 'Y', + ]); +}); + +it('maps time picker attributes when format still contains date tokens', function () { + $component = Flatpickr::make('start_time')->timePicker()->format('Y-m-d H:i'); + + expect($component->getFlatpickrAttributes())->toMatchArray([ + 'timePicker' => true, + 'noCalendar' => true, + 'enableTime' => true, + 'dateFormat' => 'H:i', + 'altFormat' => 'h:i K', + ]); +}); + +it('maps time picker attributes with seconds when format still contains date tokens', function () { + $component = Flatpickr::make('start_time')->timePicker()->seconds()->format('Y-m-d H:i:s'); + + expect($component->getFlatpickrAttributes())->toMatchArray([ + 'dateFormat' => 'H:i:S', + 'altFormat' => 'h:i:S K', + ]); +}); + +it('exposes theme asset helpers from config and enum cases', function () { + config(['flatpickr.theme' => FlatpickrTheme::DARK]); + + $component = Flatpickr::make('date'); + + expect($component->getThemeAsset())->toContain('dark.css') + ->and($component->getLightThemeAsset())->toContain('light.css') + ->and($component->getDarkThemeAsset())->toContain('dark.css'); +}); + +it('resolves min and max dates from carbon instances', function () { + $component = Flatpickr::make('date') + ->minDate(Carbon::parse('2024-01-01')) + ->maxDate(Carbon::parse('2024-12-31')); + + expect($component->getMinDate())->toBe('2024-01-01') + ->and($component->getMaxDate())->toBe('2024-12-31'); +}); + +it('supports fluent aliases for format and time configuration', function () { + $component = Flatpickr::make('date') + ->altFormat('d/m/Y') + ->dateFormat('Y-m-d') + ->enableTime(true) + ->enableSeconds(true); + + expect($component->getAltFormat())->toBe('d/m/Y') + ->and($component->getFormat())->toBe('Y-m-d') + ->and($component->getEnableTime())->toBeTrue() + ->and($component->getEnableSeconds())->toBeTrue(); +}); + +it('defaults format for datetime-only fields', function () { + $component = Flatpickr::make('published_at')->date(false)->time(true); + + expect($component->getFormat())->toBe('H:i'); +}); + +it('defaults format for datetime fields with seconds', function () { + $component = Flatpickr::make('published_at')->time(true)->seconds(); + + expect($component->getFormat())->toBe('Y-m-d H:i:s'); +}); + +it('uses the configured timezone with app fallback', function () { + config(['app.timezone' => 'UTC']); + + $component = Flatpickr::make('date')->timezone('Europe/Paris'); + + expect($component->getTimezone())->toBe('Europe/Paris') + ->and(Flatpickr::make('date')->getTimezone())->toBe('UTC'); +}); diff --git a/tests/FlatpickrCommandTest.php b/tests/FlatpickrCommandTest.php new file mode 100644 index 0000000..c1458bb --- /dev/null +++ b/tests/FlatpickrCommandTest.php @@ -0,0 +1,23 @@ +artisan('flatpickr') + ->expectsOutput('All done') + ->assertSuccessful(); +}); + +it('runs the flatpickr install command', function () { + $this->artisan('flatpickr:install') + ->expectsConfirmation('Do you want to overwrite the existing package assets if any?', 'no') + ->expectsConfirmation('Do you want to overwrite the package config file if existing?', 'no') + ->expectsConfirmation('Would you like to star our repo on GitHub?', 'no') + ->assertSuccessful(); +}); + +it('runs the flatpickr install command with forced publishing', function () { + $this->artisan('flatpickr:install') + ->expectsConfirmation('Do you want to overwrite the existing package assets if any?', 'yes') + ->expectsConfirmation('Do you want to overwrite the package config file if existing?', 'yes') + ->expectsConfirmation('Would you like to star our repo on GitHub?', 'no') + ->assertSuccessful(); +}); diff --git a/tests/FlatpickrDehydrationExtendedTest.php b/tests/FlatpickrDehydrationExtendedTest.php new file mode 100644 index 0000000..c0e887c --- /dev/null +++ b/tests/FlatpickrDehydrationExtendedTest.php @@ -0,0 +1,128 @@ +setValue($component, $native); + + return $component; +} + +it('returns an empty array when a single date value cannot be parsed', function () { + $component = Flatpickr::make('date')->format('Y-m-d'); + + expect(Flatpickr::dehydrateFlatpickr($component, 'definitely-not-a-date'))->toBe([]); +}); + +it('returns null when dehydrating unsupported state types', function () { + $component = Flatpickr::make('dates')->multiplePicker()->format('Y-m-d'); + + expect(Flatpickr::dehydrateFlatpickr($component, false))->toBeNull(); +}); + +it('dehydrates native range values as formatted date strings', function () { + $component = setFlatpickrNativeDehydration( + StatefulFlatpickr::make('range')->rangePicker()->format('Y-m-d'), + ); + + $result = Flatpickr::dehydrateFlatpickr($component, '2024-06-01 to 2024-06-15'); + + expect($result)->toBe(['2024-06-01', '2024-06-15']); +}); + +it('dehydrates native multiple values as formatted date strings', function () { + $component = setFlatpickrNativeDehydration( + StatefulFlatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction(','), + ); + + $result = Flatpickr::dehydrateFlatpickr($component, '2024-06-01,2024-06-15'); + + expect($result)->toBe(['2024-06-01', '2024-06-15']); +}); + +it('dehydrates native time-only range values with minute precision', function () { + $component = setFlatpickrNativeDehydration( + StatefulFlatpickr::make('times') + ->multiplePicker() + ->date(false) + ->time(true) + ->format('H:i'), + ); + + $result = Flatpickr::dehydrateFlatpickr($component, '08:30,09:15'); + + expect($result)->toBe(['08:30', '09:15']); +}); + +it('splits range strings using regex matches for custom formats', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('d/m/Y') + ->rangeSeparator('invalid'); + + $result = Flatpickr::dehydrateFlatpickr($component, '01/06/202401/06/2024'); + + expect($result)->toBe(['01/06/2024', '01/06/2024']); +}); + +it('falls back to returning the original range string when it cannot be split', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('Y-m-d') + ->rangeSeparator('invalid'); + + $method = new ReflectionMethod(Flatpickr::class, 'splitRangeString'); + $parts = $method->invoke(null, 'not-a-range', $component); + + expect($parts)->toBe(['not-a-range']); +}); + +it('parses carbon instances passed directly into dehydration', function () { + $component = Flatpickr::make('date')->format('Y-m-d'); + + $result = Flatpickr::dehydrateFlatpickr( + $component, + Carbon::createFromFormat('Y-m-d', '2024-06-15', config('app.timezone')), + ); + + expect($result)->toBeInstanceOf(CarbonInterface::class); +}); + +it('dehydrates datetime carbon instances with minute precision when only time is enabled', function () { + $component = Flatpickr::make('published_at')->date(false)->time(true)->format('H:i'); + + $result = Flatpickr::dehydrateFlatpickr( + $component, + Carbon::createFromFormat('H:i', '14:45', config('app.timezone')), + ); + + expect($result)->toBe('14:45'); +}); + +it('dehydrates datetime carbon instances with seconds when enabled', function () { + $component = Flatpickr::make('published_at')->date(false)->time(true)->seconds()->format('H:i:s'); + + $result = Flatpickr::dehydrateFlatpickr( + $component, + Carbon::createFromFormat('H:i:s', '14:45:10', config('app.timezone')), + ); + + expect($result)->toBe('14:45:10'); +}); + +it('extracts repeated date matches from concatenated range strings', function () { + $component = Flatpickr::make('range')->rangePicker()->format('Y-m-d'); + + $method = new ReflectionMethod(Flatpickr::class, 'extractDateMatches'); + $matches = $method->invoke(null, '2024-06-012024-06-15', $component); + + expect($matches)->toBe(['2024-06-01', '2024-06-15']); +}); diff --git a/tests/FlatpickrEdgeCasesTest.php b/tests/FlatpickrEdgeCasesTest.php new file mode 100644 index 0000000..0dbe531 --- /dev/null +++ b/tests/FlatpickrEdgeCasesTest.php @@ -0,0 +1,175 @@ +setValue($component, $native); + + return $component; +} + +it('hydrates range picker values from a single string', function () { + $component = StatefulFlatpickr::make('range') + ->rangePicker() + ->format('Y-m-d') + ->rangeSeparator(' to '); + + $component->hydrateFlatpickr($component, '2024-06-01'); + + expect($component->hydratedState)->toBe('2024-06-01'); +}); + +it('hydrates native collection values for time-only fields', function () { + $component = setFlatpickrNativeCoverage( + StatefulFlatpickr::make('times') + ->multiplePicker() + ->date(false) + ->time(true) + ->format('H:i') + ->conjunction(','), + ); + + $component->hydrateFlatpickr($component, ['08:30', '09:15']); + + expect($component->hydratedState)->toBe('08:30,09:15'); +}); + +it('hydrates native collection values for datetime fields with seconds', function () { + $component = setFlatpickrNativeCoverage( + StatefulFlatpickr::make('published_at') + ->rangePicker() + ->time(true) + ->seconds() + ->format('Y-m-d H:i:s') + ->rangeSeparator(' to '), + ); + + $component->hydrateFlatpickr($component, [ + '2024-06-15 14:30:45', + '2024-06-16 10:00:00', + ]); + + expect($component->hydratedState)->toBe('2024-06-15 14:30:45 to 2024-06-16 10:00:00'); +}); + +it('hydrates native collection values for datetime fields without seconds', function () { + $component = setFlatpickrNativeCoverage( + StatefulFlatpickr::make('published_at') + ->rangePicker() + ->time(true) + ->format('Y-m-d H:i:s') + ->rangeSeparator(' to '), + ); + + $component->hydrateFlatpickr($component, [ + '2024-06-15 14:30:00', + '2024-06-16 10:00:00', + ]); + + expect($component->hydratedState)->toBe('2024-06-15 14:30 to 2024-06-16 10:00'); +}); + +it('preserves the original state when parseToCarbon throws for single values', function () { + $component = ParseThrowingFlatpickr::make('date')->format('Y-m-d'); + + $result = Flatpickr::dehydrateFlatpickr($component, '2024-06-15'); + + expect($result)->toBe('2024-06-15'); +}); + +it('dehydrates native datetime range values using datetime strings', function () { + $component = setFlatpickrNativeCoverage( + StatefulFlatpickr::make('range') + ->rangePicker() + ->time(true) + ->format('Y-m-d H:i:s') + ->rangeSeparator(' to '), + ); + + $result = Flatpickr::dehydrateFlatpickr($component, '2024-06-15 14:30:00 to 2024-06-16 10:00:00'); + + expect($result)->toBe(['2024-06-15 14:30:00', '2024-06-16 10:00:00']); +}); + +it('splits concatenated range strings using brute force when regex extraction fails', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('d/m/Y') + ->rangeSeparator('not-in-string'); + + $method = new ReflectionMethod(Flatpickr::class, 'splitRangeString'); + $parts = $method->invoke(null, '2024-06-012024-06-15', $component); + + expect($parts)->toHaveCount(2); +}); + +it('returns the configured dateFormat property value', function () { + $component = Flatpickr::make('date'); + $property = new ReflectionProperty($component, 'dateFormat'); + $property->setValue($component, 'Y-m-d'); + + expect($component->getDateFormat())->toBe('Y-m-d'); +}); + +it('validates multiple picker array values as dates', function () { + $component = Flatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make( + ['dates' => ['2024-06-01', '2024-06-15']], + ['dates' => [$closureRule]], + ); + + expect($validator->passes())->toBeTrue(); +}); + +it('validates range picker array values as dates', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make( + ['range' => ['2024-06-01', '2024-06-15']], + ['range' => [$closureRule]], + ); + + expect($validator->passes())->toBeTrue(); +}); + +it('skips multiple picker validation when the value is null', function () { + $component = Flatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make(['dates' => null], ['dates' => [$closureRule]]); + + expect($validator->passes())->toBeTrue(); +}); + +it('skips range picker validation when the value is null', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make(['range' => null], ['range' => [$closureRule]]); + + expect($validator->passes())->toBeTrue(); +}); diff --git a/tests/FlatpickrFacadeTest.php b/tests/FlatpickrFacadeTest.php new file mode 100644 index 0000000..6b8ec19 --- /dev/null +++ b/tests/FlatpickrFacadeTest.php @@ -0,0 +1,8 @@ +toBe('coolsam/flatpickr') + ->and(FilamentFlatpickr::getFacadeRoot())->toBeInstanceOf(Coolsam\Flatpickr\FilamentFlatpickr::class); +}); diff --git a/tests/FlatpickrHydrationTest.php b/tests/FlatpickrHydrationTest.php new file mode 100644 index 0000000..a9b745c --- /dev/null +++ b/tests/FlatpickrHydrationTest.php @@ -0,0 +1,147 @@ +setValue($component, $native); + + return $component; +} + +it('returns early when hydrating blank state', function () { + $component = StatefulFlatpickr::make('date')->format('Y-m-d'); + + $component->hydrateFlatpickr($component, null); + $component->hydrateFlatpickr($component, ''); + + expect($component->hydratedState)->toBeNull(); +}); + +it('hydrates a single carbon value for non-native fields', function () { + $component = StatefulFlatpickr::make('date')->format('Y-m-d'); + + $component->hydrateFlatpickr($component, Carbon::parse('2024-06-15')); + + expect($component->hydratedState)->toBe('2024-06-15'); +}); + +it('hydrates a single carbon value for native date fields', function () { + $component = setFlatpickrNative(StatefulFlatpickr::make('date')->format('Y-m-d')); + + $component->hydrateFlatpickr($component, Carbon::parse('2024-06-15')); + + expect($component->hydratedState)->toBe('2024-06-15'); +}); + +it('hydrates a single carbon value for native datetime fields', function () { + $component = setFlatpickrNative( + StatefulFlatpickr::make('published_at')->time(true)->format('Y-m-d H:i:s'), + ); + + $component->hydrateFlatpickr($component, Carbon::parse('2024-06-15 14:30:00')); + + expect($component->hydratedState)->toBe('2024-06-15 14:30'); +}); + +it('hydrates a single carbon value for native datetime fields with seconds', function () { + $component = setFlatpickrNative( + StatefulFlatpickr::make('published_at')->time(true)->seconds()->format('Y-m-d H:i:s'), + ); + + $component->hydrateFlatpickr($component, Carbon::parse('2024-06-15 14:30:45')); + + expect($component->hydratedState)->toBe('2024-06-15 14:30:45'); +}); + +it('hydrates a single carbon value for native time-only fields', function () { + $component = setFlatpickrNative( + StatefulFlatpickr::make('start_time')->date(false)->time(true)->format('H:i'), + ); + + $component->hydrateFlatpickr($component, Carbon::createFromFormat('H:i', '08:30', config('app.timezone'))); + + expect($component->hydratedState)->toBe('08:30'); +}); + +it('hydrates multiple picker values from an array', function () { + $component = StatefulFlatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction(','); + + $component->hydrateFlatpickr($component, ['2024-06-01', '2024-06-15']); + + expect($component->hydratedState)->toBe('2024-06-01,2024-06-15'); +}); + +it('hydrates multiple picker values from a string', function () { + $component = StatefulFlatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction(','); + + $component->hydrateFlatpickr($component, '2024-06-01'); + + expect($component->hydratedState)->toBe('2024-06-01'); +}); + +it('hydrates native multiple picker values', function () { + $component = setFlatpickrNative( + StatefulFlatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction('|'), + ); + + $component->hydrateFlatpickr($component, ['2024-06-01', '2024-06-15']); + + expect($component->hydratedState)->toBe('2024-06-01|2024-06-15'); +}); + +it('hydrates range picker values from an array', function () { + $component = StatefulFlatpickr::make('range') + ->rangePicker() + ->format('Y-m-d') + ->rangeSeparator(' to '); + + $component->hydrateFlatpickr($component, ['2024-06-01', '2024-06-15']); + + expect($component->hydratedState)->toBe('2024-06-01 to 2024-06-15'); +}); + +it('hydrates native range picker values', function () { + $component = setFlatpickrNative( + StatefulFlatpickr::make('range') + ->rangePicker() + ->format('Y-m-d') + ->rangeSeparator(' - '), + ); + + $component->hydrateFlatpickr($component, ['2024-06-01', '2024-06-15']); + + expect($component->hydratedState)->toBe('2024-06-01 - 2024-06-15'); +}); + +it('clears state when hydration receives an unparsable value', function () { + $component = StatefulFlatpickr::make('date')->format('Y-m-d'); + + $component->hydrateFlatpickr($component, 'not-a-date'); + + expect($component->hydratedState)->toBeNull(); +}); + +it('parses carbon instances using the configured timezone', function () { + $component = StatefulFlatpickr::make('date') + ->format('Y-m-d') + ->timezone('UTC'); + + $result = (new ReflectionMethod($component, 'parseToCarbon')) + ->invoke($component, Carbon::parse('2024-06-15', 'Europe/Paris')); + + expect($result)->toBeInstanceOf(CarbonInterface::class) + ->and($result->timezone->getName())->toBe('UTC'); +}); diff --git a/tests/FlatpickrServiceProviderTest.php b/tests/FlatpickrServiceProviderTest.php index 159ef88..baff96d 100644 --- a/tests/FlatpickrServiceProviderTest.php +++ b/tests/FlatpickrServiceProviderTest.php @@ -2,6 +2,7 @@ use Coolsam\Flatpickr\FlatpickrServiceProvider; use Filament\Support\Facades\FilamentAsset; +use Illuminate\Support\Facades\Artisan; it('registers flatpickr assets with filament', function () { expect(FilamentAsset::getAlpineComponentSrc('flatpickr', 'coolsam/flatpickr'))->toBeString() @@ -11,3 +12,32 @@ it('exposes the flatpickr package name', function () { expect(FlatpickrServiceProvider::$name)->toBe('flatpickr'); }); + +it('publishes flatpickr assets from the service provider', function () { + Artisan::call('vendor:publish', [ + '--tag' => 'flatpickr-assets', + '--force' => true, + ]); + + expect(Artisan::output())->toContain('Publishing'); +}); + +it('publishes flatpickr stubs from the service provider', function () { + Artisan::call('vendor:publish', [ + '--tag' => 'flatpickr-stubs', + '--force' => true, + ]); + + expect(Artisan::output())->toContain('Publishing') + ->and(base_path('stubs/flatpickr/flatpickr-field.stub'))->toBeFile(); +}); + +it('exposes package routes and migration metadata', function () { + $provider = $this->app->getProvider(FlatpickrServiceProvider::class); + + $routesMethod = new ReflectionMethod($provider, 'getRoutes'); + $migrationsMethod = new ReflectionMethod($provider, 'getMigrations'); + + expect($routesMethod->invoke($provider))->toBe([]) + ->and($migrationsMethod->invoke($provider))->toBe(['create_flatpickr_table']); +}); diff --git a/tests/FlatpickrThemeTest.php b/tests/FlatpickrThemeTest.php new file mode 100644 index 0000000..24e2026 --- /dev/null +++ b/tests/FlatpickrThemeTest.php @@ -0,0 +1,23 @@ +getAsset())->toContain("themes/{$theme->value}.css"); + } +}); + +it('returns an empty asset url when theme asset generation fails', function () { + $theme = FlatpickrTheme::DEFAULT; + + app()->instance('url', new class + { + public function asset(string $path): string + { + throw new RuntimeException('asset unavailable'); + } + }); + + expect($theme->getAsset())->toBe(''); +}); diff --git a/tests/FlatpickrValidationTest.php b/tests/FlatpickrValidationTest.php index f848763..4370291 100644 --- a/tests/FlatpickrValidationTest.php +++ b/tests/FlatpickrValidationTest.php @@ -82,3 +82,63 @@ expect($stringRules)->not->toContain('after_or_equal:2024-01-01') ->and($stringRules)->not->toContain('before_or_equal:2024-12-31'); }); + +it('skips multiple picker validation when the value is blank', function () { + $component = Flatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction(','); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make(['dates' => ''], ['dates' => [$closureRule]]); + + expect($validator->passes())->toBeTrue(); +}); + +it('skips range picker validation when the value is blank', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make(['range' => ''], ['range' => [$closureRule]]); + + expect($validator->passes())->toBeTrue(); +}); + +it('accepts valid multiple picker values', function () { + $component = Flatpickr::make('dates') + ->multiplePicker() + ->format('Y-m-d') + ->conjunction(','); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make( + ['dates' => '2024-06-01,2024-06-15'], + ['dates' => [$closureRule]], + ); + + expect($validator->passes())->toBeTrue(); +}); + +it('accepts valid range picker values', function () { + $component = Flatpickr::make('range') + ->rangePicker() + ->format('Y-m-d'); + + $closureRule = collect($component->getValidationRules()) + ->first(fn ($rule) => $rule instanceof Closure); + + $validator = Validator::make( + ['range' => '2024-06-01 to 2024-06-15'], + ['range' => [$closureRule]], + ); + + expect($validator->passes())->toBeTrue(); +}); diff --git a/tests/FlatpickrViewHelpersTest.php b/tests/FlatpickrViewHelpersTest.php new file mode 100644 index 0000000..930b38c --- /dev/null +++ b/tests/FlatpickrViewHelpersTest.php @@ -0,0 +1,58 @@ +getType())->toBe('date') + ->and(Flatpickr::make('start_time')->date(false)->time(true)->getType())->toBe('time') + ->and(Flatpickr::make('published_at')->time(true)->getType())->toBe('datetime-local'); +}); + +it('exposes view helper accessors for datalist, placeholder, and readonly state', function () { + $component = Flatpickr::make('date') + ->datalist(['2024-01-01']) + ->placeholder('Pick a date') + ->readOnly(true) + ->autofocus(true) + ->extraAlpineAttributes(['x-on:change' => 'changed()']); + + expect($component->getDatalistOptions())->toBe(['2024-01-01']) + ->and($component->getStep())->toBeNull() + ->and($component->getPlaceholder())->toBe('Pick a date') + ->and($component->isReadOnly())->toBeTrue() + ->and($component->isAutofocused())->toBeTrue() + ->and($component->getExtraAlpineAttributes())->toBe(['x-on:change' => 'changed()']) + ->and($component->getExtraInputAttributeBag())->toBeInstanceOf(ComponentAttributeBag::class); +}); + +it('exposes prefix and suffix view helpers', function () { + $component = Flatpickr::make('date') + ->prefixActions([]) + ->prefixIcon('heroicon-o-calendar') + ->prefixIconColor('primary') + ->prefixLabel('From') + ->suffixActions([]) + ->suffixIcon('heroicon-o-clock') + ->suffixIconColor('gray') + ->suffixLabel('UTC'); + + expect($component->getPrefixActions())->toBe([]) + ->and($component->getPrefixIcon())->toBe('heroicon-o-calendar') + ->and($component->getPrefixIconColor())->toBe('primary') + ->and($component->getPrefixLabel())->toBe('From') + ->and($component->isPrefixInline())->toBeFalse() + ->and($component->getSuffixActions())->toBe([]) + ->and($component->getSuffixIcon())->toBe('heroicon-o-clock') + ->and($component->getSuffixIconColor())->toBe('gray') + ->and($component->getSuffixLabel())->toBe('UTC') + ->and($component->isSuffixInline())->toBeFalse(); +}); + +it('reports native mode from the underlying property', function () { + $component = Flatpickr::make('date'); + $property = new ReflectionProperty($component, 'isNative'); + $property->setValue($component, true); + + expect($component->isNative())->toBeTrue(); +}); diff --git a/tests/Support/ParseThrowingFlatpickr.php b/tests/Support/ParseThrowingFlatpickr.php new file mode 100644 index 0000000..76b07cd --- /dev/null +++ b/tests/Support/ParseThrowingFlatpickr.php @@ -0,0 +1,15 @@ +hydratedState = $state; + + return $this; + } +}