Add dependency-free browser test suite and README
49 tests run in headless Chrome via tests/run.sh: unit tests for the pure modules plus integration tests that drive the real page in an iframe (filtering, URL state, bag, limits, cross-tab sync, checkout, dialogs, accessibility and overflow checks).
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { PRODUCTS } from "../dist/js/catalog.js";
|
||||
import {
|
||||
MAX_QTY,
|
||||
STORAGE_KEY,
|
||||
cartCount,
|
||||
cartLines,
|
||||
cartSubtotal,
|
||||
changeQty,
|
||||
loadCart,
|
||||
sanitizeCart,
|
||||
saveCart,
|
||||
} from "../dist/js/cart.js";
|
||||
import { assert, equal, test } from "./harness.js";
|
||||
|
||||
const ids = PRODUCTS.map((p) => p.id);
|
||||
|
||||
function fakeStorage(initial = {}) {
|
||||
const data = { ...initial };
|
||||
return {
|
||||
data,
|
||||
getItem: (k) => (k in data ? data[k] : null),
|
||||
setItem: (k, v) => void (data[k] = String(v)),
|
||||
removeItem: (k) => void delete data[k],
|
||||
};
|
||||
}
|
||||
const broken = {
|
||||
getItem() { throw new Error("blocked"); },
|
||||
setItem() { throw new Error("quota"); },
|
||||
removeItem() { throw new Error("blocked"); },
|
||||
};
|
||||
|
||||
test("cart: the storage key is unchanged, so existing bags carry over", () => {
|
||||
equal(STORAGE_KEY, "fieldwork-bag-v1");
|
||||
});
|
||||
|
||||
test("cart: sanitizeCart keeps valid lines", () => {
|
||||
equal(sanitizeCart({ pack: 2, mug: 1 }, ids), { pack: 2, mug: 1 });
|
||||
});
|
||||
|
||||
test("cart: sanitizeCart drops invalid quantities and unknown products", () => {
|
||||
const dirty = { pack: -3, mug: 0, cap: 1.5, bottle: "2", lantern: NaN, blanket: null, ghost: 4 };
|
||||
equal(sanitizeCart(dirty, ids), {});
|
||||
equal(sanitizeCart({ pack: Infinity }, ids), {});
|
||||
});
|
||||
|
||||
test("cart: sanitizeCart clamps oversized quantities", () => {
|
||||
equal(sanitizeCart({ pack: 500 }, ids), { pack: MAX_QTY });
|
||||
});
|
||||
|
||||
test("cart: sanitizeCart rejects non-objects and cannot be prototype-polluted", () => {
|
||||
for (const bad of [null, undefined, [], "x", 5, true]) equal(sanitizeCart(bad, ids), {});
|
||||
const polluted = JSON.parse('{"__proto__":{"pack":3},"constructor":{"mug":2}}');
|
||||
equal(sanitizeCart(polluted, ids), {});
|
||||
assert(({}).pack === undefined, "Object.prototype untouched");
|
||||
});
|
||||
|
||||
test("cart: changeQty adds, removes at zero, and clamps to the limit", () => {
|
||||
let cart = changeQty({}, "pack", 1);
|
||||
equal(cart, { pack: 1 });
|
||||
cart = changeQty(cart, "pack", 1);
|
||||
equal(cart, { pack: 2 });
|
||||
cart = changeQty(cart, "pack", -2);
|
||||
equal(cart, {}, "line removed at zero");
|
||||
equal(changeQty({}, "pack", -1), {}, "cannot go negative");
|
||||
equal(changeQty({ pack: MAX_QTY }, "pack", 1), { pack: MAX_QTY }, "limit holds");
|
||||
});
|
||||
|
||||
test("cart: changeQty is immutable", () => {
|
||||
const original = Object.freeze({ pack: 1 });
|
||||
const next = changeQty(original, "mug", 1);
|
||||
equal(original, { pack: 1 });
|
||||
assert(next !== original);
|
||||
});
|
||||
|
||||
test("cart: counts, lines and subtotal (exact, in cents, in catalogue order)", () => {
|
||||
const cart = { mug: 2, pack: 1 }; // inserted out of catalogue order
|
||||
equal(cartCount(cart), 3);
|
||||
equal(cartLines(cart, PRODUCTS).map((l) => [l.product.id, l.qty, l.total]), [
|
||||
["pack", 1, 8600],
|
||||
["mug", 2, 4800],
|
||||
]);
|
||||
equal(cartSubtotal(cart, PRODUCTS), 13400);
|
||||
equal(cartSubtotal({}, PRODUCTS), 0);
|
||||
});
|
||||
|
||||
test("cart: loadCart reads, sanitises, and survives bad storage", () => {
|
||||
equal(loadCart(fakeStorage({ [STORAGE_KEY]: '{"pack":2}' }), ids), { pack: 2 });
|
||||
equal(loadCart(fakeStorage({ [STORAGE_KEY]: "not json{" }), ids), {}, "corrupt JSON");
|
||||
equal(loadCart(fakeStorage(), ids), {}, "nothing saved");
|
||||
equal(loadCart(broken, ids), {}, "storage throws");
|
||||
equal(loadCart(null, ids), {}, "storage unavailable");
|
||||
});
|
||||
|
||||
test("cart: saveCart writes, clears when empty, and never throws", () => {
|
||||
const storage = fakeStorage();
|
||||
saveCart(storage, { pack: 1 });
|
||||
equal(JSON.parse(storage.data[STORAGE_KEY]), { pack: 1 });
|
||||
saveCart(storage, {});
|
||||
assert(!(STORAGE_KEY in storage.data), "empty cart removes the key");
|
||||
saveCart(broken, { pack: 1 });
|
||||
saveCart(null, { pack: 1 });
|
||||
});
|
||||
Reference in New Issue
Block a user