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('class="days"')->not->toContain('wire:submit="showConfirm"')->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); } } });