From d288197c35b350685690cc64e2956a57b8f06067 Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Fri, 25 Sep 2026 17:02:08 +0530 Subject: [PATCH] updateson the create ordera nd testfile as done --- src/pages/doormile/orders/CreateOrder.jsx | 61 ++++++++- .../createOrderSavedContacts.test.jsx | 125 ++++++++++++++++++ 2 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 tests/integration/createOrderSavedContacts.test.jsx diff --git a/src/pages/doormile/orders/CreateOrder.jsx b/src/pages/doormile/orders/CreateOrder.jsx index 2c95ed1..35c0b26 100644 --- a/src/pages/doormile/orders/CreateOrder.jsx +++ b/src/pages/doormile/orders/CreateOrder.jsx @@ -192,18 +192,47 @@ export default function CreateOrder() { const [searchCustQuery, setSearchCustQuery] = useState(''); // Local persistent saved contacts for new/recent customers - const [savedContacts, setSavedContacts] = useState(() => { + const readSavedContacts = () => { try { const data = localStorage.getItem('doormile_saved_contacts'); return data ? JSON.parse(data) : []; } catch { return []; } - }); + }; + const [savedContacts, setSavedContacts] = useState(readSavedContacts); + + // Re-read the browser copy when it changes elsewhere. It used to be read + // once, on mount: a contact saved in another tab never appeared in a Create + // Order tab that was already open (clicking "Create order" again doesn't + // remount it). `storage` fires for other tabs, the custom event for this + // one, and focus covers anything missed while the tab was in the background. + useEffect(() => { + const refresh = () => setSavedContacts(readSavedContacts()); + const onStorage = (e) => { + if (!e.key || e.key === 'doormile_saved_contacts') refresh(); + }; + window.addEventListener('storage', onStorage); + window.addEventListener('doormile_saved_contacts_updated', refresh); + window.addEventListener('focus', refresh); + return () => { + window.removeEventListener('storage', onStorage); + window.removeEventListener('doormile_saved_contacts_updated', refresh); + window.removeEventListener('focus', refresh); + }; + }, []); const [saveForLater, setSaveForLater] = useState(true); const [isSavedNow, setIsSavedNow] = useState(false); + // "Saved" describes the contact currently in the form. Editing the name, + // phone or address makes it a different contact, so the button goes back to + // "Save Contact" — it used to stay "Saved" and suggest the new details were + // already stored when they weren't. + useEffect(() => { + setIsSavedNow(false); + }, [dropCust.firstname, dropCust.contactno, dropCust.address]); + // Map pin picker modal target ('pickup' | 'drop' | null) const [mapPickerTarget, setMapPickerTarget] = useState(null); @@ -246,14 +275,34 @@ export default function CreateOrder() { [hubList, tenantLocations] ); - const { data: customerList = [], isLoading: isCustomersLoading } = useQuery({ + const { data: customerList = [], isLoading: isAdminCustomersLoading } = useQuery({ queryKey: ['adminCustomers'], queryFn: getAdminCustomers }); + // The store "save for later" writes to (POST /admin/tenantcustomers) and the + // Customers page reads. The picker used to read only /admin/customers — the + // customer-app accounts — so a contact saved on the server never showed up + // here; only this browser's local copy did, which another browser, profile + // or incognito window can't see. Same query key as the Customers page, so + // the two share one cached list. + const { data: tenantCustomerList = [], isLoading: isTenantCustomersLoading } = useQuery({ + queryKey: ['doormile', 'tenant-customers'], + queryFn: getTenantCustomers, + staleTime: 60_000 + }); + const isCustomersLoading = isAdminCustomersLoading || isTenantCustomersLoading; + // Combined customers: Merge server customers with locally saved/recent customer contacts const allCustomers = useMemo(() => { - const list = [...(customerList || [])]; + // Tenant customers carry firstname/lastname; the picker renders `name`. + const savedOnServer = (tenantCustomerList || []).map((c) => ({ + ...c, + name: c.name || [c.firstname, c.lastname].filter(Boolean).join(' ') || undefined, + phone: c.phone || c.contactno, + isSavedForLater: true + })); + const list = [...savedOnServer, ...(customerList || [])]; const seenPhones = new Set(); const result = []; @@ -276,7 +325,7 @@ export default function CreateOrder() { result.push(c); } return result; - }, [customerList, savedContacts]); + }, [customerList, tenantCustomerList, savedContacts]); const matchedCustomer = useMemo(() => { const cleanPhone = String(dropCust.contactno || '').replace(/\D/g, ''); @@ -1818,7 +1867,7 @@ export default function CreateOrder() { const hasPin = Boolean(cust.latitude && cust.longitude); return (
diff --git a/tests/integration/createOrderSavedContacts.test.jsx b/tests/integration/createOrderSavedContacts.test.jsx new file mode 100644 index 0000000..ccce255 --- /dev/null +++ b/tests/integration/createOrderSavedContacts.test.jsx @@ -0,0 +1,125 @@ +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', () => ({ + 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); + }); +});