Major update for v1 and tests

This commit is contained in:
khalid@traclabs.com
2026-05-23 03:28:58 -05:00
parent 628a6765f4
commit b1fb6fa3aa
1748 changed files with 27723 additions and 1854 deletions

107
src/utils/makeId.test.js Normal file
View File

@@ -0,0 +1,107 @@
// Tests for makeElementId — ID generation for design elements.
//
// What this guards
// ────────────────
// makeElementId is called from inside hot paths: every addElement,
// every duplicateElement, every replaceElements iteration during
// session restoration, every template-loading entry. A regression
// that produced colliding ids would cause React key warnings, lost
// drag/selection state when keys collide on re-render, and confusion
// in undo/redo (history snapshots would have two entries with the
// same id).
//
// We test:
// • The id format is grep-able (starts with "element-") so existing
// log filters and the diagnostic-history `summarizeElements`
// helper continue to recognise our ids.
// • Suffix-less ids are unique enough for non-loop callers.
// • Suffix-mixed ids are unique even in a tight loop where
// Date.now() can return the same millisecond for every iteration.
// The suffix-uniqueness test is the regression test for the bug the
// suffix parameter was added to fix — see the docblock in makeId.js.
import { makeElementId } from './makeId';
describe('makeElementId', () => {
describe('format', () => {
it('produces an id starting with "element-"', () => {
// Required so the `summarizeElements` helper in useDesignEditor's
// diagnostic logging produces useful output, and so call sites
// that filter logs by `[element-` find what they expect.
expect(makeElementId()).toMatch(/^element-/);
});
it('embeds the timestamp', () => {
// Format: element-<ms-timestamp>-<random>. Pinning the timestamp
// position helps debugging — if an id appears in two different
// snapshots, the timestamps tell you which mount produced it.
const before = Date.now();
const id = makeElementId();
const after = Date.now();
// Extract the timestamp segment.
const match = id.match(/^element-(\d+)-/);
expect(match).not.toBeNull();
const ts = Number(match[1]);
expect(ts).toBeGreaterThanOrEqual(before);
expect(ts).toBeLessThanOrEqual(after);
});
it('embeds the suffix when provided', () => {
// Suffix lands between the timestamp and the random segment, so
// a caller passing a loop index produces something like
// `element-1779491484151-3-ip8fyoztd`. The position is documented
// in the makeId.js docblock and visible in the history-log
// snapshots from the May 22 crop+undo investigation.
const id = makeElementId(3);
expect(id).toMatch(/^element-\d+-3-[a-z0-9]+$/);
});
it('handles string suffixes (not just numeric)', () => {
// The signature accepts `number|string`. We don't currently have
// a caller using a string suffix, but the public type allows it
// so we test that it doesn't break.
const id = makeElementId('slot');
expect(id).toMatch(/^element-\d+-slot-[a-z0-9]+$/);
});
});
describe('uniqueness', () => {
it('two suffix-less calls produce different ids', () => {
// Date.now() may return the same value for two close calls on
// fast machines, but the 9-char random suffix makes a collision
// astronomically unlikely (2^46 entropy per id).
const a = makeElementId();
const b = makeElementId();
expect(a).not.toBe(b);
});
it('a tight loop produces all-unique ids when suffix is used (regression test)', () => {
// The bug that motivated the suffix parameter: a `for` loop
// creating elements in one tick produces multiple ids that
// share the same Date.now() value. Pre-suffix, the random
// segment alone made collisions rare-but-possible; the suffix
// makes them impossible (each iteration has a different
// suffix), even if Math.random somehow returned the same
// sequence twice in a row.
const ids = new Set();
for (let i = 0; i < 1000; i++) {
ids.add(makeElementId(i));
}
expect(ids.size).toBe(1000);
});
it('suffix-less ids in a tight loop are very-likely-unique (statistical, not guaranteed)', () => {
// 1000 ids without suffix. With ~2^46 random entropy, the
// birthday-paradox collision probability at N=1000 is
// approximately N^2 / (2 * 2^46) = 1.4e-8. Astronomically
// unlikely, but pin the statistical expectation here so a
// future regression that, say, dropped Math.random precision
// or hardcoded a constant random suffix gets caught.
const ids = new Set();
for (let i = 0; i < 1000; i++) {
ids.add(makeElementId());
}
expect(ids.size).toBe(1000);
});
});
});