diff --git a/README.md b/README.md index 31f01f2..7d646e9 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,39 @@ [Github repo here](https://github.com/plandek-utils/ts-time-utils) -small utils for managing Time, supported by Dayjs. It is intended to be used in tests. +small utils for managing Time, supported by `Dayjs`. It is intended to be used in tests. ## Installation `yarn add @plandek-utils/time-utils` or `npm install @plandek-utils/time-utils`. +## Dependencies + +it requires [`timekeeper`](https://www.npmjs.com/package/timekeeper) and [`Dayjs`](https://www.npmjs.com/package/dayjs) + ## Usage -TBD. +we get 2 functions: + +- `freezeTime(time, fn)`: freezes time to the given one, executes the given function, and resets the time before returning the result of that execution. +- `freezeTimeAwait(time, asyncFn)`: same as `freezeTime()` but expects an async function, and it waits for its return before resetting the time + +```typescript +// assume now is "2019-03-21T12:21:13.000Z" + +function renderTime() { + const d = new Date() + console.log(d.toISOString()) +} + +renderTime() // => logs "2019-03-21T12:21:13.000Z" + +const time = new Date("2018-01-02T13:14:15.123Z") +const res = freezeTime(time, () => { renderTime(); return 'blah' }) // => logs "2018-01-02T13:14:15.123Z" +console.log(res) // => logs 'blah' (freezeTime() returns the result of the passed function) + +renderTime() // => logs "2019-03-21T12:21:13.010Z" (time is unfrozen, let's say that a 10ms have passed) +``` ## Development, Commits, versioning and publishing diff --git a/package.json b/package.json index de640cd..ff9c6a3 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,10 @@ "engines": { "node": ">=10.0" }, - "dependencies": {}, + "dependencies": { + "dayjs": "^1.8.16", + "timekeeper": "^2.2.0" + }, "devDependencies": { "@bitjson/npm-scripts-info": "^1.0.0", "@types/inquirer": "^6.5.0", diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts new file mode 100644 index 0000000..56ec3de --- /dev/null +++ b/src/__tests__/index.spec.ts @@ -0,0 +1,43 @@ +import dayjs from "dayjs"; +import { freezeTime, freezeTimeAwait } from ".."; + +function nowString() { + const d = new Date(); + return d.toISOString(); +} + +function dayjsNow() { + return dayjs(new Date()); +} + +const now = dayjsNow().subtract(1, "week"); + +[ + { time: now, title: "works with Dayjs" }, + { time: now.toDate(), title: "works with Date" } +].forEach(({ time, title }) => { + describe(title, () => { + describe("freezeTime()", () => { + it("executes the given expect inside of the function", () => { + freezeTime(time, () => { + expect(nowString()).toEqual(time.toISOString()); + }); + expect.assertions(1); + }); + }); + + describe("freezeTimeAwait()", () => { + async function doShit(): Promise { + return Promise.resolve(dayjsNow().toISOString()); + } + + it("executes the given expect inside of the function", async () => { + await freezeTimeAwait(time, async () => { + const res = await doShit(); + expect(res).toEqual(time.toISOString()); + }); + expect.assertions(1); + }); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index c18d925..81a290b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,58 @@ -export * from "./lib/my-lib"; +import { Dayjs, isDayjs } from "dayjs"; +import timekeeper from "timekeeper"; + +/** + * @internal + */ +function getDate(time: Dayjs | Date) { + return isDayjs(time) ? time.toDate() : time; +} + +/** + * Executes the given function, freezing the time first to the given `time`, and resetting the function execution (uses `timekeeper`) + * + * ```typescript + * // assume now is "2019-03-21T12:21:13.000Z" + * + * function renderTime() { + * const d = new Date() + * console.log(d.toISOString()) + * } + * + * renderTime() // => logs "2019-03-21T12:21:13.000Z" + * + * const time = new Date("2018-01-02T13:14:15.123Z") + * const res = freezeTime(time, () => { renderTime(); return 'blah' }) // => logs "2018-01-02T13:14:15.123Z" + * console.log(res) // => logs 'blah' (freezeTime() returns the result of the passed function) + * + * renderTime() // => logs "2019-03-21T12:21:13.010Z" (time is unfrozen, let's say that a 10ms have passed) + * ``` + * + * @param time the time that will be the new "now" + * @param fn function to execute, on which the time will be frozen as `time` + */ +export function freezeTime(time: Dayjs | Date, fn: () => T): T { + timekeeper.freeze(getDate(time)); + const result = fn(); + timekeeper.reset(); + return result; +} + +/** + * same as `freezeTime()` but it expects an async function. It will await for the function's return and return it before resetting the time + * + * TODO: this needs concurrency testing + * + * @param time the time that will be the new "now" + * @param fn async function to execute, on which the time will be frozen as `time` + * @see freezeTime + */ +export async function freezeTimeAwait( + time: Dayjs | Date, + fn: () => Promise +): Promise { + timekeeper.freeze(getDate(time)); + const result = await fn(); + timekeeper.reset(); + return result; +} diff --git a/src/lib/__tests__/my-lib.spec.ts b/src/lib/__tests__/my-lib.spec.ts deleted file mode 100644 index 851a9d1..0000000 --- a/src/lib/__tests__/my-lib.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { identity } from "../my-lib"; - -describe("identity()", () => { - it("returns the same input given", () => { - expect(identity(null)).toBeNull(); - expect(identity(4)).toBe(4); - expect(identity("whatever ")).toBe("whatever "); - - const obj = { a: 1, b: 2 }; - expect(identity(obj)).toBe(obj); - }); -}); diff --git a/src/lib/my-lib.ts b/src/lib/my-lib.ts deleted file mode 100644 index d700a34..0000000 --- a/src/lib/my-lib.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function identity(a: T): T { - return a; -} - -export {}; diff --git a/src/types/.keep b/src/types/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/yarn.lock b/yarn.lock index b01d1db..05eab06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1489,6 +1489,11 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.8.16: + version "1.8.16" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.16.tgz#2a3771de537255191b947957af2fd90012e71e64" + integrity sha512-XPmqzWz/EJiaRHjBqSJ2s6hE/BUoCIHKgdS2QPtTQtKcS9E4/Qn0WomoH1lXanWCzri+g7zPcuNV4aTZ8PMORQ== + debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -5623,6 +5628,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +timekeeper@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368" + integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"