/** * Setup validation. * * The rule every message here follows: say what is wrong AND how to fix it. A * store owner who is told "invalid input" has learned nothing they did not * already know, and the person it fails is the least technical user on the * platform — somebody setting up a shop for the first time. */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { isValid, validateDelivery, validateStoreInfo } from './validation'; const store = (over: Partial[0]> = {}) => validateStoreInfo({ tenantname: 'Testmart', primarycontact: '9876543210', primaryemail: 'owner@testmart.in', address: '1 Test Street', city: 'Coimbatore', postcode: '641001', opentime: '08:00', closetime: '22:00', ...over, }); test('a complete store passes', () => { assert.equal(isValid(store()), true); }); test('every required field is named when missing', () => { const e = store({ tenantname: '', primarycontact: '', address: '', city: '', postcode: '' }); for (const field of ['tenantname', 'primarycontact', 'address', 'city', 'postcode']) { assert.ok(e[field], `${field} was accepted empty`); } }); /* Counting the digits back is the difference between a message somebody can act on and one they argue with. "Please enter a valid phone number" on a number that looks fine is the worst kind of error. */ test('a wrong-length phone says how many digits it got', () => { assert.match(String(store({ primarycontact: '98765' })['primarycontact']), /5 digits/); assert.match(String(store({ postcode: '6410' })['postcode']), /4 digits/); }); // Pasted numbers carry spaces, +91 and brackets. Those are the user's habit, // not an error. test('a formatted phone number is accepted', () => { assert.equal(store({ primarycontact: '+91 98765 43210' })['primarycontact'], undefined); }); test('email is optional but must be usable when given', () => { assert.equal(store({ primaryemail: '' })['primaryemail'], undefined); assert.ok(store({ primaryemail: 'owner@' })['primaryemail']); assert.ok(store({ primaryemail: 'owner.testmart.in' })['primaryemail']); }); // The example from the brief, and a real ordering mistake: a shop that closes // before it opens takes no orders all day and nothing explains why. test('closing time must be after opening time', () => { const e = store({ opentime: '22:00', closetime: '08:00' }); assert.match(String(e['closetime']), /later than opening/); }); /* ── Delivery ────────────────────────────────────────────────────────────── */ const delivery = (over: Partial[0]> = {}) => validateDelivery({ offersDelivery: true, radiusKm: '5', minOrder: '199', charge: '30', freeAbove: '499', mins: '45', ...over, }); test('a complete delivery setup passes', () => { assert.equal(isValid(delivery()), true); }); test('the delivery question itself must be answered', () => { assert.ok(validateDelivery({ offersDelivery: null, radiusKm: '', minOrder: '', charge: '', freeAbove: '', mins: '', })['offersDelivery']); }); /* A shop that does not deliver must not be held up by delivery fields it cannot see. Validating hidden inputs is how a form refuses to submit for reasons nobody can find on screen. */ test('saying no to delivery asks for nothing else', () => { const e = validateDelivery({ offersDelivery: false, radiusKm: '', minOrder: '', charge: '', freeAbove: '', mins: '', }); assert.equal(isValid(e), true); }); test('radius and delivery time are required when delivering', () => { assert.ok(delivery({ radiusKm: '' })['radiusKm']); assert.ok(delivery({ mins: '' })['mins']); assert.ok(delivery({ radiusKm: '0' })['radiusKm']); }); test('the money fields are optional', () => { assert.equal(isValid(delivery({ minOrder: '', charge: '', freeAbove: '' })), true); }); test('negative money is refused', () => { assert.ok(delivery({ charge: '-10' })['charge']); }); /* Free delivery below the minimum order means every order qualifies — the opposite of what the shopkeeper is trying to set up, and it would only be noticed in the takings. */ test('free-delivery threshold below the minimum order is caught', () => { assert.match(String(delivery({ minOrder: '500', freeAbove: '200' })['freeAbove']), /every order/); });