/** * The fold from the catalogue's 31 categories into the app's ten aisles. * * The stakes here are not cosmetic. `subcategoryid` is the only thing the * customer app groups by, and a category that folds to nothing lands the * product in "Uncategorized" — which is the state the whole platform was in. */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { APP_AISLES, FALLBACK_AISLE_IDS, aisleForCategory, aisleIdForCategory, aisleIdsFrom, } from './appAisle'; import { CATEGORY_REGISTRY, UNKNOWN_CATEGORY } from './productCategory'; const ids = aisleIdsFrom(undefined); test('every one of the 31 categories folds into an aisle', () => { const orphans = CATEGORY_REGISTRY.map((entry) => entry.category).filter( (category) => aisleForCategory(category) === null, ); assert.deepEqual(orphans, [], 'a category with no aisle is a product the app cannot show'); }); // "General" is the ladder saying it does not know. Inventing an aisle for it // would put an unidentified product on a real shelf, which is worse than the // app's own "Uncategorized" heading — that one at least tells the truth. test('the unknown bucket deliberately has no aisle', () => { assert.equal(aisleForCategory(UNKNOWN_CATEGORY), null); assert.equal(aisleIdForCategory(UNKNOWN_CATEGORY, ids), 0); }); test('a name that is not a category at all folds to nothing', () => { assert.equal(aisleForCategory(''), null); assert.equal(aisleForCategory(undefined), null); assert.equal(aisleForCategory('Category 2'), null, 'the old default is not an aisle'); }); test('the things a shopper would look for land where they would look', () => { assert.equal(aisleForCategory('Spices & Masalas'), 'Foodgrains & Pulses'); assert.equal(aisleForCategory('Dairy'), 'Dairy, Deli & Egg'); assert.equal(aisleForCategory('Hair Care'), 'Beauty & Personal Care'); assert.equal(aisleForCategory('Household Cleaning'), 'Hygiene Essentials'); assert.equal(aisleForCategory('Cooking Oils'), 'Oil & Ghee'); assert.equal(aisleForCategory('Biscuits & Cookies'), 'Bakes & Nuts'); }); // The registry writes an en-dash; a sheet typed by hand will not. Two strings // that read the same must not reach different aisles. test('dash and spacing variants reach the same aisle', () => { assert.equal(aisleForCategory('Household – Agarbatti'), 'Hygiene Essentials'); assert.equal(aisleForCategory('Household - Agarbatti'), 'Hygiene Essentials'); assert.equal(aisleForCategory('household—agarbatti'), 'Hygiene Essentials'); assert.equal(aisleForCategory(' HEALTH CARE – ANTISEPTIC '), 'Hygiene Essentials'); }); /* ── Ids come from the platform's list, never from the wrong table ────────── */ // `productsubcategories` holds these ten names at 14-23; the app's utils list // holds the same ten at 9-18. Matching on the name is what keeps a product out // of the second set, which the grouping does not read. test('ids are taken from the rows the API returned, by name', () => { const live = aisleIdsFrom([ { subcatid: 15, subcatname: 'Dairy, Deli & Egg', categoryid: 2 }, { subcatid: 21, subcatname: 'Snacks & Drinks', categoryid: 2 }, ]); assert.equal(aisleIdForCategory('Dairy', live), 15); assert.equal(aisleIdForCategory('Chocolates', live), 21); }); // A name the API did not return has no id from it — but the aisle is still one // of the ten, so the last-known id is better than 0, which means "no aisle". test('a missing row falls back to the last known id rather than to zero', () => { const partial = aisleIdsFrom([{ subcatid: 15, subcatname: 'Dairy, Deli & Egg', categoryid: 2 }]); assert.equal(aisleIdForCategory('Cooking Oils', partial), FALLBACK_AISLE_IDS['Oil & Ghee']); }); test('an empty response falls back to the whole known list', () => { const empty = aisleIdsFrom([]); assert.equal(empty.size, APP_AISLES.length); assert.equal(aisleIdForCategory('Eggs', empty), FALLBACK_AISLE_IDS['Dairy, Deli & Egg']); }); test('every aisle has an id, and no two share one', () => { const values = APP_AISLES.map((aisle) => FALLBACK_AISLE_IDS[aisle]); assert.equal(new Set(values).size, values.length); assert.ok(values.every((id) => id > 0)); });