Files
doormilxpress_astryx/tests/lib/routingSummary.test.js

181 lines
6.5 KiB
JavaScript

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();
});
});