b1ac864573
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
159 lines
6.5 KiB
PHP
159 lines
6.5 KiB
PHP
<?php
|
|
|
|
use App\Enums\ConsultationStatus;
|
|
use App\Models\Consultation;
|
|
use App\Models\User;
|
|
use App\Models\WorkingHour;
|
|
use App\Services\AvailabilityService;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Livewire\Volt\Volt;
|
|
|
|
beforeEach(function () {
|
|
Mail::fake();
|
|
RateLimiter::clear('guest-booking:127.0.0.1');
|
|
});
|
|
|
|
function openEveryDay(): void
|
|
{
|
|
foreach (range(0, 6) as $dow) {
|
|
WorkingHour::factory()->create(['day_of_week' => $dow, 'is_active' => true, 'start_time' => '09:00', 'end_time' => '13:00']);
|
|
}
|
|
}
|
|
|
|
function firstBookableDate(): string
|
|
{
|
|
$service = app(AvailabilityService::class);
|
|
for ($i = 0; $i < 60; $i++) {
|
|
$d = today()->addDays($i);
|
|
if (in_array($service->getDateStatus($d), ['available', 'partial'])) {
|
|
return $d->format('Y-m-d');
|
|
}
|
|
}
|
|
throw new RuntimeException('no bookable day');
|
|
}
|
|
|
|
function fillCompactForm($component, string $date, string $time = '09:00')
|
|
{
|
|
return $component
|
|
->call('selectSlot', $date, $time)
|
|
->set('guestName', 'Test Guest')
|
|
->set('guestPhone', '+970599000000')
|
|
->set('guestEmail', 'guest@example.com')
|
|
->set('problemSummary', 'A sufficiently long description of my case.')
|
|
->set('captchaAnswer', session('captcha_answer'));
|
|
}
|
|
|
|
test('strip variant lists up to 14 bookable days, selects the first and loads its slots', function () {
|
|
openEveryDay();
|
|
$first = firstBookableDate();
|
|
|
|
$component = Volt::test('availability-calendar', ['variant' => 'strip'])
|
|
->assertSet('selectedDate', $first)
|
|
->assertSee('class="days"', false)
|
|
->assertSee('class="slots"', false)
|
|
->assertDontSee('calendar-grid', false);
|
|
|
|
expect(substr_count($component->html(), 'class="day"'))->toBeGreaterThan(0)->toBeLessThanOrEqual(14);
|
|
expect($component->get('availableSlots'))->not->toBeEmpty();
|
|
|
|
$second = collect(range(1, 59))->map(fn ($i) => today()->addDays($i)->format('Y-m-d'))
|
|
->first(fn ($d) => $d > $first && in_array(app(AvailabilityService::class)->getDateStatus(Carbon::parse($d)), ['available', 'partial']));
|
|
$component->call('selectDate', $second)->assertSet('selectedDate', $second);
|
|
});
|
|
|
|
test('strip variant shows the none message when nothing is bookable', function () {
|
|
Volt::test('availability-calendar', ['variant' => 'strip'])->assertSee(__('site.booking_none'));
|
|
});
|
|
|
|
test('default variant is still the month calendar', function () {
|
|
Volt::test('availability-calendar')->assertSet('variant', 'month')->assertSee('calendar-grid', false)->assertDontSee('class="days"', false);
|
|
});
|
|
|
|
test('compact booking renders the form in the specified order', function () {
|
|
openEveryDay();
|
|
$html = Volt::test('pages.booking', ['compact' => true])->html();
|
|
|
|
$order = ['wire:model="guestName"', 'wire:model="guestPhone"', 'wire:model="guestEmail"', 'class="days"', 'class="slots"',
|
|
'wire:model="problemSummary"', 'wire:model="captchaAnswer"', 'btn btn-gold', 'class="note"'];
|
|
$last = -1;
|
|
foreach ($order as $needle) {
|
|
$pos = strpos($html, $needle);
|
|
expect($pos)->not->toBeFalse("missing $needle");
|
|
expect($pos)->toBeGreaterThan($last);
|
|
$last = $pos;
|
|
}
|
|
expect($html)->toContain('book-r');
|
|
});
|
|
|
|
test('a guest can book from the compact card and lands on success', function () {
|
|
openEveryDay();
|
|
$date = firstBookableDate();
|
|
|
|
$c = Volt::test('pages.booking', ['compact' => true]);
|
|
fillCompactForm($c, $date)->call('showConfirm')->assertHasNoErrors()->assertSet('showConfirmation', true)
|
|
->call('submit')->assertRedirect(route('booking.success'));
|
|
|
|
expect(Consultation::count())->toBe(1);
|
|
$row = Consultation::first();
|
|
expect($row->guest_email)->toBe('guest@example.com')->and($row->status)->toBe(ConsultationStatus::Pending)->and($row->user_id)->toBeNull();
|
|
});
|
|
|
|
test('compact booking keeps the same validation as the page', function () {
|
|
openEveryDay();
|
|
$date = firstBookableDate();
|
|
|
|
$c = Volt::test('pages.booking', ['compact' => true]);
|
|
fillCompactForm($c, $date)->set('problemSummary', 'too short')->call('showConfirm')->assertHasErrors(['problemSummary']);
|
|
|
|
$c = Volt::test('pages.booking', ['compact' => true]);
|
|
fillCompactForm($c, $date)->set('captchaAnswer', 'wrong')->call('showConfirm')->assertHasErrors(['captchaAnswer']);
|
|
|
|
$c = Volt::test('pages.booking', ['compact' => true])
|
|
->set('guestName', 'Test Guest')->set('guestPhone', '1')->set('guestEmail', 'a@b.co')
|
|
->set('problemSummary', 'A sufficiently long description of my case.')->set('captchaAnswer', session('captcha_answer'))
|
|
->call('showConfirm')->assertHasErrors(['selectedTime']);
|
|
|
|
expect(Consultation::count())->toBe(0);
|
|
});
|
|
|
|
test('one booking per email per day is enforced in compact mode', function () {
|
|
openEveryDay();
|
|
$date = firstBookableDate();
|
|
Consultation::factory()->create(['guest_name' => 'Other', 'guest_phone' => '123', 'guest_email' => 'guest@example.com', 'booking_date' => $date, 'booking_time' => '12:00', 'status' => ConsultationStatus::Pending, 'user_id' => null]);
|
|
|
|
$c = Volt::test('pages.booking', ['compact' => true]);
|
|
fillCompactForm($c, $date)->call('showConfirm')->assertHasErrors(['guestEmail']);
|
|
});
|
|
|
|
test('logged in clients see the message instead of the compact form', function () {
|
|
$user = User::factory()->client()->create();
|
|
|
|
Volt::actingAs($user)->test('pages.booking', ['compact' => true])
|
|
->assertSee(__('booking.logged_in_title'))
|
|
->assertSee(route('client.consultations.book'), false)
|
|
->assertDontSee('wire:model="guestName"', false);
|
|
});
|
|
|
|
test('home page has the booking card with the finale balance', function () {
|
|
$html = $this->get('/')->assertOk()->getContent();
|
|
|
|
expect($html)->toContain('id="book"')->toContain('class="book"')->toContain('book-l on-dark')
|
|
->toContain('data-balance="finale"')->toContain('tel:+970599353502')->toContain(__('footer.email'))->toContain('book-r');
|
|
});
|
|
|
|
test('the booking page itself is not compact', function () {
|
|
$html = $this->get('/booking')->assertOk()->getContent();
|
|
expect($html)->not->toContain('book-r')->toContain('calendar-grid');
|
|
});
|
|
|
|
test('booking card keys exist in both languages', function () {
|
|
foreach (['ar', 'en'] as $locale) {
|
|
$t = require base_path("lang/$locale/site.php");
|
|
foreach (['art_booking', 'booking_title', 'booking_lede', 'booking_day', 'booking_time', 'booking_submit', 'booking_none'] as $k) {
|
|
expect($t)->toHaveKey($k);
|
|
}
|
|
}
|
|
});
|