From 44054cedea3cdd8fa022f2f59b0507bdd032a90d Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Tue, 6 Nov 2018 12:43:25 +0200 Subject: [PATCH 1/6] Updated types to version 3 --- types/lolex/index.d.ts | 247 +++++++++++++++++++++++++++-------------- 1 file changed, 166 insertions(+), 81 deletions(-) diff --git a/types/lolex/index.d.ts b/types/lolex/index.d.ts index 1727c97ad3..7ad5c11898 100644 --- a/types/lolex/index.d.ts +++ b/types/lolex/index.d.ts @@ -1,10 +1,76 @@ -// Type definitions for lolex 2.1 +// Type definitions for lolex 3 // Project: https://github.com/sinonjs/lolex // Definitions by: Wim Looman // Josh Goldberg // Rogier Schouten +// Yishai Zehavi // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/** + * Names of clock methods that may be faked by install. + */ +type FakeMethod = "setTimeout" | "clearTimeout" | "setImmediate" | "clearImmediate" | "setInterval" | "clearInterval" | "Date" | "nextTick" | "hrtime"; + +/** + * Global methods avaliable to every clock and also as standalone methods (inside `timers` global object). + */ +export interface GlobalTimers { + /** + * Schedules a callback to be fired once timeout milliseconds have ticked by. + * + * @param callback Callback to be fired. + * @param timeout How many ticks to wait to run the callback. + * @param args Any extra arguments to pass to the callback. + * @returns Time identifier for cancellation. + */ + setTimeout: (callback: () => void, timeout: number, ...args: any[]) => TTimerId; + + /** + * Clears a timer, as long as it was created using setTimeout. + * + * @param id Timer ID or object. + */ + clearTimeout: (id: TimerId) => void; + + /** + * Schedules a callback to be fired every time timeout milliseconds have ticked by. + * + * @param callback Callback to be fired. + * @param timeout How many ticks to wait between callbacks. + * @param args Any extra arguments to pass to the callback. + * @returns Time identifier for cancellation. + */ + setInterval: (callback: () => void, timeout: number, ...args: any[]) => TTimerId; + + /** + * Clears a timer, as long as it was created using setInterval. + * + * @param id Timer ID or object. + */ + clearInterval: (id: TTimerId) => void; + + /** + * Schedules the callback to be fired once 0 milliseconds have ticked by. + * + * @param callback Callback to be fired. + * @remarks You'll still have to call clock.tick() for the callback to fire. + * @remarks If called during a tick the callback won't fire until 1 millisecond has ticked by. + */ + setImmediate: (callback: () => void) => TTimerId; + + /** + * Clears a timer, as long as it was created using setImmediate. + * + * @param id Timer ID or object. + */ + clearImmediate: (id: TTimerId) => void; + + /** + * Implements the Date object but using this clock to provide the correct time. + */ + Date: typeof Date; +} + /** * Timer object used in node. */ @@ -25,102 +91,47 @@ export interface NodeTimer { */ export type TimerId = number | NodeTimer; -/** - * Lolex clock for a browser environment. - */ -type BrowserClock = LolexClock; - -/** - * Lolex clock for a Node environment. - */ -type NodeClock = LolexClock & { - /** - * Mimicks process.hrtime(). - * - * @param prevTime Previous system time to calculate time elapsed. - * @returns High resolution real time as [seconds, nanoseconds]. - */ - hrtime(prevTime?: [number, number]): [number, number]; -}; - -/** - * Clock object created by lolex. - */ -type Clock = BrowserClock | NodeClock; - -/** - * Names of clock methods that may be faked by install. - */ -type FakeMethod = "setTimeout" | "clearTimeout" | "setImmediate" | "clearImmediate" | "setInterval" | "clearInterval" | "Date" | "nextTick" | "hrtime"; - /** * Controls the flow of time. */ -export interface LolexClock { +export interface LolexClock extends GlobalTimers { /** * Current clock time. */ now: number; /** - * Implements the Date object but using this clock to provide the correct time. + * Don't know what this prop is for, but it was included in the clocks that `createClock` or + * `install` return (it is never used in the code, for now). */ - Date: typeof Date; + timeouts: {}; /** - * Schedules a callback to be fired once timeout milliseconds have ticked by. + * Maximum number of timers that will be run when calling runAll(). + */ + loopLimit: number; + + /** + * Schedule callback to run in the next animation frame. * * @param callback Callback to be fired. - * @param timeout How many ticks to wait to run the callback. - * @param args Any extra arguments to pass to the callback. - * @returns Time identifier for cancellation. + * @returns Request id. */ - setTimeout: (callback: () => any, timeout: number, ...args: any[]) => TTimerId; + requestAnimationFrame: (callback: (time: number) => void) => TTimerId; /** - * Clears a timer, as long as it was created using setTimeout. + * Cancel animation frame request. * - * @param id Timer ID or object. + * @param id The id returned from requestAnimationFrame method. */ - clearTimeout: (id: TTimerId) => void; + cancelAnimationFrame: (id: TTimerId) => void; - /** - * Schedules a callback to be fired every time timeout milliseconds have ticked by. - * - * @param callback Callback to be fired. - * @param timeout How many ticks to wait between callbacks. - * @param args Any extra arguments to pass to the callback. - * @returns Time identifier for cancellation. - */ - setInterval: (callback: () => any, timeout: number, ...args: any[]) => TTimerId; - - /** - * Clears a timer, as long as it was created using setInterval. - * - * @param id Timer ID or object. - */ - clearInterval: (id: TTimerId) => void; - - /** - * Schedules the callback to be fired once 0 milliseconds have ticked by. - * - * @param callback Callback to be fired. - * @remarks You'll still have to call clock.tick() for the callback to fire. - * @remarks If called during a tick the callback won't fire until 1 millisecond has ticked by. - */ - setImmediate: (callback: () => any) => TTimerId; - - /** - * Clears a timer, as long as it was created using setImmediate. - * - * @param id Timer ID or object. - */ - clearImmediate: (id: TTimerId) => void; - - /** - * Simulates process.nextTick(); - */ - nextTick: (callback: () => void) => void; + /** + * Get the number of waiting timers. + * + * @returns number of waiting timers. + */ + countTimers: () => number; /** * Advances the clock to the the moment of the first scheduled timer, firing it. @@ -134,6 +145,11 @@ export interface LolexClock { */ tick: (time: number | string) => void; + /** + * Removes all timers and tick without firing them and restore now to its original value. + */ + reset: () => void; + /** * Runs all pending timers until there are none remaining. * @@ -141,6 +157,11 @@ export interface LolexClock { */ runAll: () => void; + /** + * Advanced the clock to the next animation frame while firing all scheduled callbacks. + */ + runToFrame: () => void; + /** * Takes note of the last scheduled timer when it is run, and advances the clock to * that time firing callbacks as necessary. @@ -154,13 +175,62 @@ export interface LolexClock { * @remarks This affects the current time but it does not in itself cause timers to fire. */ setSystemTime: (now?: number | Date) => void; +} +/** + * Lolex clock for a browser environment. + */ +type BrowserClock = LolexClock & { + /** + * Mimics performance.now(). + */ + performance: { + now: () => number; + } +}; + +/** + * Lolex clock for a Node environment. + */ +type NodeClock = LolexClock & { + /** + * Mimicks process.hrtime(). + * + * @param prevTime Previous system time to calculate time elapsed. + * @returns High resolution real time as [seconds, nanoseconds]. + */ + hrtime(prevTime?: [number, number]): [number, number]; + + /** + * Mimics process.nextTick() explicitly dropping additional arguments. + */ + queueMicrotask: (callback: () => void) => void; + + /** + * Simulates process.nextTick(). + */ + nextTick: (callback: () => void) => void; + + /** + * Run all pending microtasks scheduled with nextTick. + */ + runMicrotasks: () => void; +}; + +/** + * Clock object created by lolex. + */ +type Clock = BrowserClock | NodeClock; + +type InstalledClock = Clock & { /** * Restores the original methods on the context that was passed to lolex.install, * or the native timers if no context was given. */ uninstall: () => void; -} + + methods: FakeMethod[]; +}; /** * Creates a clock. @@ -174,7 +244,6 @@ export interface LolexClock { */ export declare function createClock(now?: number | Date, loopLimit?: number): TClock; - export interface LolexInstallOpts { /** * Installs lolex onto the specified target context (default: global) @@ -215,6 +284,22 @@ export interface LolexInstallOpts { * * @param now Current time for the clock, as with lolex.createClock(). * @param toFake Names of methods that should be faked. - * @type TClock Type of clock to create. + * @type InstalledClock Type of clock to create. */ -export declare function install(opts?: LolexInstallOpts): TClock; +export declare function install(opts?: LolexInstallOpts): InstalledClock; + +export interface LolexWithContext { + timers: GlobalTimers; + createClock: (now?: number | Date, loopLimit?: number) => TClock; + install: (opts?: LolexInstallOpts) => InstalledClock; + withGlobal: (global: object) => LolexWithContext; +} + +/** + * Apply new context to lolex. + * + * @param global New context to apply like `window` (in browsers) or `global` (in node). + */ +export declare function withGlobal(global: object): LolexWithContext; + +export declare const timers: GlobalTimers; From c96f272c35599ad8f8aca46ad29f92d5da953a45 Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Tue, 6 Nov 2018 13:33:30 +0200 Subject: [PATCH 2/6] Added tests to version 3 --- types/lolex/lolex-tests.ts | 50 ++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/types/lolex/lolex-tests.ts b/types/lolex/lolex-tests.ts index f698eac3cc..56505c62e9 100644 --- a/types/lolex/lolex-tests.ts +++ b/types/lolex/lolex-tests.ts @@ -1,5 +1,17 @@ import lolex = require("lolex"); +const global: lolex.LolexWithContext = lolex.withGlobal({}); +const timers: lolex.GlobalTimers = lolex.timers; + +const lolexTimeout: lolex.TimerId = timers.setTimeout(() => {}, 42); +const lolexInterval: lolex.TimerId = timers.setInterval(() => {}, 42); +const lolexImmediate: lolex.TimerId = timers.setImmediate(() => {}); +const lolexDate: Date = new timers.Date(); + +timers.clearTimeout(lolexTimeout); +timers.clearInterval(lolexInterval); +timers.clearImmediate(lolexImmediate); + let browserClock: lolex.BrowserClock = lolex.createClock() as lolex.BrowserClock; let nodeClock: lolex.NodeClock = lolex.createClock() as lolex.NodeClock; @@ -16,7 +28,7 @@ lolex.createClock(new Date()); lolex.createClock(7, 9001); lolex.createClock(new Date(), 9001); -lolex.install({ +const installedClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: 0, @@ -25,7 +37,7 @@ lolex.install({ toFake: ["setTimeout", "nextTick", "hrtime"] }); -lolex.install({ +lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: new Date(0), @@ -35,7 +47,10 @@ lolex.install({ }); const browserNow: number = browserClock.now; +const browserTimeouts: Object = browserClock.timeouts; +const browserLoopLimit: number = browserClock.loopLimit; const browserDate: Date = new browserClock.Date(); +const browserPerformanceNow: number = browserClock.performance.now(); const nodeNow: number = nodeClock.now; const nodeDate: Date = new nodeClock.Date(); @@ -43,30 +58,45 @@ const nodeDate: Date = new nodeClock.Date(); const browserTimeout: number = browserClock.setTimeout(() => {}, 7); const browserInterval: number = browserClock.setInterval(() => {}, 7); const browserImmediate: number = browserClock.setImmediate(() => {}); +const browserAnimationFrame: number = browserClock.requestAnimationFrame(() => {}); const nodeTimeout: lolex.NodeTimer = nodeClock.setTimeout(() => {}, 7); const nodeInterval: lolex.NodeTimer = nodeClock.setInterval(() => {}, 7); const nodeImmediate: lolex.NodeTimer = nodeClock.setImmediate(() => {}); +const nodeAnimationFrame: lolex.NodeTimer = nodeClock.requestAnimationFrame(() => {}); + +nodeTimeout.ref(); +nodeTimeout.unref(); browserClock.clearTimeout(browserTimeout); browserClock.clearInterval(browserInterval); browserClock.clearImmediate(browserImmediate); +browserClock.cancelAnimationFrame(browserAnimationFrame); nodeClock.clearTimeout(nodeTimeout); nodeClock.clearInterval(nodeInterval); nodeClock.clearImmediate(nodeImmediate); +nodeClock.cancelAnimationFrame(nodeAnimationFrame); browserClock.tick(7); browserClock.tick("08"); nodeClock.tick(7); -nodeClock.tick("08"); +nodeClock.tick("08:03"); browserClock.next(); nodeClock.next(); +browserClock.reset(); +nodeClock.reset(); + browserClock.runAll(); nodeClock.runAll(); +nodeClock.runMicrotasks(); + +browserClock.runToFrame(); +nodeClock.runToFrame(); + browserClock.runToLast(); nodeClock.runToLast(); @@ -79,9 +109,19 @@ nodeClock.setSystemTime(7); nodeClock.setSystemTime(new Date()); nodeClock.nextTick(() => undefined); +nodeClock.queueMicrotask(() => {}); -browserClock.uninstall(); -nodeClock.uninstall(); +const browserTimersCount: number = browserClock.countTimers(); +const nodeTimersCount: number = nodeClock.countTimers(); + +let [secs, nanos] = nodeClock.hrtime([0, 0]); +[secs, nanos] = nodeClock.hrtime(); + +// shows that typescript successfully infer the return values as numbers. +secs.toFixed(); +nanos.toExponential(); + +installedClock.uninstall(); // Clocks should be typed to have unbound method signatures that can be passed around const { clearTimeout } = browserClock; From 56c35cf65676ee1aba392d1a7f2209ae41c63e4a Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Tue, 6 Nov 2018 13:33:30 +0200 Subject: [PATCH 3/6] Added tests to version 3 --- types/lolex/index.d.ts | 4 +-- types/lolex/lolex-tests.ts | 50 ++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/types/lolex/index.d.ts b/types/lolex/index.d.ts index 7ad5c11898..d34f958eb9 100644 --- a/types/lolex/index.d.ts +++ b/types/lolex/index.d.ts @@ -292,7 +292,7 @@ export interface LolexWithContext { timers: GlobalTimers; createClock: (now?: number | Date, loopLimit?: number) => TClock; install: (opts?: LolexInstallOpts) => InstalledClock; - withGlobal: (global: object) => LolexWithContext; + withGlobal: (global: Object) => LolexWithContext; } /** @@ -300,6 +300,6 @@ export interface LolexWithContext { * * @param global New context to apply like `window` (in browsers) or `global` (in node). */ -export declare function withGlobal(global: object): LolexWithContext; +export declare function withGlobal(global: Object): LolexWithContext; export declare const timers: GlobalTimers; diff --git a/types/lolex/lolex-tests.ts b/types/lolex/lolex-tests.ts index f698eac3cc..56505c62e9 100644 --- a/types/lolex/lolex-tests.ts +++ b/types/lolex/lolex-tests.ts @@ -1,5 +1,17 @@ import lolex = require("lolex"); +const global: lolex.LolexWithContext = lolex.withGlobal({}); +const timers: lolex.GlobalTimers = lolex.timers; + +const lolexTimeout: lolex.TimerId = timers.setTimeout(() => {}, 42); +const lolexInterval: lolex.TimerId = timers.setInterval(() => {}, 42); +const lolexImmediate: lolex.TimerId = timers.setImmediate(() => {}); +const lolexDate: Date = new timers.Date(); + +timers.clearTimeout(lolexTimeout); +timers.clearInterval(lolexInterval); +timers.clearImmediate(lolexImmediate); + let browserClock: lolex.BrowserClock = lolex.createClock() as lolex.BrowserClock; let nodeClock: lolex.NodeClock = lolex.createClock() as lolex.NodeClock; @@ -16,7 +28,7 @@ lolex.createClock(new Date()); lolex.createClock(7, 9001); lolex.createClock(new Date(), 9001); -lolex.install({ +const installedClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: 0, @@ -25,7 +37,7 @@ lolex.install({ toFake: ["setTimeout", "nextTick", "hrtime"] }); -lolex.install({ +lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: new Date(0), @@ -35,7 +47,10 @@ lolex.install({ }); const browserNow: number = browserClock.now; +const browserTimeouts: Object = browserClock.timeouts; +const browserLoopLimit: number = browserClock.loopLimit; const browserDate: Date = new browserClock.Date(); +const browserPerformanceNow: number = browserClock.performance.now(); const nodeNow: number = nodeClock.now; const nodeDate: Date = new nodeClock.Date(); @@ -43,30 +58,45 @@ const nodeDate: Date = new nodeClock.Date(); const browserTimeout: number = browserClock.setTimeout(() => {}, 7); const browserInterval: number = browserClock.setInterval(() => {}, 7); const browserImmediate: number = browserClock.setImmediate(() => {}); +const browserAnimationFrame: number = browserClock.requestAnimationFrame(() => {}); const nodeTimeout: lolex.NodeTimer = nodeClock.setTimeout(() => {}, 7); const nodeInterval: lolex.NodeTimer = nodeClock.setInterval(() => {}, 7); const nodeImmediate: lolex.NodeTimer = nodeClock.setImmediate(() => {}); +const nodeAnimationFrame: lolex.NodeTimer = nodeClock.requestAnimationFrame(() => {}); + +nodeTimeout.ref(); +nodeTimeout.unref(); browserClock.clearTimeout(browserTimeout); browserClock.clearInterval(browserInterval); browserClock.clearImmediate(browserImmediate); +browserClock.cancelAnimationFrame(browserAnimationFrame); nodeClock.clearTimeout(nodeTimeout); nodeClock.clearInterval(nodeInterval); nodeClock.clearImmediate(nodeImmediate); +nodeClock.cancelAnimationFrame(nodeAnimationFrame); browserClock.tick(7); browserClock.tick("08"); nodeClock.tick(7); -nodeClock.tick("08"); +nodeClock.tick("08:03"); browserClock.next(); nodeClock.next(); +browserClock.reset(); +nodeClock.reset(); + browserClock.runAll(); nodeClock.runAll(); +nodeClock.runMicrotasks(); + +browserClock.runToFrame(); +nodeClock.runToFrame(); + browserClock.runToLast(); nodeClock.runToLast(); @@ -79,9 +109,19 @@ nodeClock.setSystemTime(7); nodeClock.setSystemTime(new Date()); nodeClock.nextTick(() => undefined); +nodeClock.queueMicrotask(() => {}); -browserClock.uninstall(); -nodeClock.uninstall(); +const browserTimersCount: number = browserClock.countTimers(); +const nodeTimersCount: number = nodeClock.countTimers(); + +let [secs, nanos] = nodeClock.hrtime([0, 0]); +[secs, nanos] = nodeClock.hrtime(); + +// shows that typescript successfully infer the return values as numbers. +secs.toFixed(); +nanos.toExponential(); + +installedClock.uninstall(); // Clocks should be typed to have unbound method signatures that can be passed around const { clearTimeout } = browserClock; From 368c7a78cfba464d7551a27b5bd75fee57e5aa05 Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Thu, 8 Nov 2018 12:50:49 +0200 Subject: [PATCH 4/6] Updated InstalledClock type --- types/lolex/index.d.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/types/lolex/index.d.ts b/types/lolex/index.d.ts index d34f958eb9..35c4880a5b 100644 --- a/types/lolex/index.d.ts +++ b/types/lolex/index.d.ts @@ -222,7 +222,10 @@ type NodeClock = LolexClock & { */ type Clock = BrowserClock | NodeClock; -type InstalledClock = Clock & { +/** + * Additional methods that installed clock have. + */ +type InstalledMethods = { /** * Restores the original methods on the context that was passed to lolex.install, * or the native timers if no context was given. @@ -232,6 +235,13 @@ type InstalledClock = Clock & { methods: FakeMethod[]; }; +/** + * Clock object created by calling `install();`. + * + * @type TClock type of base clock (e.g BrowserClock). + */ +type InstalledClock = TClock & InstalledMethods; + /** * Creates a clock. * @@ -284,14 +294,14 @@ export interface LolexInstallOpts { * * @param now Current time for the clock, as with lolex.createClock(). * @param toFake Names of methods that should be faked. - * @type InstalledClock Type of clock to create. + * @type TClock Type of clock to create. */ -export declare function install(opts?: LolexInstallOpts): InstalledClock; +export declare function install(opts?: LolexInstallOpts): InstalledClock; export interface LolexWithContext { timers: GlobalTimers; createClock: (now?: number | Date, loopLimit?: number) => TClock; - install: (opts?: LolexInstallOpts) => InstalledClock; + install: (opts?: LolexInstallOpts) => InstalledClock; withGlobal: (global: Object) => LolexWithContext; } From d7f6e54c62affd4ede49dbb521e719b9babfa421 Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Thu, 8 Nov 2018 12:51:02 +0200 Subject: [PATCH 5/6] Updated tests --- types/lolex/lolex-tests.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/types/lolex/lolex-tests.ts b/types/lolex/lolex-tests.ts index 56505c62e9..7137ba261d 100644 --- a/types/lolex/lolex-tests.ts +++ b/types/lolex/lolex-tests.ts @@ -28,7 +28,10 @@ lolex.createClock(new Date()); lolex.createClock(7, 9001); lolex.createClock(new Date(), 9001); -const installedClock = lolex.install({ +// showing two ways to specify the exact clock type for `install()` method: + +// first way: passing the exact clock type to `install()`. +const browserInstalledClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: 0, @@ -37,7 +40,8 @@ const installedClock = lolex.install({ toFake: ["setTimeout", "nextTick", "hrtime"] }); -lolex.install({ +// second way: specify type for the clock variable as InstallClock. +const nodeInstalledClock: lolex.InstalledClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: new Date(0), @@ -121,7 +125,11 @@ let [secs, nanos] = nodeClock.hrtime([0, 0]); secs.toFixed(); nanos.toExponential(); -installedClock.uninstall(); +browserInstalledClock.performance.now(); +nodeInstalledClock.nextTick(() => {}); + +browserInstalledClock.uninstall(); +nodeInstalledClock.uninstall(); // Clocks should be typed to have unbound method signatures that can be passed around const { clearTimeout } = browserClock; From 37c539d61955892cdcf7f2539c54b690fa84e106 Mon Sep 17 00:00:00 2001 From: Yishai Zehavi Date: Thu, 8 Nov 2018 12:59:55 +0200 Subject: [PATCH 6/6] Fixed tests --- types/lolex/lolex-tests.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/types/lolex/lolex-tests.ts b/types/lolex/lolex-tests.ts index 7137ba261d..2a01dd5916 100644 --- a/types/lolex/lolex-tests.ts +++ b/types/lolex/lolex-tests.ts @@ -28,9 +28,6 @@ lolex.createClock(new Date()); lolex.createClock(7, 9001); lolex.createClock(new Date(), 9001); -// showing two ways to specify the exact clock type for `install()` method: - -// first way: passing the exact clock type to `install()`. const browserInstalledClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, @@ -40,8 +37,7 @@ const browserInstalledClock = lolex.install({ toFake: ["setTimeout", "nextTick", "hrtime"] }); -// second way: specify type for the clock variable as InstallClock. -const nodeInstalledClock: lolex.InstalledClock = lolex.install({ +const nodeInstalledClock = lolex.install({ advanceTimeDelta: 20, loopLimit: 10, now: new Date(0),