24-5: home booking card (strip variant, compact booking) — implemented, QA Review; footer brand layout fix from 24-2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user