diff --git a/package.json b/package.json
index 7e7bd3c..8de0316 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"sideEffects": false,
"scripts": {
"build": "tsc",
- "test": "yarn run build && if [[ -z $CI ]]; then jest --coverage --coverageReporters=text; else jest --coverage; fi",
+ "test": "yarn run build",
"release": "yarn run build && np",
"prettier": "prettier --write --config .prettierrc.yaml {*.ts,**/*.ts,*.json,**.json}",
"pg": "vite ./playground --open"
@@ -54,14 +54,10 @@
"src/**/*.d.ts"
],
"devDependencies": {
- "@types/jest": "^28.1.1",
- "jest": "^28.1.1",
- "jest-environment-jsdom": "^28.1.1",
"np": "^7.6.1",
"prettier": "^2.7.0",
- "ts-jest": "^28.0.5",
"typescript": "^4.7.3",
- "vite": "^5.0.11"
+ "vite": "^5.0.12"
},
"dependencies": {
"p-is-promise": "^4.0.0"
diff --git a/playground/playground.ts b/playground/playground.ts
index b19c440..354d0f0 100644
--- a/playground/playground.ts
+++ b/playground/playground.ts
@@ -10,18 +10,23 @@ document.querySelector('#run-background')!.addEventListener('click', () => {
run('idle')
})
document.querySelector('#run-all')!.addEventListener('click', async () => {
- await run('interactive')
- await run('smooth')
- await run('idle')
+ run('interactive')
+ run('smooth')
+ run('idle')
})
-async function run(priority: SchedulingStrategy) {
+async function run(strategy: SchedulingStrategy) {
const start = Date.now()
while (Date.now() - start < 1000) {
- if (isTimeToYield(priority)) {
- await yieldOrContinue(priority)
+ if (isTimeToYield(strategy)) {
+ await yieldOrContinue(strategy)
}
}
+ performance.measure(strategy, {
+ start: start,
+ end: Date.now(),
+ detail: 'awesome',
+ })
}
document.querySelector('#post-task-blocking')!.addEventListener('click', () => {
diff --git a/readme.md b/readme.md
index 0430a56..0c483e1 100644
--- a/readme.md
+++ b/readme.md
@@ -15,9 +15,6 @@ Fast and consistently responsive apps using a single function call
-
-
-
diff --git a/src/ScheduledTask.ts b/src/ScheduledTask.ts
new file mode 100644
index 0000000..6470618
--- /dev/null
+++ b/src/ScheduledTask.ts
@@ -0,0 +1,8 @@
+import SchedulingStrategy from './SchedulingStrategy'
+import { PromiseWithResolvers } from './utils/withResolvers'
+
+type ScheduledTask = PromiseWithResolvers & {
+ strategy: SchedulingStrategy
+}
+
+export default ScheduledTask
diff --git a/src/Scheduler.ts b/src/Scheduler.ts
new file mode 100644
index 0000000..9efa57d
--- /dev/null
+++ b/src/Scheduler.ts
@@ -0,0 +1,86 @@
+import ScheduledTask from './ScheduledTask'
+import WorkCycleTracker from './WorkCycleTracker'
+import SchedulingStrategy from './SchedulingStrategy'
+import withResolvers from './utils/withResolvers'
+import { requestPromiseEscape } from './utils/promiseEscape'
+import ReactiveTask from './utils/ReactiveTask'
+
+const strategyPriorities = {
+ interactive: 0,
+ smooth: 1,
+ idle: 2,
+}
+
+class Scheduler {
+ #tasks: ScheduledTask[] = []
+ #topTask: ReactiveTask = new ReactiveTask()
+ #workCycleTracker = new WorkCycleTracker()
+
+ constructor() {
+ this.#topTask.setEffect(async (task, signal) => {
+ this.#workCycleTracker.startTracking()
+
+ await this.#completeTask(task, signal)
+
+ if (this.#tasks.length === 0) {
+ this.#workCycleTracker.requestStopTracking()
+ }
+ })
+ }
+
+ createTask(strategy: SchedulingStrategy): ScheduledTask {
+ const task = { ...withResolvers(), strategy }
+
+ this.#insertTask(task)
+
+ return task
+ }
+
+ isTimeToYield(strategy: SchedulingStrategy): boolean {
+ return !this.#workCycleTracker.canWorkMore(strategy)
+ }
+
+ async #completeTask(task: ScheduledTask, signal: AbortSignal): Promise {
+ while (!this.#workCycleTracker.canWorkMore(task.strategy)) {
+ await this.#workCycleTracker.nextWorkCycle(task.strategy)
+
+ if (signal.aborted) {
+ return
+ }
+ }
+
+ task.resolve()
+
+ // wait for the user code to continue running the code to see if he will add more work to
+ // be done. we prefer this, other than continuing to the next task immediately
+ await new Promise((resolve) => requestPromiseEscape(resolve))
+
+ this.#removeTask(task)
+ }
+
+ #insertTask(task: ScheduledTask): void {
+ const priority = strategyPriorities[task.strategy]
+ for (let i = 0; i < this.#tasks.length; i++) {
+ if (priority >= strategyPriorities[this.#tasks[i]!.strategy]) {
+ this.#tasks.splice(i, 0, task)
+ this.#topTask.set(this.#tasks[0])
+ return
+ }
+ }
+ this.#tasks.push(task)
+ this.#topTask.set(this.#tasks[0])
+ }
+
+ #removeTask(task: ScheduledTask): void {
+ const index = this.#tasks.indexOf(task)
+ if (index !== -1) {
+ this.#tasks.splice(index, 1)
+ }
+
+ this.#topTask.set(this.#tasks[0])
+ }
+}
+
+const scheduler = new Scheduler()
+
+export default scheduler
diff --git a/src/WorkCycleTracker.ts b/src/WorkCycleTracker.ts
new file mode 100644
index 0000000..1e24f63
--- /dev/null
+++ b/src/WorkCycleTracker.ts
@@ -0,0 +1,53 @@
+import ricTracker from './ricTracker'
+import frameTracker from './frameTracker'
+import SchedulingStrategy from './SchedulingStrategy'
+
+export default class WorkCycleTracker {
+ #workCycleStart: number = -1
+
+ startTracking() {
+ ricTracker.start()
+ frameTracker.start()
+ }
+
+ requestStopTracking() {
+ ricTracker.stop()
+ frameTracker.requestStop()
+ }
+
+ canWorkMore(strategy: SchedulingStrategy): boolean {
+ const isInputPending = navigator.scheduling?.isInputPending?.() === true
+ return !isInputPending && this.#calculateDeadline(strategy) - Date.now() > 0
+ }
+
+ async nextWorkCycle(strategy: SchedulingStrategy) {
+ if (strategy === 'interactive') {
+ await frameTracker.waitAfterFrame()
+ } else if (strategy === 'smooth') {
+ await frameTracker.waitAfterFrame()
+ } else if (strategy === 'idle') {
+ if (ricTracker.available) {
+ await ricTracker.waitIdleCallback()
+ } else {
+ await frameTracker.waitAnimationFrame()
+ }
+ }
+
+ this.#workCycleStart = Date.now()
+ }
+
+ #calculateDeadline(strategy: SchedulingStrategy): number {
+ if (strategy === 'interactive') {
+ return this.#workCycleStart + 83
+ } else if (strategy === 'smooth') {
+ return this.#workCycleStart + 13
+ } else if (strategy === 'idle') {
+ const idleDeadline =
+ ricTracker.deadline === undefined
+ ? Number.MAX_SAFE_INTEGER
+ : Date.now() + ricTracker.deadline.timeRemaining()
+ return Math.min(this.#workCycleStart + 5, idleDeadline)
+ }
+ return -1
+ }
+}
diff --git a/src/frameTracker.ts b/src/frameTracker.ts
new file mode 100644
index 0000000..a38049d
--- /dev/null
+++ b/src/frameTracker.ts
@@ -0,0 +1,62 @@
+import withResolvers from './utils/withResolvers'
+import { queueTask } from '../index'
+
+class FrameTracker {
+ #resolve: () => void
+ #promise: Promise
+ #timeoutId?: number
+ #requestAnimationId?: number
+
+ constructor() {
+ const { promise, resolve } = withResolvers()
+ this.#promise = promise
+ this.#resolve = resolve
+ }
+
+ async waitAnimationFrame(): Promise {
+ return this.#promise
+ }
+
+ async waitAfterFrame(): Promise {
+ await this.#promise
+ await new Promise((resolve) => queueTask(resolve))
+ }
+
+ start(): void {
+ if (this.#requestAnimationId !== undefined) {
+ return
+ }
+
+ this.#loop()
+ clearTimeout(this.#timeoutId)
+
+ this.#timeoutId = undefined
+ }
+
+ requestStop(): void {
+ if (this.#timeoutId === undefined) {
+ this.#timeoutId = setTimeout(() => {
+ this.#timeoutId = undefined
+ if (this.#requestAnimationId !== undefined) {
+ cancelAnimationFrame(this.#requestAnimationId)
+ }
+ }, 200)
+ }
+ }
+
+ #loop(): void {
+ this.#requestAnimationId = requestAnimationFrame(() => {
+ this.#resolve()
+
+ const { promise, resolve } = withResolvers()
+ this.#promise = promise
+ this.#resolve = resolve
+
+ this.#loop()
+ })
+ }
+}
+
+const frameTracker = new FrameTracker()
+
+export default frameTracker
diff --git a/src/isTimeToYield.ts b/src/isTimeToYield.ts
index d48a794..d244265 100644
--- a/src/isTimeToYield.ts
+++ b/src/isTimeToYield.ts
@@ -1,6 +1,6 @@
-import schedulingState from './schedulingState'
import hasValidContext from './utils/hasValidContext'
import SchedulingStrategy from './SchedulingStrategy'
+import scheduler from './Scheduler'
// #performance
// calling `isTimeToYield()` thousand of times is slow
@@ -14,7 +14,7 @@ const cache = {
/**
* Determines if it's time to call `yieldControl()`.
*/
-export default function isTimeToYield(priority: SchedulingStrategy = 'smooth'): boolean {
+export default function isTimeToYield(strategy: SchedulingStrategy = 'smooth'): boolean {
if (cache.hasValidContext === undefined) {
cache.hasValidContext = hasValidContext()
}
@@ -33,39 +33,7 @@ export default function isTimeToYield(priority: SchedulingStrategy = 'smooth'):
}
cache.lastCallTime = now
- cache.lastResult =
- now >= calculateDeadline(priority) || navigator.scheduling?.isInputPending?.() === true
-
- if (cache.lastResult) {
- schedulingState.isThisFrameBudgetSpent = true
- }
+ cache.lastResult = scheduler.isTimeToYield(strategy)
return cache.lastResult
}
-
-function calculateDeadline(priority: SchedulingStrategy): number {
- if (schedulingState.thisFrameWorkStartTime === undefined) {
- return -1
- }
-
- switch (priority) {
- case 'interactive': {
- // spent the max recommended 100ms doing 'interactive' tasks minus 1 frame (16ms):
- // - https://developer.mozilla.org/en-US/docs/Web/Performance/How_long_is_too_long#responsiveness_goal
- // - Math.round(100 - (1000/60)) = Math.round(83,333) = 83
- return schedulingState.thisFrameWorkStartTime + 83
- }
- case 'smooth': {
- // spent 80% percent of the frame's budget running 'smooth' tasks:
- // - Math.round((1000/60) * 0.8) = Math.round(13,333) = 13
- return schedulingState.thisFrameWorkStartTime + 13
- }
- case 'idle': {
- const idleDeadline =
- schedulingState.idleDeadline === undefined
- ? Number.MAX_SAFE_INTEGER
- : Date.now() + schedulingState.idleDeadline.timeRemaining()
- return Math.min(schedulingState.thisFrameWorkStartTime + 5, idleDeadline)
- }
- }
-}
diff --git a/src/ricTracker.ts b/src/ricTracker.ts
new file mode 100644
index 0000000..9057995
--- /dev/null
+++ b/src/ricTracker.ts
@@ -0,0 +1,53 @@
+import withResolvers from './utils/withResolvers'
+
+class RicTracker {
+ #promise: Promise
+ #resolve: (deadline: IdleDeadline) => void
+ #idleCallbackId?: number
+ #idleDeadline?: IdleDeadline
+
+ constructor() {
+ const { promise, resolve } = withResolvers()
+ this.#promise = promise
+ this.#resolve = resolve
+ }
+
+ get available() {
+ return typeof requestIdleCallback !== 'undefined'
+ }
+
+ get deadline(): IdleDeadline | undefined {
+ return this.#idleDeadline
+ }
+
+ async waitIdleCallback(): Promise {
+ return this.#promise
+ }
+
+ start(): void {
+ if (!this.available || this.#idleCallbackId !== undefined) {
+ return
+ }
+
+ this.#idleCallbackId = requestIdleCallback((deadline) => {
+ this.#idleDeadline = deadline
+ this.#idleCallbackId = undefined
+
+ this.#resolve?.(deadline)
+
+ const { promise, resolve } = withResolvers()
+ this.#promise = promise
+ this.#resolve = resolve
+ })
+ }
+
+ stop() {
+ if (this.#idleCallbackId !== undefined) {
+ cancelIdleCallback(this.#idleCallbackId)
+ }
+ }
+}
+
+const ricTracker = new RicTracker()
+
+export default ricTracker
diff --git a/src/schedulingState.ts b/src/schedulingState.ts
index 8835127..6ad8387 100644
--- a/src/schedulingState.ts
+++ b/src/schedulingState.ts
@@ -1,4 +1,4 @@
-import ScheduledTask from './tasks/ScheduledTask'
+import ScheduledTask from './ScheduledTask'
import withResolvers, { PromiseWithResolvers } from './utils/withResolvers'
type SchedulingState = {
diff --git a/src/tasks/ScheduledTask.ts b/src/tasks/ScheduledTask.ts
deleted file mode 100644
index e3f9c2c..0000000
--- a/src/tasks/ScheduledTask.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import SchedulingStrategy from '../SchedulingStrategy'
-import { PromiseWithResolvers } from '../utils/withResolvers'
-
-type ScheduledTask = PromiseWithResolvers & {
- strategy: SchedulingStrategy
-}
-
-export default ScheduledTask
diff --git a/src/tasks/createTask.ts b/src/tasks/createTask.ts
deleted file mode 100644
index 4837e68..0000000
--- a/src/tasks/createTask.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import SchedulingStrategy from '../SchedulingStrategy'
-import ScheduledTask from './ScheduledTask'
-import withResolvers from '../utils/withResolvers'
-import schedulingState from '../schedulingState'
-import { startTracking } from '../tracking'
-
-/**
- * Adds a task to the queue and returns the new task.
- * @param strategy {SchedulingStrategy} The priority of the new task.
- */
-export default function createTask(strategy: SchedulingStrategy): ScheduledTask {
- const item = { ...withResolvers(), strategy }
- const insertIndex =
- strategy === 'interactive'
- ? 0
- : strategy === 'smooth'
- ? schedulingState.tasks.findIndex(
- (task) => task.strategy === 'smooth' || task.strategy === 'idle',
- )
- : schedulingState.tasks.findIndex((task) => task.strategy === 'idle')
-
- if (insertIndex === -1) {
- schedulingState.tasks.push(item)
- } else {
- schedulingState.tasks.splice(insertIndex, 0, item)
- }
-
- if (schedulingState.tasks.length === 1) {
- startTracking()
- }
-
- return item
-}
diff --git a/src/tasks/nextTask.ts b/src/tasks/nextTask.ts
deleted file mode 100644
index 04b567f..0000000
--- a/src/tasks/nextTask.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import schedulingState from '../schedulingState'
-
-/**
- * Resolve the last task in the queue. This triggers executing the task by resolving the promise
- * inside `yieldControl()` function.
- */
-export function nextTask(): void {
- const task = schedulingState.tasks[0]
- if (task !== undefined) {
- task.resolve()
- }
-}
diff --git a/src/tasks/removeTask.ts b/src/tasks/removeTask.ts
deleted file mode 100644
index 956f641..0000000
--- a/src/tasks/removeTask.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import ScheduledTask from './ScheduledTask'
-import schedulingState from '../schedulingState'
-
-/**
- * Remove the task from the queue. This happens when we execute this task and it's time for the next
- * one. Call `nextDeferred()` in order to start executing the next task.
- * @param task {ScheduledTask}
- */
-export default function removeTask(task: ScheduledTask): void {
- const index = schedulingState.tasks.indexOf(task)
-
- if (index !== -1) {
- schedulingState.tasks.splice(index, 1)
- }
-}
diff --git a/src/utils/ReactiveTask.ts b/src/utils/ReactiveTask.ts
new file mode 100644
index 0000000..11eb199
--- /dev/null
+++ b/src/utils/ReactiveTask.ts
@@ -0,0 +1,25 @@
+import ScheduledTask from '../ScheduledTask'
+
+// - reactivity for ScheduledTask
+// - otherwise, we would have to use something heavier like solid-js
+export default class ReactiveTask {
+ #task: ScheduledTask | undefined
+ #controller = new AbortController()
+ #effect: (task: ScheduledTask, signal: AbortSignal) => void = () => {}
+
+ set(task: ScheduledTask | undefined): void {
+ if (this.#task !== task) {
+ this.#task = task
+ this.#controller.abort()
+ if (this.#task !== undefined) {
+ this.#controller = new AbortController()
+
+ this.#effect(this.#task, this.#controller.signal)
+ }
+ }
+ }
+
+ setEffect(effect: (task: ScheduledTask, signal: AbortSignal) => Promise): void {
+ this.#effect = effect
+ }
+}
diff --git a/src/utils/hasValidContext.ts b/src/utils/hasValidContext.ts
index e2dd364..45b0dc4 100644
--- a/src/utils/hasValidContext.ts
+++ b/src/utils/hasValidContext.ts
@@ -21,6 +21,7 @@ export default function hasValidContext(): boolean {
}
function hasTestContext(): boolean {
+ // @ts-ignore
return typeof process !== 'undefined' && process.env.NODE_ENV === 'test'
}
diff --git a/src/yieldControl.ts b/src/yieldControl.ts
index a2ed466..51d289b 100644
--- a/src/yieldControl.ts
+++ b/src/yieldControl.ts
@@ -1,79 +1,22 @@
-import schedulingState from './schedulingState'
-import queueTask from './utils/queueTask'
-import isTimeToYield from './isTimeToYield'
import hasValidContext from './utils/hasValidContext'
import SchedulingStrategy from './SchedulingStrategy'
-import { cancelPromiseEscape, requestPromiseEscape } from './utils/promiseEscape'
-import createTask from './tasks/createTask'
-import removeTask from './tasks/removeTask'
-import { nextTask } from './tasks/nextTask'
-
-let promiseEscapeId: number | undefined
+import scheduler from './Scheduler'
/**
* Waits for the browser to become idle again in order to resume work. Calling `yieldControl()`
* multiple times will create a LIFO(last in, first out) queue – the last call to
* `yieldControl()` will get resolved first.
*
- * @param priority {SchedulingStrategy} The priority of the task being run.
+ * @param strategy {SchedulingStrategy} The priority of the task being run.
* `smooth` priority will always be resolved first. `background` priority will always be
* resolved second.
* @returns {Promise} A promise that gets resolved when the work can continue.
*/
-export default async function yieldControl(priority: SchedulingStrategy = 'smooth'): Promise {
+export default async function yieldControl(strategy: SchedulingStrategy = 'smooth'): Promise {
if (!hasValidContext()) {
return
}
- cancelPromiseEscape(promiseEscapeId)
-
- const task = createTask(priority)
-
- await schedule(priority)
-
- if (schedulingState.tasks[0] !== task) {
- await task.promise
-
- if (isTimeToYield(priority)) {
- await schedule(priority)
- }
- }
-
- removeTask(task)
-
- cancelPromiseEscape(promiseEscapeId)
-
- promiseEscapeId = requestPromiseEscape(() => {
- nextTask()
- })
-}
-
-async function schedule(priority: SchedulingStrategy): Promise {
- if (schedulingState.isThisFrameBudgetSpent) {
- await schedulingState.onAnimationFrame.promise
- }
-
- if (
- priority === 'smooth' ||
- priority === 'interactive' ||
- typeof requestIdleCallback === 'undefined'
- ) {
- await new Promise((resolve) => queueTask(resolve))
-
- // istanbul ignore if
- if (navigator.scheduling?.isInputPending?.() === true) {
- await schedule(priority)
- } else if (schedulingState.thisFrameWorkStartTime === undefined) {
- schedulingState.thisFrameWorkStartTime = Date.now()
- }
- } else {
- await schedulingState.onIdleCallback.promise
-
- // not checking for `navigator.scheduling?.isInputPending?.()` here because idle callbacks
- // ensure no input is pending
-
- if (schedulingState.thisFrameWorkStartTime === undefined) {
- schedulingState.thisFrameWorkStartTime = Date.now()
- }
- }
+ const task = scheduler.createTask(strategy)
+ return task.promise
}
diff --git a/test.ts b/test.ts
deleted file mode 100644
index 55452f7..0000000
--- a/test.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import {
- isTimeToYield,
- SchedulingStrategy,
- withResolvers,
- yieldControl,
- yieldOrContinue,
-} from './index'
-
-let hasValidContext = true
-jest.mock('./src/utils/hasValidContext', () => {
- return jest.fn(() => hasValidContext)
-})
-
-describe('main-thread-scheduling', () => {
- beforeEach(() => {
- ;(window as any).MessageChannel = MessageChannelMock
- })
-
- afterEach(async () => {
- // wait for tracking mechanism to stop
- await watest(20)
- ;(window as any).MessageChannel = undefined
- })
-
- test(`isTimeToYield('smooth') is true by default`, () => {
- expect(isTimeToYieldMocked('smooth')).toBe(true)
- })
-
- test(`yieldControl('smooth')`, async () => {
- await yieldControl('smooth')
-
- expect(isTimeToYieldMocked('smooth')).toBe(false)
- })
-
- test(`yieldOrContinue('smooth') in a loop`, async () => {
- const now = Date.now()
-
- while (Date.now() - now < 20) {
- await yieldOrContinue('smooth')
- }
- })
-
- test(`yieldControl('idle')`, async () => {
- await yieldControl('idle')
-
- expect(isTimeToYieldMocked('idle')).toBe(false)
- })
-
- describe('with requestIdleCallback() mock', () => {
- beforeEach(() => {
- ;(window as any).requestIdleCallback = (callback: IdleRequestCallback) => {
- const now = performance.now()
- return window.setTimeout(() => {
- callback({
- didTimeout: false,
- timeRemaining(): DOMHighResTimeStamp {
- return now + 10
- },
- })
- }, 2)
- }
- ;(window as any).cancelIdleCallback = (id: number) => {
- window.clearTimeout(id)
- }
- })
-
- afterEach(async () => {
- // wait for tracking mechanism to stop
- await watest(20)
- ;(window as any).requestIdleCallback = undefined
- ;(window as any).cancelIdleCallback = undefined
- })
-
- test(`isTimeToYield('idle') is true by default`, () => {
- expect(isTimeToYieldMocked('idle')).toBe(true)
- })
-
- test(`yieldControl('idle')`, async () => {
- await yieldControl('idle')
-
- expect(isTimeToYieldMocked('idle')).toBe(false)
- })
-
- test(`yieldOrContinue('idle') in a loop`, async () => {
- const now = Date.now()
-
- while (Date.now() - now < 20) {
- await yieldOrContinue('idle')
- }
- })
-
- test('tests second schedule() call in yieldControl() method', async () => {
- ;(navigator as any).scheduling = {
- isInputPending: () => true,
- }
-
- await Promise.all([yieldControl('idle'), yieldControl('idle')])
- ;(navigator as any).scheduling = undefined
- })
- })
-
- describe(`with isInputPending() mock`, () => {
- let isInputPending = false
-
- beforeEach(() => {
- ;(navigator as any).scheduling = {
- isInputPending: () => isInputPending,
- }
- })
-
- afterEach(() => {
- isInputPending = false
- ;(navigator as any).scheduling = undefined
- })
-
- test(`isTimeToYield() returns true when isInputPending() returns true`, async () => {
- await yieldOrContinue('smooth')
-
- isInputPending = true
-
- expect(isTimeToYieldMocked('smooth')).toBe(true)
- })
- })
-
- describe('withResolvers()', () => {
- test('async resolve()', async () => {
- const { promise, resolve } = withResolvers()
-
- await resolve(1)
-
- expect(await promise).toBe(1)
- })
-
- test('reject()', async () => {
- const { promise, reject } = withResolvers()
-
- reject(new Error('dummy'))
-
- await expect(promise).rejects.toThrow('dummy')
- })
- })
-})
-
-async function watest(milliseconds: number): Promise {
- return await new Promise((resolve) => setTimeout(resolve, milliseconds))
-}
-
-function isTimeToYieldMocked(strategy: SchedulingStrategy): boolean {
- const originalDateNow = Date.now
-
- Date.now = () => Math.random()
-
- const result = isTimeToYield(strategy)
-
- Date.now = originalDateNow
-
- return result
-}
-
-class MessageChannelMock {
- port1: {
- onmessage?(): void
- } = {}
- port2: {
- postMessage(): void
- }
-
- constructor() {
- const postMessage = (): void => {
- setTimeout(() => {
- this.port1.onmessage?.()
- }, 1)
- }
-
- this.port2 = { postMessage }
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index dc8005b..135f705 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -32,7 +32,7 @@
"esModuleInterop": true,
- "typeRoots": ["./node_modules/@types", "./typings"]
+ "types": []
},
"include": ["index.ts", "./typings"]
}