Lock-file determinism guarantees re-bakes do not renumber existing tiles. Atlas_id mismatch is a hard-fail to prevent silent corruption. Deleted tile IDs move to deleted[] and are not recycled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.5 KiB
JavaScript
76 lines
2.5 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const path = require('node:path');
|
|
const { loadLock, assignIds, serializeLock } = require('../src/lock');
|
|
|
|
const EXISTING_LOCK = path.resolve(__dirname, 'fixtures/lock-existing.json');
|
|
|
|
test('lock: load existing', () => {
|
|
const lock = loadLock(EXISTING_LOCK);
|
|
assert.strictEqual(lock.atlas_id, 'small_demo');
|
|
assert.strictEqual(lock.bindings.grass, 1);
|
|
assert.strictEqual(lock.bindings.stone, 2);
|
|
assert.strictEqual(lock.next_id, 3);
|
|
});
|
|
|
|
test('lock: load nonexistent returns null', () => {
|
|
const lock = loadLock('/nonexistent/path.json');
|
|
assert.strictEqual(lock, null);
|
|
});
|
|
|
|
test('lock: assign IDs to fresh sources', () => {
|
|
const sources = [
|
|
{ name: 'apple' },
|
|
{ name: 'banana' },
|
|
{ name: 'cherry' },
|
|
];
|
|
const { tiles, lock } = assignIds(sources, null, 'fresh_atlas');
|
|
assert.strictEqual(tiles[0].id, 1);
|
|
assert.strictEqual(tiles[1].id, 2);
|
|
assert.strictEqual(tiles[2].id, 3);
|
|
assert.strictEqual(lock.atlas_id, 'fresh_atlas');
|
|
assert.strictEqual(lock.next_id, 4);
|
|
assert.deepStrictEqual(lock.deleted, []);
|
|
});
|
|
|
|
test('lock: preserve existing bindings on re-bake', () => {
|
|
const existing = loadLock(EXISTING_LOCK);
|
|
const sources = [
|
|
{ name: 'grass' },
|
|
{ name: 'stone' },
|
|
{ name: 'water' }, // new
|
|
];
|
|
const { tiles, lock } = assignIds(sources, existing, 'small_demo');
|
|
const idByName = Object.fromEntries(tiles.map(t => [t.name, t.id]));
|
|
assert.strictEqual(idByName.grass, 1); // preserved
|
|
assert.strictEqual(idByName.stone, 2); // preserved
|
|
assert.strictEqual(idByName.water, 3); // new
|
|
assert.strictEqual(lock.next_id, 4);
|
|
});
|
|
|
|
test('lock: deleted tiles move to deleted[]', () => {
|
|
const existing = loadLock(EXISTING_LOCK);
|
|
const sources = [
|
|
{ name: 'grass' },
|
|
// stone removed
|
|
];
|
|
const { lock } = assignIds(sources, existing, 'small_demo');
|
|
assert.deepStrictEqual(lock.deleted, [2]);
|
|
assert.strictEqual(lock.bindings.stone, undefined);
|
|
assert.strictEqual(lock.bindings.grass, 1);
|
|
});
|
|
|
|
test('lock: atlas_id mismatch -> error', () => {
|
|
const existing = loadLock(EXISTING_LOCK);
|
|
assert.throws(
|
|
() => assignIds([{ name: 'grass' }], existing, 'WRONG_ID'),
|
|
/atlas_id mismatch/,
|
|
);
|
|
});
|
|
|
|
test('lock: serialize is JSON with 2-space indent', () => {
|
|
const lock = { atlas_id: 'x', bindings: { a: 1 }, deleted: [], next_id: 2 };
|
|
const s = serializeLock(lock);
|
|
assert.ok(s.includes(' "atlas_id"'));
|
|
});
|