108 lines
4.6 KiB
JavaScript
108 lines
4.6 KiB
JavaScript
// 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);
|
|
});
|
|
});
|
|
});
|