|
import { StatefulPurgatory } from './stateful-purgatory' |
|
|
|
const PROBE_TIMEOUT_TIME = 1000 |
|
|
|
const EXAMPLE_KEYS = ['foo', 'bar', 'baz'] as const |
|
|
|
// https://jestjs.io/docs/next/timer-mocks |
|
// https://github.com/facebook/jest/blob/fdc74af3/examples/timer/__tests__/infinite_timer_game.test.js |
|
|
|
const createDomTree = (id: string, datasetKey?: string): HTMLDivElement => { |
|
const button = document.createElement('button') |
|
button.textContent = 'Click Me!' |
|
|
|
if (datasetKey) { |
|
button.dataset.key = datasetKey |
|
} |
|
|
|
const rootEl = document.createElement('div') |
|
rootEl.setAttribute('id', id) |
|
rootEl.appendChild(button) |
|
|
|
return rootEl |
|
} |
|
|
|
describe('common/stateful-purgatory', () => { |
|
beforeAll(() => { |
|
jest.useFakeTimers() |
|
}) |
|
|
|
afterAll(() => { |
|
jest.useRealTimers() |
|
}) |
|
|
|
describe('internal state', () => { |
|
let state: StatefulPurgatory<string> |
|
|
|
beforeEach(() => { |
|
state = new StatefulPurgatory() |
|
}) |
|
|
|
describe('coordinating with DOM', () => { |
|
type PleasePurge = { expired: string[] } |
|
const EXPECTED_KEY = EXAMPLE_KEYS[0] // foo |
|
const tree = createDomTree('alpha', EXPECTED_KEY) |
|
document.body.appendChild(tree) |
|
|
|
const spyPleasePurgeEvent = jest.fn() |
|
|
|
const pleasePurgeEventListener = (event: Event) => { |
|
if ('detail' in event) { |
|
const detail = (event as CustomEvent<PleasePurge>).detail |
|
spyPleasePurgeEvent(detail.expired) |
|
} |
|
} |
|
|
|
const probe = jest.fn((recv: Set<string>) => { |
|
const expired = [...recv] |
|
document.dispatchEvent(new CustomEvent<PleasePurge>('please-purge', { detail: { expired } })) |
|
return expired |
|
}) |
|
|
|
const clickHandler = (event: HTMLElementEventMap['click']) => { |
|
if (event.target) { |
|
const dataset = (event.target as HTMLButtonElement).dataset |
|
const { key } = dataset // data-key="foo" |
|
state.yup(key) |
|
} |
|
} |
|
|
|
beforeEach(() => { |
|
state.setDebounce(probe, PROBE_TIMEOUT_TIME) |
|
tree.querySelector('button').addEventListener('click', clickHandler) |
|
jest.advanceTimersByTime(PROBE_TIMEOUT_TIME - 100) |
|
}) |
|
|
|
afterEach(() => { |
|
spyPleasePurgeEvent.mockReset() |
|
}) |
|
|
|
beforeAll(() => { |
|
document.addEventListener('please-purge', pleasePurgeEventListener) |
|
}) |
|
|
|
afterAll(() => { |
|
document.removeEventListener('please-purge', pleasePurgeEventListener) |
|
}) |
|
|
|
test('clicking on an element', () => { |
|
tree.querySelector('button').click() |
|
tree.querySelector('button').click() |
|
jest.advanceTimersByTime(10) |
|
tree.querySelector('button').click() |
|
jest.advanceTimersByTime(PROBE_TIMEOUT_TIME) |
|
expect(state.expired).toHaveLength(1) |
|
expect(probe).toHaveBeenNthCalledWith(1, expect.objectContaining(new Set([EXPECTED_KEY]))) |
|
expect(spyPleasePurgeEvent).toHaveBeenNthCalledWith(1, expect.objectContaining([EXPECTED_KEY])) |
|
}) |
|
}) |
|
|
|
describe('expiration', () => { |
|
// The probe we use so we can watch |
|
const probe = jest.fn() |
|
|
|
beforeEach(() => { |
|
state.setDebounce(probe, PROBE_TIMEOUT_TIME) |
|
// Track a few keys |
|
EXAMPLE_KEYS.forEach(k => state.yup(k)) |
|
jest.advanceTimersByTime(PROBE_TIMEOUT_TIME - 100) |
|
}) |
|
|
|
afterEach(() => { |
|
probe.mockReset() |
|
}) |
|
|
|
test('keys are not expired before timeout', () => { |
|
expect(state.expired).toHaveLength(0) |
|
expect(probe).not.toHaveBeenCalled() |
|
}) |
|
|
|
test('after timeout time, keys apears in expired list', () => { |
|
jest.advanceTimersByTime(100) |
|
expect(state.expired).toHaveLength(EXAMPLE_KEYS.length) |
|
expect(probe).toHaveBeenNthCalledWith(1, expect.objectContaining(new Set([...EXAMPLE_KEYS]))) |
|
}) |
|
|
|
test('after timeout time, only keys not called becomes expired', () => { |
|
// Turns out that all, except the first one we want to omit |
|
const stillBeingWatchedItems = EXAMPLE_KEYS.slice(1) |
|
// Poke only the rest, omit only one ^ (the first) |
|
stillBeingWatchedItems.forEach(k => state.yup(k)) // bar,bazz |
|
jest.advanceTimersByTime(100) |
|
expect(state.expired).toMatchObject([EXAMPLE_KEYS[0]]) // foo |
|
expect(probe).toHaveBeenNthCalledWith(1, expect.objectContaining(new Set([EXAMPLE_KEYS[0]]))) |
|
}) |
|
}) |
|
}) |
|
}) |