fix(toast): prefer evicting toasts that have been visible >= 1.5s during a burst

Fixes #80

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-12 20:28:42 -04:00
co-authored by Claude Opus 5
parent e96bd527a0
commit d0017e4a78
2 changed files with 64 additions and 10 deletions
+30
View File
@@ -127,6 +127,36 @@ test('toastQueueAtom keeps a new transient toast even when the cap is full of st
assert.equal(ids.length, 6);
});
// --- #80: eviction prefers toasts that have already been visible a while ---
test('toastQueueAtom prefers evicting an old-enough toast over a fresh one when both exist', (t) => {
t.mock.timers.enable({ apis: ['Date'] });
const store = createStore();
store.set(toastQueueAtom, makeToast('old')); // t = 0
t.mock.timers.tick(2000); // 'old' is now well past the 1.5s min-visible mark
store.set(toastQueueAtom, makeToast('mid')); // t = 2000, too young to prefer
for (let i = 0; i < 4; i += 1) store.set(toastQueueAtom, makeToast(`n${i}`)); // fills to cap + 1
const ids = store.get(toastQueueAtom).map((toast) => toast.id);
// 'old' has been visible long enough to be preferred for eviction over the
// still-young 'mid', even though 'old' isn't the only non-sticky candidate.
assert.ok(!ids.includes('old'));
assert.ok(ids.includes('mid'));
assert.equal(ids.length, 5);
});
test('toastQueueAtom falls back to oldest-first when no toast is old enough yet', () => {
const store = createStore();
// All appended back-to-back within the same tick — none has reached the
// 1.5s min-visible mark, so eviction falls back to plain oldest-first.
for (let i = 0; i < 7; i += 1) store.set(toastQueueAtom, makeToast(`t${i}`));
assert.deepEqual(
store.get(toastQueueAtom).map((toast) => toast.id),
['t2', 't3', 't4', 't5', 't6'],
);
});
test('createDownloadToast: filename in body, no room navigation, unique ids', () => {
const a = createDownloadToast('photo.jpg');
assert.equal(a.displayName, 'Downloaded');