Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Empty file added database/migrations/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions stubs/flatpickr-field.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{ namespace }}

// Stub for publishing custom flatpickr field scaffolding.
170 changes: 170 additions & 0 deletions tests/FlatpickrAttributesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php

use Coolsam\Flatpickr\Enums\FlatpickrMode;
use Coolsam\Flatpickr\Enums\FlatpickrMonthSelectorType;
use Coolsam\Flatpickr\Enums\FlatpickrPosition;
use Coolsam\Flatpickr\Enums\FlatpickrTheme;
use Coolsam\Flatpickr\Forms\Components\Flatpickr;
use Coolsam\Flatpickr\Tests\Support\StatefulFlatpickr;
use Illuminate\Support\Carbon;

it('maps configured options to flatpickr attributes', function () {
$component = StatefulFlatpickr::make('field')
->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');
});
23 changes: 23 additions & 0 deletions tests/FlatpickrCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

it('runs the flatpickr console command', function () {
$this->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();
});
128 changes: 128 additions & 0 deletions tests/FlatpickrDehydrationExtendedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

use Carbon\CarbonInterface;
use Coolsam\Flatpickr\Forms\Components\Flatpickr;
use Coolsam\Flatpickr\Tests\Support\StatefulFlatpickr;
use Illuminate\Support\Carbon;

function setFlatpickrNativeDehydration(StatefulFlatpickr $component, bool $native = true): StatefulFlatpickr
{
$property = new ReflectionProperty($component, 'isNative');
$property->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']);
});
Loading
Loading