import { baseLine, nextActionLabel, routingReason, routingRoute, summariseRouting } from '@/lib/routingSummary'; /** * The routing block, turned into sentences. * * This is what a support agent reads to a rider who has rung asking why his * screen says hand over at a base instead of deliver to the customer. Two * things carry the weight here: * * 1. A PROJECTION MUST NOT READ AS A DECISION. Before pickup nothing has been * routed; `decided: false` means the answer is what WILL happen. Stating a * forecast as a fact is a wrong answer delivered confidently. * 2. THE WIRE SAYS HUB, PEOPLE SAY BASE. The translation happens at this edge * and nowhere else, and it never travels back up the wire. */ const HUB_ROUTED = { pickup_source_type: 'customer', pickup_source_name: 'Anitha R', from_address: '12 Race Course Road, Coimbatore', from_pincode: '641018', to_address: '44 Mount Road, Chennai', destination_pincode: '600002', is_hyperlocal: false, consignment_state: 'Created', next_action: 'inward_at_hub', next_hub: { id: 1, name: 'Coimbatore Hub', address: '14 Avinashi Road, Peelamedu, Coimbatore', pincode: '641004', latitude: 11.0272, longitude: 76.9905 }, inwardedat: null, decided: true }; const HYPERLOCAL = { ...HUB_ROUTED, destination_pincode: '641004', is_hyperlocal: true, consignment_state: 'Out_for_Delivery', next_action: 'deliver', next_hub: null }; describe('routingReason — why it is going that way', () => { it('should say a base is involved when the areas differ', () => { expect(routingReason(HUB_ROUTED)).toMatch(/through a base/i); }); it('should say the rider carries it straight there when the areas match', () => { expect(routingReason(HYPERLOCAL)).toMatch(/straight to the receiver/i); }); it('should never say "hub" to an operator', () => { // Internal vocabulary. Screens say base. expect(routingReason(HUB_ROUTED).toLowerCase()).not.toContain('hub'); }); it('should return an empty string rather than throw when there is no routing block', () => { // An older booking, or a backend that has not shipped this yet. expect(routingReason(null)).toBe(''); expect(routingReason(undefined)).toBe(''); }); }); describe('routingRoute — the from → to line', () => { it('should show both pincodes', () => { expect(routingRoute(HUB_ROUTED)).toContain('641018'); expect(routingRoute(HUB_ROUTED)).toContain('600002'); }); it('should not caveat a decision that has actually been taken', () => { expect(routingRoute(HUB_ROUTED)).not.toMatch(/not yet collected/i); }); it('should mark an undecided booking as a forecast, not a fact', () => { const undecided = { ...HUB_ROUTED, decided: false }; expect(routingRoute(undecided)).toMatch(/not yet collected/i); expect(routingRoute(undecided)).toMatch(/will happen, not what has/i); }); it('should show a dash for a pincode the booking never carried', () => { // The delivery-pincode column is genuinely empty on plenty of rows; a blank // gap in the line reads as a rendering bug rather than as missing data. expect(routingRoute({ ...HUB_ROUTED, destination_pincode: '' })).toContain('—'); }); }); describe('nextActionLabel — what the rider does next', () => { it('should translate every action the backend can send', () => { expect(nextActionLabel('pickup')).toMatch(/collect/i); expect(nextActionLabel('inward_at_hub')).toMatch(/base/i); expect(nextActionLabel('start_delivery')).toMatch(/delivery run/i); expect(nextActionLabel('deliver')).toMatch(/receiver/i); expect(nextActionLabel('handed_to_hub')).toMatch(/handed over/i); expect(nextActionLabel('none')).toMatch(/nothing further/i); }); it('should render inward_at_hub as a BASE handover, never as a hub one', () => { expect(nextActionLabel('inward_at_hub').toLowerCase()).not.toContain('hub'); }); it('should show an unknown action as itself rather than swallow it', () => { // The backend may add actions. A blank row tells the operator nothing; the // raw word at least tells them something new exists. expect(nextActionLabel('return_to_sender')).toBe('return_to_sender'); }); it('should show a dash when there is no action at all', () => { expect(nextActionLabel(undefined)).toBe('—'); expect(nextActionLabel('')).toBe('—'); }); }); describe('baseLine — naming the base', () => { it('should read the base with its pincode', () => { expect(baseLine(HUB_ROUTED.next_hub)).toBe('Coimbatore Hub · 641004'); }); it('should fall back to the name alone when a base has no pincode', () => { expect(baseLine({ name: 'Coimbatore Hub' })).toBe('Coimbatore Hub'); }); it('should be null when no base is involved', () => { // A hyperlocal parcel has no base. That is the correct answer, not a gap. expect(baseLine(null)).toBeNull(); expect(baseLine({})).toBeNull(); }); }); describe('summariseRouting — everything the drawer renders', () => { it('should carry the base, its address and the next step for a hub-routed parcel', () => { const s = summariseRouting(HUB_ROUTED); expect(s.base).toBe('Coimbatore Hub · 641004'); expect(s.baseAddress).toBe('14 Avinashi Road, Peelamedu, Coimbatore'); expect(s.nextAction).toMatch(/base/i); expect(s.consignmentState).toBe('Created'); expect(s.decided).toBe(true); }); it('should carry no base for a hyperlocal parcel', () => { const s = summariseRouting(HYPERLOCAL); expect(s.base).toBeNull(); expect(s.baseAddress).toBeNull(); expect(s.nextAction).toMatch(/receiver/i); }); it('should surface the received-at time once the parcel is actually inwarded', () => { const s = summariseRouting({ ...HUB_ROUTED, consignment_state: 'Inwarded_at_Hub', next_action: 'handed_to_hub', inwardedat: '2026-09-02T14:22:10Z' }); expect(s.inwardedAt).toBe('2026-09-02T14:22:10Z'); expect(s.nextAction).toMatch(/handed over/i); }); it('should show a dash for a parcel that has no consignment yet', () => { const s = summariseRouting({ ...HUB_ROUTED, consignment_state: '', decided: false }); expect(s.consignmentState).toBe('—'); expect(s.decided).toBe(false); }); it('should be null when the booking carries no routing block, so the panel hides', () => { // An older backend, or a booking fetched before this shipped. The drawer // renders nothing rather than a panel of dashes. expect(summariseRouting(null)).toBeNull(); expect(summariseRouting(undefined)).toBeNull(); }); });