diff --git a/CHANGELOG.md b/CHANGELOG.md index a89eafd9b..edc1cc12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements +- [#413](https://github.com/alleslabs/celatone-frontend/pull/413) Add jest test cases for date utils - [#404](https://github.com/alleslabs/celatone-frontend/pull/404) Use internal navigate instead of app link for block navigation - [#396](https://github.com/alleslabs/celatone-frontend/pull/396) Refactor useConfig, disable wasm related tabs on the public project page - [#392](https://github.com/alleslabs/celatone-frontend/pull/392) Refactor proposal table and fix empty state of the proposal list table diff --git a/package.json b/package.json index 2f55c8500..d1fde484a 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "husky": "^8.0.1", "jest": "^29.5.0", "lint-staged": "^13.0.3", + "mockdate": "^3.0.5", "next-sitemap": "^3.1.25", "prettier": "^2.7.1", "standard-version": "^9.5.0", diff --git a/src/lib/utils/date.test.ts b/src/lib/utils/date.test.ts new file mode 100644 index 000000000..3b3e5af30 --- /dev/null +++ b/src/lib/utils/date.test.ts @@ -0,0 +1,219 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import MockDate from "mockdate"; + +import { + getDefaultDate, + getCurrentDate, + parseDate, + parseDateOpt, + parseDateDefault, + formatUTC, + dateFromNow, + formatSeconds, +} from "./date"; + +const MOCK_CURRENT_ISO = "2026-06-06T06:00:00.000Z"; +const DEFAULT_ISO = "1970-01-01T00:00:00.000Z"; + +beforeEach(() => { + MockDate.set(new Date(MOCK_CURRENT_ISO)); +}); + +afterEach(() => { + MockDate.reset(); +}); + +describe("getDefaultDate", () => { + test("should correctly return default date", () => { + expect(getDefaultDate().toISOString()).toEqual(DEFAULT_ISO); + }); +}); + +describe("getCurrentDate", () => { + test("should correctly return current date", () => { + expect(getCurrentDate().toISOString()).toEqual(MOCK_CURRENT_ISO); + }); +}); + +describe("parseDate", () => { + test("should correctly parse date from string", () => { + let d = "2018"; + expect(parseDate(d).toISOString()).toEqual("2018-01-01T00:00:00.000Z"); + d = "2018-04"; + expect(parseDate(d).toISOString()).toEqual("2018-04-01T00:00:00.000Z"); + d = "2018-04-24"; + expect(parseDate(d).toISOString()).toEqual("2018-04-24T00:00:00.000Z"); + d = "2018-04-24 11:12"; + expect(parseDate(d).toISOString()).toEqual("2018-04-24T11:12:00.000Z"); + d = "2018-05-02 11:12:13"; + expect(parseDate(d).toISOString()).toEqual("2018-05-02T11:12:13.000Z"); + d = "2018-05-02 11:12:13.998"; + expect(parseDate(d).toISOString()).toEqual("2018-05-02T11:12:13.998Z"); + d = "2018036187"; + expect(parseDate(d).toISOString()).toEqual("2018-05-03T15:00:00.000Z"); + }); +}); + +describe("parseDateOpt", () => { + test("should correctly parse date from string", () => { + let d = "2018"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-01-01T00:00:00.000Z"); + d = "2018-04"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-04-01T00:00:00.000Z"); + d = "2018-04-24"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-04-24T00:00:00.000Z"); + d = "2018-04-24 11:12"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-04-24T11:12:00.000Z"); + d = "2018-05-02 11:12:13"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-05-02T11:12:13.000Z"); + d = "2018-05-02 11:12:13.998"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-05-02T11:12:13.998Z"); + d = "2018036187"; + expect(parseDateOpt(d)?.toISOString()).toEqual("2018-05-03T15:00:00.000Z"); + }); + test("should correctly return undefined for undefined/falsy parameter", () => { + // undefined and falsy cases + expect(parseDateOpt(undefined)).toBeUndefined(); + expect(parseDateOpt("")).toBeUndefined(); + }); +}); + +describe("parseDateDefault", () => { + test("should correctly parse date from string", () => { + let d = "2018"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-01-01T00:00:00.000Z" + ); + d = "2018-04"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-04-01T00:00:00.000Z" + ); + d = "2018-04-24"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-04-24T00:00:00.000Z" + ); + d = "2018-04-24 11:12"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-04-24T11:12:00.000Z" + ); + d = "2018-05-02 11:12:13"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-05-02T11:12:13.000Z" + ); + d = "2018-05-02 11:12:13.998"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-05-02T11:12:13.998Z" + ); + d = "2018036187"; + expect(parseDateDefault(d).toISOString()).toEqual( + "2018-05-03T15:00:00.000Z" + ); + }); + test("should correctly return default value for undefined/falsy parameter", () => { + // undefined and falsy cases + expect(parseDateDefault(undefined).toISOString()).toEqual(DEFAULT_ISO); + expect(parseDateDefault("").toISOString()).toEqual(DEFAULT_ISO); + }); +}); + +describe("formatUTC", () => { + test("should correctly format date to UTC string", () => { + let d = parseDate("2018"); + expect(formatUTC(d)).toEqual("Jan 01, 2018, 12:00:00 AM (UTC)"); + d = parseDate("2018-04"); + expect(formatUTC(d)).toEqual("Apr 01, 2018, 12:00:00 AM (UTC)"); + d = parseDate("2018-04-24"); + expect(formatUTC(d)).toEqual("Apr 24, 2018, 12:00:00 AM (UTC)"); + d = parseDate("2018-04-24 11:12"); + expect(formatUTC(d)).toEqual("Apr 24, 2018, 11:12:00 AM (UTC)"); + d = parseDate("2018-05-02 11:12:13"); + expect(formatUTC(d)).toEqual("May 02, 2018, 11:12:13 AM (UTC)"); + d = parseDate("2018-05-02 11:12:13.998"); + expect(formatUTC(d)).toEqual("May 02, 2018, 11:12:13 AM (UTC)"); + d = parseDate("2018036187"); + expect(formatUTC(d)).toEqual("May 03, 2018, 3:00:00 PM (UTC)"); + }); +}); + +describe("dateFromNow", () => { + test("(Past) should correctly get date from now", () => { + // Past + let d = parseDate("2026-06-06T05:59:30.000Z"); + expect(dateFromNow(d)).toEqual("a few seconds ago"); + d = parseDate("2026-06-06T05:59:00.000Z"); + expect(dateFromNow(d)).toEqual("a minute ago"); + d = parseDate("2026-06-06T05:30:00.000Z"); + expect(dateFromNow(d)).toEqual("30 minutes ago"); + d = parseDate("2026-06-06T05:00:00.000Z"); + expect(dateFromNow(d)).toEqual("an hour ago"); + d = parseDate("2026-06-06T00:00:00.000Z"); + expect(dateFromNow(d)).toEqual("6 hours ago"); + d = parseDate("2026-06-05T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("a day ago"); + d = parseDate("2026-05-31T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("6 days ago"); + d = parseDate("2026-05-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("a month ago"); + d = parseDate("2025-12-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("6 months ago"); + d = parseDate("2025-06-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("a year ago"); + d = parseDate("2012-06-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("14 years ago"); + }); + + test("(Future) should correctly get date from now", () => { + // Future + let d = parseDate("2026-06-06T06:00:30.000Z"); + expect(dateFromNow(d)).toEqual("in a few seconds"); + d = parseDate("2026-06-06T06:00:50.000Z"); + expect(dateFromNow(d)).toEqual("in a minute"); + d = parseDate("2026-06-06T06:30:00.000Z"); + expect(dateFromNow(d)).toEqual("in 30 minutes"); + d = parseDate("2026-06-06T07:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in an hour"); + d = parseDate("2026-06-06T12:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in 6 hours"); + d = parseDate("2026-06-07T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in a day"); + d = parseDate("2026-06-12T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in 6 days"); + d = parseDate("2026-07-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in a month"); + d = parseDate("2026-12-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in 6 months"); + d = parseDate("2027-06-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in a year"); + d = parseDate("2040-06-06T06:00:00.000Z"); + expect(dateFromNow(d)).toEqual("in 14 years"); + }); +}); + +describe("formatSeconds", () => { + test("should correctly format seconds", () => { + let s = "1000000s"; + expect(formatSeconds(s)).toEqual("11 days"); + s = "86400s"; + expect(formatSeconds(s)).toEqual("1 day"); + s = "7200s"; + expect(formatSeconds(s)).toEqual("2 hours"); + s = "3600s"; + expect(formatSeconds(s)).toEqual("1 hour"); + s = "120s"; + expect(formatSeconds(s)).toEqual("2 minutes"); + s = "60s"; + expect(formatSeconds(s)).toEqual("1 minute"); + s = "30s"; + expect(formatSeconds(s)).toEqual("30 seconds"); + s = "1s"; + expect(formatSeconds(s)).toEqual("1 second"); + s = "0s"; + expect(formatSeconds(s)).toEqual("0 second"); + }); + test("should correctly return fallback for undefined/NaN/lesser than 0 parameter", () => { + expect(formatSeconds(undefined)).toEqual("N/A"); + expect(formatSeconds("")).toEqual("N/A"); + expect(formatSeconds("ABCDEs")).toEqual("N/A"); + expect(formatSeconds("-1s")).toEqual("N/A"); + }); +}); diff --git a/src/lib/utils/date.ts b/src/lib/utils/date.ts index 89173bbee..70600dd90 100644 --- a/src/lib/utils/date.ts +++ b/src/lib/utils/date.ts @@ -27,9 +27,10 @@ export const formatUTC = (date: Date) => export const dateFromNow = (date: Date) => dayjs.utc(date).fromNow(); export const formatSeconds = (sec: Option) => { - if (!sec) return "N/A"; + if (sec === undefined || Number.isNaN(parseInt(sec, 10))) return "N/A"; const formatSec = big(sec.replace("s", "")); + // TODO: use `pluralize` here switch (true) { case formatSec.gte(86400): { const days = formatSec.div(86400).round(0, 0).toNumber(); @@ -43,7 +44,9 @@ export const formatSeconds = (sec: Option) => { const mins = formatSec.div(60).round(0, 0).toNumber(); return `${mins} minute`.concat(mins > 1 ? "s" : ""); } + case formatSec.lt(0): + return "N/A"; default: - return `${formatSec.toFixed()} seconds`; + return `${formatSec.toFixed()} second`.concat(formatSec.gt(1) ? "s" : ""); } }; diff --git a/yarn.lock b/yarn.lock index 42b92ff7b..794e353a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9781,6 +9781,11 @@ mobx@^6.6.2: resolved "https://registry.npmjs.org/mobx/-/mobx-6.6.2.tgz" integrity sha512-IOpS0bf3+hXIhDIy+CmlNMBfFpAbHS0aVHcNC+xH/TFYEKIIVDKNYRh9eKlXuVfJ1iRKAp0cRVmO145CyJAMVQ== +mockdate@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz"