// orderFlow imports geocodeAddress from a component that pulls in // @astryxdesign ESM, which jest does not transform. Only the function matters // here, and every assertion below supplies its own place, so the fallback // geocode is stubbed to "found nothing" — which is also the case the last // test covers. jest.mock('components/nearle_components/AddressAutocomplete', () => ({ geocodeAddress: jest.fn().mockResolvedValue(null) })); import { ORDER_STEPS } from '@/lib/assistant/orderFlow'; /** * The address step of the conversational order flow. * * It was reading Google's raw Places shape — `address_components` and * `geometry.location.lat()` — from a value that never has either. * `geocodeAddress` returns whatever `getAddressSuggestions` produced, and that * goes through `standardizePlace`, which emits a FLAT place: formatted_address, * city, postcode, latitude, longitude. * * So the forEach never ran and both coordinates came back undefined. Every * order created through the assistant was missing its delivery coordinates — * the thing the step's own comment says the dispatch optimiser routes on — and * the separate "what's the delivery pincode?" step fired every time because * nothing had ever set it. */ const addressStep = ORDER_STEPS.find((s) => s.id === 'deliveryaddress'); // What geocodingService.standardizePlace actually returns. const flatPlace = { formatted_address: '18 Race Course Road, Coimbatore, Tamil Nadu 641018', name: '18 Race Course Road', suburb: 'Race Course', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '641018', latitude: 11.0038, longitude: 76.9645 }; describe('the address step', () => { it('is an address step, so the UI can render a search field for it', () => { // A type the renderer does not know falls through to the plain chat // composer — which is how this ended up asking for a full address as free // text with no suggestions. expect(addressStep.type).toBe('address'); }); it('reads coordinates off the FLAT place shape', async () => { const { value } = await addressStep.resolve('race course road', { place: flatPlace }); expect(value.deliverylatitude).toBeCloseTo(11.0038); expect(value.deliverylongitude).toBeCloseTo(76.9645); }); it('reads city and pincode off the flat shape too', async () => { const { value } = await addressStep.resolve('race course road', { place: flatPlace }); expect(value.deliverycity).toBe('Coimbatore'); expect(value.deliverypincode).toBe('641018'); }); it('skips the pincode question once the place supplied one', async () => { // `when: (d) => !d.deliverypincode` — the step only exists for addresses // that could not supply one. It used to fire every single time. const pincodeStep = ORDER_STEPS.find((s) => s.id === 'deliverypincode'); const { value } = await addressStep.resolve('race course road', { place: flatPlace }); const draft = addressStep.apply({}, value); expect(pincodeStep.when(draft)).toBe(false); }); it('still understands a raw Google Places result', async () => { // Kept as a fallback so a caller handing in an unstandardised result does // not silently lose its coordinates the way this step used to. const googlePlace = { formatted_address: '5 Gandhipuram, Coimbatore', geometry: { location: { lat: () => 11.0168, lng: () => 76.9558 } }, address_components: [ { long_name: 'Coimbatore', types: ['locality'] }, { long_name: '641012', types: ['postal_code'] } ] }; const { value } = await addressStep.resolve('gandhipuram', { place: googlePlace }); expect(value.deliverylatitude).toBeCloseTo(11.0168); expect(value.deliverycity).toBe('Coimbatore'); expect(value.deliverypincode).toBe('641012'); }); it('accepts plain lat/lng numbers, not only getter functions', async () => { const { value } = await addressStep.resolve('somewhere', { place: { formatted_address: 'X', geometry: { location: { lat: 12.5, lng: 77.5 } } } }); expect(value.deliverylatitude).toBe(12.5); expect(value.deliverylongitude).toBe(77.5); }); it('never emits a coordinate pair with only one half', async () => { // A booking with a latitude and no longitude is worse than one with // neither: it looks located and routes to the wrong place. const { value } = await addressStep.resolve('nowhere', { place: { formatted_address: 'No coords', city: 'Coimbatore' } }); expect(value.deliverylatitude).toBeUndefined(); expect(value.deliverylongitude).toBeUndefined(); // and it still keeps what it DID learn expect(value.deliverycity).toBe('Coimbatore'); expect(value.deliveryaddress).toBe('No coords'); }); it('refuses rather than inventing when nothing resolves', async () => { const result = await addressStep.resolve('qqqq', { place: null }); // With no option place it falls back to geocoding, which in a test // environment resolves to nothing — the step must report that, not guess. expect(result.error || result.value).toBeTruthy(); if (result.error) expect(result.error).toMatch(/couldn.t find that address/i); }); });