Skip to content

Instantly share code, notes, and snippets.

@groupsky
Last active September 10, 2020 16:56
Show Gist options
  • Save groupsky/28446ef02590043f78a53eaea99256de to your computer and use it in GitHub Desktop.
Save groupsky/28446ef02590043f78a53eaea99256de to your computer and use it in GitHub Desktop.
Allow to manually trigger specific timers
import MockTimer from './MockTimer.js'
const timer1 = new MockTimer(100)
const timer2 = new MockTimer(200)
// timeout are numbers and can be compared
assert(timer1.timeout > 0)
// and computed
assert(timer1.timeout + 1 > timer1.timeout)
const calls = []
setTimeout(() => calls.push('A'), timer1.timeout)
const timerB = setTimeout(() => calls.push('B'), timer2.timeout)
setTimeout(() => calls.push('C'), 100)
// lets wait for all timers to expire
await new Promise(resolve => setTimeout(resolve, 200))
// only the timer that is not using the mocked number fired
assert(calls.join() === 'C')
// at any time it can be triggered
timer1.trigger()
assert(calls.join() === 'C,A')
// or cleared the regular way
clearTimeout(timerB)
timer2.trigger()
assert(calls.join() === 'C,A')
import { act } from 'react-dom/test-utils'
class UniqueNumber extends Number {}
/**
* Mock specific timer and allow the others to passthrough normally
*/
export default class MockTimer {
/**
* timeout value to pass to setTimeout as second parameter
* @type (Number)
*/
timeout
pending = []
constructor (timeout = 9999) {
timeout = new UniqueNumber(timeout)
// scope to the instance for faster check
class ClearTimeout {
constructor (fn) { this.fn = fn }
}
global.setTimeout = ((setTimeout) => (fn, tm) => {
if (tm !== this.timeout) {
return setTimeout(fn, this.timeout)
}
this.pending.push(fn)
return new ClearTimeout(fn)
})(global.setTimeout)
global.clearTimeout = (clearTimeout) => (h) => {
if (!(h instanceof ClearTimeout)) {
return clearTimeout(h)
}
const idx = this.pending.indexOf(h.fn)
if (idx === -1) {
throw new Error('multiple calls to clearTimeout with same handle')
}
this.pending.splice(idx, 1)
}
}
/**
* Trigger the last pending callback
* @return {Promise<void>}
*/
async trigger () {
await act(async () => {
const fn = this.pending.pop()
if (fn == null) {
throw new Error('no pending timers')
}
fn()
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment