Files
session-notify/tests/unit/toasts.test.js
T

101 lines
3.9 KiB
JavaScript

// The popup stack's lifetime: the user's choice decides it, and "never" really
// means the card waits for the user rather than for a timer.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createStore } from '../../src/client/core/store.js'
import { createToastStack } from '../../src/client/core/toasts.js'
import { createCopy } from '../../src/client/core/copy.js'
import { createTranslator } from '../../src/client/i18n/index.js'
import { TOAST_DURATION_MS } from '../../src/client/constants.js'
const tr = createTranslator({ getSnapshot: () => ({ active: 'zh' }) })
const candidate = { kind: 'completion', sessionId: 's1', title: '标题', detail: '', pendingKind: '' }
/** A stack over a store whose only interesting field is the popup lifetime. */
function stack(dismissAfterMs) {
const store = createStore({ dismissAfterMs, toasts: [] })
return { store, toasts: createToastStack({ store, copy: createCopy(tr) }) }
}
const ids = (store) => store.getSnapshot().toasts.map((toast) => toast.id)
test('a card closes itself after the configured lifetime', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(6000)
toasts.showToast(candidate)
assert.equal(ids(store).length, 1)
context.mock.timers.tick(5999)
assert.equal(ids(store).length, 1, 'the card must outlive its countdown')
context.mock.timers.tick(1)
assert.deepEqual(ids(store), [])
})
test('a lifetime of zero leaves the card until it is dismissed', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(0)
toasts.showToast(candidate)
context.mock.timers.tick(60 * 60 * 1000)
assert.equal(ids(store).length, 1, 'nothing may close a "never" card')
toasts.closeToast(ids(store)[0])
assert.deepEqual(ids(store), [])
})
test('hovering holds the card, and the lifetime chosen while held applies afterwards', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(3000)
toasts.showToast(candidate)
const id = ids(store)[0]
toasts.holdToast(id)
context.mock.timers.tick(30 * 1000)
assert.equal(ids(store).length, 1, 'a held card has no timer left')
// The settings page switches to "never" while the pointer is still on the card.
store.set({ ...store.getSnapshot(), dismissAfterMs: 0 })
toasts.armToast(id)
context.mock.timers.tick(60 * 60 * 1000)
assert.equal(ids(store).length, 1)
})
test('a lifetime change applies to the cards already on screen, but never to a held one', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(30000)
toasts.showToast(candidate)
toasts.showToast({ ...candidate, sessionId: 's2' })
const [newest, oldest] = ids(store)
toasts.holdToast(oldest)
// The settings page shortens the lifetime while both cards are visible.
store.set({ ...store.getSnapshot(), dismissAfterMs: 3000 })
toasts.rearmAll()
context.mock.timers.tick(3000)
assert.deepEqual(ids(store), [oldest], 'the visible card followed the new choice, the held one did not')
toasts.armToast(oldest)
context.mock.timers.tick(3000)
assert.deepEqual(ids(store), [], 'once the pointer leaves, the held card counts down again')
void newest
})
test('a lifetime this version cannot use falls back to the shipped default', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(undefined)
toasts.showToast(candidate)
context.mock.timers.tick(TOAST_DURATION_MS)
assert.deepEqual(ids(store), [])
})
test('the stack never grows past its bound, however long the cards live', (context) => {
context.mock.timers.enable({ apis: ['setTimeout'] })
const { store, toasts } = stack(0)
for (let index = 0; index < 5; index += 1) toasts.showToast({ ...candidate, sessionId: `s${index}` })
assert.equal(ids(store).length, 3)
assert.deepEqual(ids(store), ['dsn-5', 'dsn-4', 'dsn-3'], 'newest first')
})