Something went wrong. Try again.
Browser extension: detect and subscribe to standard.site publications on ATProto
Something went wrong. Try again.
5.3 kB · 153 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154// The rule that decides whether a placeholder is ever seen: a load that lands// from the extension's own cache must not draw one, and a load that goes to// the network must. Everything here is timing, so it is tested on a fake clock// rather than through a popup.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'import { LoadTracker, SHOW_AFTER_MS, showsPlaceholder } from './loading'
beforeEach(() => { vi.useFakeTimers()})afterEach(() => { vi.useRealTimers()})
/** A promise this test settles by hand, standing in for a card's `load`. */function deferred() { let settle!: (err?: Error) => void const work = new Promise<void>((resolve, reject) => { settle = (err) => (err ? reject(err) : resolve()) }) return { work, settle }}
/** Let the timers run to `ms`, and the promise callbacks behind them too. */const tick = (ms: number) => vi.advanceTimersByTimeAsync(ms)
describe('showsPlaceholder', () => { it('shows nothing for the first SHOW_AFTER_MS', () => { expect(showsPlaceholder({ startedAt: 0 }, SHOW_AFTER_MS - 1)).toBe(false) })
it('shows once a load has run past the delay', () => { expect(showsPlaceholder({ startedAt: 0 }, SHOW_AFTER_MS)).toBe(true) })
it('shows nothing for a load that has ended, however long it took', () => { expect(showsPlaceholder({ startedAt: 0, endedAt: 9_000 }, 9_001)).toBe(false) })})
describe('LoadTracker', () => { it('never draws for a load that lands from cache', async () => { const redraw = vi.fn() const tracker = new LoadTracker(redraw) const { work, settle } = deferred() tracker.track('owner', work)
await tick(20) settle() await tick(0)
expect(tracker.busy('owner')).toBe(false) expect(tracker.anyPending()).toBe(false) // The redraw scheduled for the delay has to have been called off, or the // popup redraws for a load that finished long before. await tick(SHOW_AFTER_MS * 2) expect(redraw).not.toHaveBeenCalled() })
it('draws once a load crosses the delay, and again when it lands', async () => { const redraw = vi.fn() const tracker = new LoadTracker(redraw) const { work, settle } = deferred() tracker.track('owner', work)
await tick(SHOW_AFTER_MS - 1) expect(tracker.busy('owner')).toBe(false) expect(redraw).not.toHaveBeenCalled()
await tick(1) expect(tracker.busy('owner')).toBe(true) expect(redraw).toHaveBeenCalledTimes(1)
settle() await tick(0) expect(tracker.busy('owner')).toBe(false) // The card redraws for itself when it has an answer, but a load can end // with nothing to draw; the placeholder still has to come down. expect(redraw).toHaveBeenCalledTimes(2) })
it('takes the placeholder down when a load fails', async () => { const redraw = vi.fn() const tracker = new LoadTracker(redraw) const { work, settle } = deferred() tracker.track('subscribers', work)
await tick(SHOW_AFTER_MS) expect(tracker.busy('subscribers')).toBe(true)
settle(new Error('constellation said no')) await tick(0) expect(tracker.busy('subscribers')).toBe(false) })
it('keeps one timer for loads that started at different times', async () => { const redraw = vi.fn() const tracker = new LoadTracker(redraw) const first = deferred() const second = deferred()
tracker.track('owner', first.work) await tick(100) // A second load starting must not push back the first one's placeholder. tracker.track('subscribers', second.work)
await tick(50) expect(tracker.busy('owner')).toBe(true) expect(tracker.busy('subscribers')).toBe(false)
await tick(100) expect(tracker.busy('subscribers')).toBe(true) expect(redraw).toHaveBeenCalledTimes(2) })
it('is never busy for a card that does not load', () => { const tracker = new LoadTracker(vi.fn()) tracker.track('labels', undefined) expect(tracker.busy('labels')).toBe(false) expect(tracker.anyPending()).toBe(false) })
// What `aria-busy` is worth to anything reading it from outside — assistive // technology, and the capture (scripts/capture-status-docs.mjs), which // refuses to shoot a card that has it. The card sets it from anyPending, so // a placeholder on screen with the attribute already gone would be a // screenshot of a half-drawn popup that the capture had been told was done. it('is pending for as long as anything is drawing a placeholder', async () => { const tracker = new LoadTracker(vi.fn()) const fast = deferred() const slow = deferred() tracker.track('owner', fast.work) tracker.track('subscribers', slow.work)
for (const at of [0, SHOW_AFTER_MS - 1, 1, 500]) { await tick(at) const showing = ['owner', 'subscribers'].filter((id) => tracker.busy(id)) if (showing.length > 0) expect(tracker.anyPending()).toBe(true) }
fast.settle() await tick(0) // The slow one is still drawing, so the card is still busy. expect(tracker.busy('subscribers')).toBe(true) expect(tracker.anyPending()).toBe(true)
slow.settle() await tick(0) expect(tracker.busy('subscribers')).toBe(false) expect(tracker.anyPending()).toBe(false) })})