import React from 'react'; import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; /** * "Save for later" on Create Order, then Saved Contacts in another tab. * * The contact didn't show up, for two reasons this pins: * 1. The picker read only /admin/customers (customer-app accounts), while * "save for later" writes to /admin/tenantcustomers — so a contact saved * on the server never appeared; only the saving browser's local copy did. * 2. The local copy was read once, on mount, so a Create Order tab that was * already open never saw a contact saved in another tab. */ jest.mock('lucide-react', () => new Proxy({}, { get: (_t, prop) => (prop === '__esModule' ? true : (props) => ), }) ); jest.mock('leaflet', () => ({ __esModule: true, default: { DivIcon: function DivIcon() {} } })); jest.mock('react-leaflet', () => ({ MapContainer: () => null, Marker: () => null, Polyline: () => null, TileLayer: () => null, useMap: () => ({}), })); jest.mock('@/components/doormile/AddressAutocomplete', () => ({ __esModule: true, default: () => null })); jest.mock('@/components/doormile/MapPinPickerModal', () => ({ __esModule: true, default: () => null })); jest.mock('@/components/doormile/HubLocationSelect', () => ({ HubLocationSelect: () => null })); jest.mock('@/lib/doormileHooks', () => ({ useHubs: () => ({ data: [] }) })); jest.mock('@/lib/geocodingService', () => ({ geocodeAddress: jest.fn(), saveRecentAddress: jest.fn() })); jest.mock('@/lib/distance', () => ({ // The formatters are pure — keep the real ones so the summary card renders. formatRouteDistance: jest.requireActual('@/lib/distance').formatRouteDistance, formatRouteDuration: jest.requireActual('@/lib/distance').formatRouteDuration, calculateDrivingDistance: jest.fn(() => Promise.resolve(null)), calculateDrivingRoute: jest.fn(() => Promise.resolve(null)), calculateTotalCharge: jest.fn(() => 0), })); jest.mock('@/components/third-party/OpenToast', () => ({ OpenToast: jest.fn() })); jest.mock('@/api/doormile/endpoints', () => ({ createExpressBooking: jest.fn(), createTenantCustomer: jest.fn(), getAdminCustomers: jest.fn(), getAdminPricing: jest.fn(() => Promise.resolve([])), getAdminTenants: jest.fn(() => Promise.resolve([])), getTenantCustomers: jest.fn(), getTenantLocations: jest.fn(() => Promise.resolve([])), })); import CreateOrder from '@/pages/doormile/orders/CreateOrder'; import { getAdminCustomers, getTenantCustomers } from '@/api/doormile/endpoints'; const renderPage = () => { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( ); }; const openSavedContacts = async () => { fireEvent.click(screen.getAllByText('Saved Contacts')[0].closest('button')); const heading = await screen.findByRole('heading', { name: 'Saved Contacts' }); return heading.closest('div.bg-white'); }; beforeEach(() => { localStorage.clear(); getAdminCustomers.mockResolvedValue([ { appcustomerid: 7, name: 'App Customer', phone: '9111111111' }, ]); getTenantCustomers.mockResolvedValue([]); }); describe('Create Order — Saved Contacts', () => { it('shows a contact saved on the server, which this browser has never seen', async () => { // Nothing in localStorage: this is the "other browser / incognito" case. getTenantCustomers.mockResolvedValue([ { tenantcustomerid: 42, firstname: 'Test', lastname: 'Save Contact', phone: '9000000001', address: '12 MG Road' }, ]); renderPage(); const modal = await openSavedContacts(); await waitFor(() => expect(within(modal).getByText('Test Save Contact')).toBeInTheDocument()); expect(within(modal).getByText('(9000000001)')).toBeInTheDocument(); // The customer-app accounts are still listed alongside. expect(within(modal).getByText('App Customer')).toBeInTheDocument(); }); it('picks up a contact saved in another tab without reloading the page', async () => { renderPage(); let modal = await openSavedContacts(); await waitFor(() => expect(within(modal).getByText('App Customer')).toBeInTheDocument()); expect(within(modal).queryByText('Other Tab Contact')).not.toBeInTheDocument(); // Another tab saves a contact: it writes localStorage, and the browser // fires `storage` in every OTHER tab. act(() => { localStorage.setItem( 'doormile_saved_contacts', JSON.stringify([{ id: 'saved_9000000002', name: 'Other Tab Contact', phone: '9000000002', isSavedForLater: true }]) ); window.dispatchEvent(new StorageEvent('storage', { key: 'doormile_saved_contacts' })); }); modal = screen.getByRole('heading', { name: 'Saved Contacts' }).closest('div.bg-white'); await waitFor(() => expect(within(modal).getByText('Other Tab Contact')).toBeInTheDocument()); }); it('lists a number once when it is both saved locally and on the server', async () => { localStorage.setItem( 'doormile_saved_contacts', JSON.stringify([{ id: 'saved_9000000001', name: 'Test Save Contact', phone: '9000000001', isSavedForLater: true }]) ); getTenantCustomers.mockResolvedValue([ { tenantcustomerid: 42, firstname: 'Test', lastname: 'Save Contact', phone: '9000000001' }, ]); renderPage(); const modal = await openSavedContacts(); await waitFor(() => expect(within(modal).getByText('App Customer')).toBeInTheDocument()); expect(within(modal).getAllByText('(9000000001)')).toHaveLength(1); }); });