SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,75 @@
import { describe, expect, it } from 'vitest';
import { DebounceMachine } from '../src/notifications/debounce';
describe('DebounceMachine — online↔offline debounce (Uptime-Kuma semantics)', () => {
it('a cold online (no prior offline) fires nothing', () => {
const m = new DebounceMachine();
expect(m.onOnline('u')).toEqual({ kind: 'none' });
});
it('an offline that self-heals within the window fires nothing', () => {
const m = new DebounceMachine();
expect(m.onOffline('u', 30)).toEqual({ kind: 'schedule', delayMs: 30_000 });
expect(m.onOnline('u')).toEqual({ kind: 'cancel' });
// back to baseline — a later offline schedules fresh
expect(m.onOffline('u', 30)).toEqual({ kind: 'schedule', delayMs: 30_000 });
});
it('an offline that survives the window fires offline, then recovery fires online (paired)', () => {
const m = new DebounceMachine();
expect(m.onOffline('u', 30)).toEqual({ kind: 'schedule', delayMs: 30_000 });
expect(m.onTimerElapsed('u')).toEqual({ kind: 'emit', event: 'offline' });
expect(m.onOnline('u')).toEqual({ kind: 'emit', event: 'online' });
// after recovery completes, the next online is cold again
expect(m.onOnline('u')).toEqual({ kind: 'none' });
});
it('debounceSeconds<=0 fires offline immediately', () => {
const m = new DebounceMachine();
expect(m.onOffline('u', 0)).toEqual({ kind: 'emit', event: 'offline' });
expect(m.onOnline('u')).toEqual({ kind: 'emit', event: 'online' });
});
it('treats a non-finite debounceSeconds as no-debounce (fires immediately)', () => {
const m = new DebounceMachine();
expect(m.onOffline('u', Number.NaN)).toEqual({ kind: 'emit', event: 'offline' });
});
it('a second offline while pending is a no-op', () => {
const m = new DebounceMachine();
expect(m.onOffline('u', 30)).toEqual({ kind: 'schedule', delayMs: 30_000 });
expect(m.onOffline('u', 30)).toEqual({ kind: 'none' });
});
it('a second offline after one already fired is a no-op', () => {
const m = new DebounceMachine();
m.onOffline('u', 0); // fires offline
expect(m.onOffline('u', 0)).toEqual({ kind: 'none' });
});
it('a timer elapsing after a cancel is a no-op (stale timer)', () => {
const m = new DebounceMachine();
m.onOffline('u', 30); // schedule
m.onOnline('u'); // cancel
expect(m.onTimerElapsed('u')).toEqual({ kind: 'none' });
});
it('tracks UINs independently', () => {
const m = new DebounceMachine();
expect(m.onOffline('a', 0)).toEqual({ kind: 'emit', event: 'offline' });
expect(m.onOffline('b', 30)).toEqual({ kind: 'schedule', delayMs: 30_000 });
expect(m.onOnline('b')).toEqual({ kind: 'cancel' });
expect(m.onOnline('a')).toEqual({ kind: 'emit', event: 'online' });
});
it('starts a new debounce cycle after recovery without retaining the old timer state', () => {
const m = new DebounceMachine();
m.onOffline('u', 30);
m.onTimerElapsed('u');
m.onOnline('u');
expect(m.onTimerElapsed('u')).toEqual({ kind: 'none' });
expect(m.onOffline('u', 60)).toEqual({ kind: 'schedule', delayMs: 60_000 });
expect(m.onOnline('u')).toEqual({ kind: 'cancel' });
expect(m.onOnline('u')).toEqual({ kind: 'none' });
});
});