Spaces:
Sleeping
Sleeping
| // FE-COMP-RES-001 to FE-COMP-RES-040 | |
| import { render, screen, waitFor, within } from '../../../tests/helpers/render'; | |
| import userEvent from '@testing-library/user-event'; | |
| import { useAuthStore } from '../../store/authStore'; | |
| import { useTripStore } from '../../store/tripStore'; | |
| import { useSettingsStore } from '../../store/settingsStore'; | |
| import { usePermissionsStore } from '../../store/permissionsStore'; | |
| import { resetAllStores, seedStore } from '../../../tests/helpers/store'; | |
| import { buildUser, buildTrip, buildReservation, buildDay, buildPlace } from '../../../tests/helpers/factories'; | |
| import ReservationsPanel from './ReservationsPanel'; | |
| vi.mock('../../api/authUrl', () => ({ getAuthUrl: vi.fn().mockResolvedValue('http://test/file') })); | |
| const defaultProps = { | |
| tripId: 1, | |
| reservations: [], | |
| days: [], | |
| assignments: {}, | |
| files: [], | |
| onAdd: vi.fn(), | |
| onEdit: vi.fn(), | |
| onDelete: vi.fn(), | |
| onNavigateToFiles: vi.fn(), | |
| }; | |
| beforeEach(() => { | |
| resetAllStores(); | |
| seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); | |
| seedStore(useTripStore, { trip: buildTrip({ id: 1 }) }); | |
| seedStore(useSettingsStore, { settings: { time_format: '24h', blur_booking_codes: false, temperature_unit: 'celsius', language: 'en', dark_mode: false, default_currency: 'USD', default_lat: 48.8566, default_lng: 2.3522, default_zoom: 10, map_tile_url: '', show_place_description: false } }); | |
| }); | |
| describe('ReservationsPanel', () => { | |
| it('FE-COMP-RES-001: renders without crashing', () => { | |
| render(<ReservationsPanel {...defaultProps} />); | |
| expect(document.body).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-002: shows Bookings title', () => { | |
| render(<ReservationsPanel {...defaultProps} />); | |
| // reservations.title = "Bookings" | |
| expect(screen.getByText('Bookings')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-003: shows empty state when no reservations', () => { | |
| render(<ReservationsPanel {...defaultProps} reservations={[]} />); | |
| // "No reservations yet" appears in both header subtitle and empty state body | |
| const els = screen.getAllByText('No reservations yet'); | |
| expect(els.length).toBeGreaterThan(0); | |
| }); | |
| it('FE-COMP-RES-004: shows empty hint text', () => { | |
| render(<ReservationsPanel {...defaultProps} reservations={[]} />); | |
| expect(screen.getByText(/Add reservations for flights/i)).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-005: shows Manual Booking add button', () => { | |
| render(<ReservationsPanel {...defaultProps} />); | |
| // Button text is reservations.addManual = "Manual Booking" | |
| expect(screen.getByText('Manual Booking')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-006: clicking Manual Booking button calls onAdd', async () => { | |
| const user = userEvent.setup(); | |
| const onAdd = vi.fn(); | |
| render(<ReservationsPanel {...defaultProps} onAdd={onAdd} />); | |
| await user.click(screen.getByText('Manual Booking')); | |
| expect(onAdd).toHaveBeenCalled(); | |
| }); | |
| it('FE-COMP-RES-007: renders reservation title', () => { | |
| // Component renders r.title, not r.name | |
| const res = buildReservation({ title: 'Hotel Paris', type: 'hotel', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Hotel Paris')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-008: renders confirmed reservation badge', () => { | |
| const res = buildReservation({ title: 'Flight NY', type: 'flight', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| // "Confirmed" appears in both section header and card badge | |
| const els = screen.getAllByText('Confirmed'); | |
| expect(els.length).toBeGreaterThan(0); | |
| }); | |
| it('FE-COMP-RES-009: renders pending reservation badge', () => { | |
| const res = buildReservation({ title: 'Hotel Rome', type: 'hotel', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| // "Pending" appears in both section header and card badge | |
| const els = screen.getAllByText('Pending'); | |
| expect(els.length).toBeGreaterThan(0); | |
| }); | |
| it('FE-COMP-RES-010: shows reservations title and cards', () => { | |
| const r1 = buildReservation({ title: 'My Flight Booking', type: 'flight', status: 'confirmed' }); | |
| const r2 = buildReservation({ title: 'Grand Hotel', type: 'hotel', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r1, r2]} />); | |
| expect(screen.getByText('My Flight Booking')).toBeInTheDocument(); | |
| expect(screen.getByText('Grand Hotel')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-011: hotel reservation renders', () => { | |
| const res = buildReservation({ title: 'Grand Hotel', type: 'hotel', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Grand Hotel')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-012: flight reservation renders', () => { | |
| const res = buildReservation({ title: 'Air France 123', type: 'flight', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Air France 123')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-013: multiple reservations all render', () => { | |
| const r1 = buildReservation({ title: 'Hotel A', type: 'hotel', status: 'confirmed' }); | |
| const r2 = buildReservation({ title: 'Flight B', type: 'flight', status: 'confirmed' }); | |
| const r3 = buildReservation({ title: 'Restaurant C', type: 'restaurant', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r1, r2, r3]} />); | |
| expect(screen.getByText('Hotel A')).toBeInTheDocument(); | |
| expect(screen.getByText('Flight B')).toBeInTheDocument(); | |
| expect(screen.getByText('Restaurant C')).toBeInTheDocument(); | |
| }); | |
| it('FE-COMP-RES-014: edit button calls onEdit with reservation', async () => { | |
| const user = userEvent.setup(); | |
| const onEdit = vi.fn(); | |
| const res = buildReservation({ id: 77, title: 'Editable Res', type: 'hotel', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} onEdit={onEdit} />); | |
| const editBtn = screen.getByTitle('Edit'); | |
| await user.click(editBtn); | |
| expect(onEdit).toHaveBeenCalledWith(expect.objectContaining({ id: 77 })); | |
| }); | |
| it('FE-COMP-RES-015: delete button opens confirm dialog, then calls onDelete', async () => { | |
| const user = userEvent.setup(); | |
| const onDelete = vi.fn().mockResolvedValue(undefined); | |
| const res = buildReservation({ id: 88, title: 'Delete Me', type: 'hotel', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} onDelete={onDelete} />); | |
| await user.click(screen.getByTitle('Delete')); | |
| // Confirm dialog appears β click the Confirm button | |
| const confirmBtn = await screen.findByText('Confirm'); | |
| await user.click(confirmBtn); | |
| await waitFor(() => expect(onDelete).toHaveBeenCalledWith(88)); | |
| }); | |
| // ββ Section collapsing ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-016: clicking Pending section header collapses it', async () => { | |
| const user = userEvent.setup(); | |
| const res = buildReservation({ title: 'Pending Hotel', type: 'hotel', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| // Initially the card is visible | |
| expect(screen.getByText('Pending Hotel')).toBeInTheDocument(); | |
| // Click the "Pending" section header button (the one with count badge) | |
| const pendingButtons = screen.getAllByText('Pending'); | |
| // The section header button contains "Pending" text | |
| const sectionHeaderBtn = pendingButtons.find(el => el.closest('button')); | |
| await user.click(sectionHeaderBtn!.closest('button')!); | |
| // Card should no longer be visible | |
| expect(screen.queryByText('Pending Hotel')).not.toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-017: clicking Pending section header again expands it', async () => { | |
| const user = userEvent.setup(); | |
| const res = buildReservation({ title: 'Pending Train', type: 'train', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| const pendingButtons = screen.getAllByText('Pending'); | |
| const sectionHeaderBtn = pendingButtons.find(el => el.closest('button')); | |
| // Collapse | |
| await user.click(sectionHeaderBtn!.closest('button')!); | |
| expect(screen.queryByText('Pending Train')).not.toBeInTheDocument(); | |
| // Re-query after collapse | |
| const pendingButtons2 = screen.getAllByText('Pending'); | |
| const sectionHeaderBtn2 = pendingButtons2.find(el => el.closest('button')); | |
| // Expand | |
| await user.click(sectionHeaderBtn2!.closest('button')!); | |
| expect(screen.getByText('Pending Train')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-018: confirmed and pending sections render separately', () => { | |
| const confirmed = buildReservation({ title: 'Confirmed Flight', type: 'flight', status: 'confirmed' }); | |
| const pending = buildReservation({ title: 'Pending Restaurant', type: 'restaurant', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[confirmed, pending]} />); | |
| // Both section labels should appear (as buttons or spans in card headers, plus section titles) | |
| const confirmedEls = screen.getAllByText('Confirmed'); | |
| const pendingEls = screen.getAllByText('Pending'); | |
| expect(confirmedEls.length).toBeGreaterThan(0); | |
| expect(pendingEls.length).toBeGreaterThan(0); | |
| }); | |
| // ββ ReservationCard details βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-019: reservation with date shows formatted date', () => { | |
| const res = buildReservation({ reservation_time: '2025-06-15', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| // Should show some form of Jun 15 formatted date | |
| expect(screen.getByText(/Jun/i)).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-020: reservation with ISO datetime shows time', () => { | |
| const res = buildReservation({ reservation_time: '2025-06-15T14:30:00Z', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| // Time column should appear (exact format depends on locale/env but contains hour:minute) | |
| expect(screen.getByText(/\d{1,2}:\d{2}/)).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-021: confirmation number is visible by default (no blur)', () => { | |
| const res = buildReservation({ confirmation_number: 'ABC123', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('ABC123')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-022: confirmation number is blurred when blur_booking_codes=true', () => { | |
| seedStore(useSettingsStore, { settings: { time_format: '24h', blur_booking_codes: true, temperature_unit: 'celsius', language: 'en', dark_mode: false, default_currency: 'USD', default_lat: 48.8566, default_lng: 2.3522, default_zoom: 10, map_tile_url: '', show_place_description: false } }); | |
| const res = buildReservation({ confirmation_number: 'ABC123', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| const codeEl = screen.getByText('ABC123'); | |
| expect(codeEl.style.filter).toContain('blur'); | |
| }); | |
| it('FE-PLANNER-RESP-023: confirmation code revealed on hover when blurred', async () => { | |
| const user = userEvent.setup(); | |
| seedStore(useSettingsStore, { settings: { time_format: '24h', blur_booking_codes: true, temperature_unit: 'celsius', language: 'en', dark_mode: false, default_currency: 'USD', default_lat: 48.8566, default_lng: 2.3522, default_zoom: 10, map_tile_url: '', show_place_description: false } }); | |
| const res = buildReservation({ confirmation_number: 'ABC123', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| const codeEl = screen.getByText('ABC123'); | |
| expect(codeEl.style.filter).toContain('blur'); | |
| await user.hover(codeEl); | |
| expect(codeEl.style.filter).toBe('none'); | |
| }); | |
| it('FE-PLANNER-RESP-024: reservation notes are shown', () => { | |
| const res = buildReservation({ notes: 'Window seat requested', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Window seat requested')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-025: reservation location is shown', () => { | |
| const res = buildReservation({ location: 'Charles de Gaulle Airport', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Charles de Gaulle Airport')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-026: flight metadata (airline, flight number) renders', () => { | |
| const res = buildReservation({ | |
| type: 'flight', | |
| status: 'confirmed', | |
| metadata: JSON.stringify({ airline: 'Air France', flight_number: 'AF001', departure_airport: 'CDG', arrival_airport: 'JFK' }), | |
| }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('Air France')).toBeInTheDocument(); | |
| expect(screen.getByText('AF001')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-027: train metadata (train number, platform, seat) renders', () => { | |
| const res = buildReservation({ | |
| type: 'train', | |
| status: 'confirmed', | |
| metadata: JSON.stringify({ train_number: 'TGV9876', platform: '3', seat: '42A' }), | |
| }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('TGV9876')).toBeInTheDocument(); | |
| expect(screen.getByText('3')).toBeInTheDocument(); | |
| expect(screen.getByText('42A')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-028: hotel check-in/check-out metadata renders', () => { | |
| const res = buildReservation({ | |
| type: 'hotel', | |
| status: 'confirmed', | |
| metadata: JSON.stringify({ check_in_time: '14:00', check_out_time: '11:00' }), | |
| }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.getByText('14:00')).toBeInTheDocument(); | |
| expect(screen.getByText('11:00')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-029: linked assignment shows day title and place name', () => { | |
| const place = buildPlace({ name: 'Eiffel Tower', place_time: '10:00' }); | |
| const assignmentId = 55; | |
| const day = { ...buildDay({ id: 1, title: 'Day 1', date: '2025-06-01' }), day_number: 1 } as any; | |
| const assignments = { '1': [{ id: assignmentId, order_index: 0, day_id: 1, place_id: place.id, notes: null, place }] }; | |
| const res = buildReservation({ assignment_id: assignmentId, status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} days={[day]} assignments={assignments} />); | |
| expect(screen.getByText(/Day 1/)).toBeInTheDocument(); | |
| expect(screen.getByText(/Eiffel Tower/)).toBeInTheDocument(); | |
| }); | |
| // ββ Status toggle (canEdit=true) ββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-030: status label is always a span (not clickable)', () => { | |
| const res = buildReservation({ title: 'My Booking', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| const pendingEls = screen.getAllByText('Pending'); | |
| const statusSpan = pendingEls.find(el => el.tagName === 'SPAN'); | |
| expect(statusSpan).toBeDefined(); | |
| const statusBtn = pendingEls.find(el => el.tagName === 'BUTTON'); | |
| expect(statusBtn).toBeUndefined(); | |
| }); | |
| // ββ Status (canEdit=false) ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-032: status label is a span (not button) when canEdit=false', () => { | |
| seedStore(usePermissionsStore, { permissions: { reservation_edit: 'admin' } }); | |
| const res = buildReservation({ title: 'Read Only', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| const pendingEls = screen.getAllByText('Pending'); | |
| const statusSpan = pendingEls.find(el => el.tagName === 'SPAN'); | |
| expect(statusSpan).toBeDefined(); | |
| const statusBtn = pendingEls.find(el => el.tagName === 'BUTTON'); | |
| expect(statusBtn).toBeUndefined(); | |
| }); | |
| it('FE-PLANNER-RESP-033: edit and delete buttons hidden when canEdit=false', () => { | |
| seedStore(usePermissionsStore, { permissions: { reservation_edit: 'admin' } }); | |
| const res = buildReservation({ title: 'Read Only', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| expect(screen.queryByTitle('Edit')).not.toBeInTheDocument(); | |
| expect(screen.queryByTitle('Delete')).not.toBeInTheDocument(); | |
| }); | |
| // ββ Delete confirmation βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-034: delete confirm dialog shows reservation title', async () => { | |
| const user = userEvent.setup(); | |
| const res = buildReservation({ id: 99, title: 'Paris Hotel', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| await user.click(screen.getByTitle('Delete')); | |
| // The dialog body contains the title in the delete message | |
| const dialogBody = await screen.findByText(/will be permanently deleted/i); | |
| expect(dialogBody.textContent).toContain('Paris Hotel'); | |
| }); | |
| it('FE-PLANNER-RESP-035: clicking Cancel in delete dialog closes it without calling onDelete', async () => { | |
| const user = userEvent.setup(); | |
| const onDelete = vi.fn(); | |
| const res = buildReservation({ id: 100, title: 'Cancel Test', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} onDelete={onDelete} />); | |
| await user.click(screen.getByTitle('Delete')); | |
| const cancelBtn = await screen.findByText('Cancel'); | |
| await user.click(cancelBtn); | |
| expect(onDelete).not.toHaveBeenCalled(); | |
| expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-036: clicking backdrop closes delete confirm dialog', async () => { | |
| const user = userEvent.setup(); | |
| const res = buildReservation({ id: 101, title: 'Backdrop Test', status: 'confirmed' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} />); | |
| await user.click(screen.getByTitle('Delete')); | |
| // Dialog is visible | |
| await screen.findByText('Cancel'); | |
| // Click the fixed backdrop (the outermost div of the portal) | |
| const backdrop = document.querySelector('[style*="position: fixed"]') as HTMLElement; | |
| await user.click(backdrop!); | |
| await waitFor(() => expect(screen.queryByText('Cancel')).not.toBeInTheDocument()); | |
| }); | |
| // ββ Files βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-037: attached files section appears for reservation with files', () => { | |
| const res = buildReservation({ id: 77, status: 'confirmed' }); | |
| const files = [{ id: 1, trip_id: 1, reservation_id: 77, original_name: 'boarding_pass.pdf', url: '/uploads/bp.pdf', filename: 'bp.pdf', mime_type: 'application/pdf', created_at: '2025-01-01T00:00:00.000Z' }]; | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} files={files} />); | |
| expect(screen.getByText('boarding_pass.pdf')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-038: linked file (via linked_reservation_ids) also appears', () => { | |
| const res = buildReservation({ id: 77, status: 'confirmed' }); | |
| const files = [{ id: 2, trip_id: 1, reservation_id: null, linked_reservation_ids: [77], original_name: 'voucher.pdf', url: '/uploads/v.pdf', filename: 'v.pdf', mime_type: 'application/pdf', created_at: '2025-01-01T00:00:00.000Z' }]; | |
| render(<ReservationsPanel {...defaultProps} reservations={[res]} files={files as any} />); | |
| expect(screen.getByText('voucher.pdf')).toBeInTheDocument(); | |
| }); | |
| // ββ Add button ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| it('FE-PLANNER-RESP-039: "Add" button hidden when canEdit=false', () => { | |
| seedStore(usePermissionsStore, { permissions: { reservation_edit: 'admin' } }); | |
| render(<ReservationsPanel {...defaultProps} />); | |
| expect(screen.queryByText('Manual Booking')).not.toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-040: multiple reservations in pending section all render', () => { | |
| const r1 = buildReservation({ title: 'Pending 1', status: 'pending' }); | |
| const r2 = buildReservation({ title: 'Pending 2', status: 'pending' }); | |
| const r3 = buildReservation({ title: 'Pending 3', status: 'pending' }); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r1, r2, r3]} />); | |
| expect(screen.getByText('Pending 1')).toBeInTheDocument(); | |
| expect(screen.getByText('Pending 2')).toBeInTheDocument(); | |
| expect(screen.getByText('Pending 3')).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-041: dateless transport with legacy T-prefix shows time without "Invalid Date"', () => { | |
| const day = buildDay({ date: null, day_number: 25 } as any); | |
| const r = buildReservation({ | |
| title: 'Cruise test', | |
| type: 'cruise', | |
| status: 'pending', | |
| reservation_time: 'T10:00', | |
| reservation_end_time: 'T18:00', | |
| day_id: day.id, | |
| end_day_id: day.id, | |
| } as any); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r]} days={[day]} />); | |
| expect(screen.queryByText(/Invalid Date/)).not.toBeInTheDocument(); | |
| expect(screen.getByText(/10:00/)).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-042: dateless transport with bare time format shows time without "Invalid Date"', () => { | |
| const day = buildDay({ date: null, day_number: 3 } as any); | |
| const r = buildReservation({ | |
| title: 'Car rental', | |
| type: 'car', | |
| status: 'pending', | |
| reservation_time: '09:00', | |
| reservation_end_time: '17:00', | |
| day_id: day.id, | |
| end_day_id: day.id, | |
| } as any); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r]} days={[day]} />); | |
| expect(screen.queryByText(/Invalid Date/)).not.toBeInTheDocument(); | |
| expect(screen.getByText(/09:00/)).toBeInTheDocument(); | |
| }); | |
| it('FE-PLANNER-RESP-043: dated transport still shows date and time correctly', () => { | |
| const day = buildDay({ date: '2026-07-15', day_number: 1 }); | |
| const r = buildReservation({ | |
| title: 'Flight out', | |
| type: 'flight', | |
| status: 'confirmed', | |
| reservation_time: '2026-07-15T08:30', | |
| reservation_end_time: '2026-07-15T10:45', | |
| day_id: day.id, | |
| } as any); | |
| render(<ReservationsPanel {...defaultProps} reservations={[r]} days={[day]} />); | |
| expect(screen.queryByText(/Invalid Date/)).not.toBeInTheDocument(); | |
| expect(screen.getByText(/08:30/)).toBeInTheDocument(); | |
| }); | |
| }); | |