102 lines
3.5 KiB
JavaScript
102 lines
3.5 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { Store } from '../src/store.js';
|
|
import { MS_PER_DAY } from '../src/pure.js';
|
|
import { createLocalStorage } from './localstorage-shim.js';
|
|
|
|
const DAY = MS_PER_DAY;
|
|
const NOW = 1_700_000_000_000;
|
|
|
|
function freshStore() {
|
|
return new Store({ storage: createLocalStorage() });
|
|
}
|
|
|
|
test('Store: starts empty', () => {
|
|
const s = freshStore();
|
|
assert.equal(s.getTarget('strength'), null);
|
|
});
|
|
|
|
test('Store: setTarget validates via parseTarget', () => {
|
|
const s = freshStore();
|
|
assert.equal(s.setTarget('strength', '25M'), true);
|
|
assert.equal(s.getTarget('strength'), 25_000_000);
|
|
});
|
|
|
|
test('Store: setTarget rejects invalid and keeps previous', () => {
|
|
const s = freshStore();
|
|
s.setTarget('strength', 25_000_000);
|
|
assert.equal(s.setTarget('strength', 'abc'), false);
|
|
assert.equal(s.getTarget('strength'), 25_000_000);
|
|
});
|
|
|
|
test('Store: targets are per-attribute', () => {
|
|
const s = freshStore();
|
|
s.setTarget('strength', 25_000_000);
|
|
s.setTarget('speed', 50_000_000);
|
|
assert.equal(s.getTarget('strength'), 25_000_000);
|
|
assert.equal(s.getTarget('speed'), 50_000_000);
|
|
});
|
|
|
|
test('Store: persists across instances via storage', () => {
|
|
const storage = createLocalStorage();
|
|
const a = new Store({ storage });
|
|
a.setTarget('strength', 25_000_000);
|
|
const b = new Store({ storage });
|
|
assert.equal(b.getTarget('strength'), 25_000_000);
|
|
});
|
|
|
|
test('Store: corrupted JSON is wiped with a warning', () => {
|
|
const storage = createLocalStorage();
|
|
storage.setItem('tat.targets', '{not json');
|
|
const warnings = [];
|
|
const s = new Store({ storage, onWarn: (m) => warnings.push(m) });
|
|
assert.equal(s.getTarget('strength'), null);
|
|
assert.equal(warnings.length, 1);
|
|
assert.equal(storage.getItem('tat.targets'), null);
|
|
const warningsBefore = warnings.length;
|
|
new Store({ storage, onWarn: (m) => warnings.push(m) });
|
|
assert.equal(warnings.length, warningsBefore);
|
|
});
|
|
|
|
test('Store: latches off further saves after a storage write failure', () => {
|
|
const data = new Map();
|
|
const throwingStorage = {
|
|
getItem(k) { return data.has(k) ? data.get(k) : null; },
|
|
setItem() { throw new Error('quota exceeded'); },
|
|
removeItem(k) { data.delete(k); },
|
|
clear() { data.clear(); },
|
|
};
|
|
const warnings = [];
|
|
const s = new Store({ storage: throwingStorage, onWarn: (m) => warnings.push(m) });
|
|
assert.equal(s.setTarget('strength', 25_000_000), false);
|
|
assert.equal(s.setTarget('speed', 50_000_000), false);
|
|
assert.equal(warnings.length, 1);
|
|
assert.equal(s.getTarget('strength'), 25_000_000);
|
|
assert.equal(s.getTarget('speed'), 50_000_000);
|
|
});
|
|
|
|
test('Store: recordTrain appends to per-attribute history', () => {
|
|
const s = freshStore();
|
|
s.recordTrain('strength', 247, NOW);
|
|
s.recordTrain('strength', 250, NOW - 1000);
|
|
assert.equal(s.history.strength.length, 2);
|
|
assert.equal(s.history.strength[0].delta, 247);
|
|
});
|
|
|
|
test('Store: recordTrain prunes entries older than 30 days', () => {
|
|
const s = freshStore();
|
|
s.recordTrain('strength', 100, NOW - 31 * DAY);
|
|
s.recordTrain('strength', 200, NOW);
|
|
assert.equal(s.history.strength.length, 1);
|
|
assert.equal(s.history.strength[0].delta, 200);
|
|
});
|
|
|
|
test('Store: getSummary returns computed summary for attribute', () => {
|
|
const s = freshStore();
|
|
s.recordTrain('strength', 247, NOW - 1000);
|
|
s.recordTrain('strength', 247, NOW - 2000);
|
|
const sum = s.getSummary('strength', NOW);
|
|
assert.equal(sum.trainsToday, 2);
|
|
assert.equal(sum.perDay, Math.floor((2 / 7) * 247));
|
|
});
|