diff --git a/ts/lib/BUILD.bazel b/ts/lib/BUILD.bazel index e3f565b59..627396e19 100644 --- a/ts/lib/BUILD.bazel +++ b/ts/lib/BUILD.bazel @@ -2,6 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library") load("//ts:prettier.bzl", "prettier_test") load("//ts:eslint.bzl", "eslint_test") load("//ts:protobuf.bzl", "protobufjs_library") +load("@npm//jest-cli:index.bzl", "jest_test") # Protobuf ############# @@ -44,7 +45,10 @@ genrule( ts_library( name = "lib", - srcs = glob(["**/*.ts"]) + [":i18n.ts"], + srcs = glob( + ["**/*.ts"], + exclude = ["*.test.ts"], + ) + [":i18n.ts"], data = [ "backend_proto", ], @@ -72,3 +76,31 @@ eslint_test( name = "eslint", srcs = glob(["*.ts"]), ) + +ts_library( + name = "test_lib", + srcs = glob(["*.test.ts"]), + tsconfig = "//ts:tsconfig.json", + deps = [ + ":lib", + "@npm//@types/jest", + ], +) + +jest_test( + name = "test", + args = [ + "--no-cache", + "--no-watchman", + "--ci", + "--colors", + "--config", + "$(location //ts:jest.config.js)", + ], + data = [ + ":test_lib", + "//ts:jest.config.js", + "//ts:package.json", + "@npm//protobufjs", + ], +) diff --git a/ts/lib/time.test.ts b/ts/lib/time.test.ts new file mode 100644 index 000000000..64a86b9de --- /dev/null +++ b/ts/lib/time.test.ts @@ -0,0 +1,25 @@ +import { naturalUnit, naturalWholeUnit, TimespanUnit } from "./time"; + +test("natural unit", () => { + expect(naturalUnit(5)).toBe(TimespanUnit.Seconds); + expect(naturalUnit(59)).toBe(TimespanUnit.Seconds); + expect(naturalUnit(60)).toBe(TimespanUnit.Minutes); + expect(naturalUnit(60 * 60 - 1)).toBe(TimespanUnit.Minutes); + expect(naturalUnit(60 * 60)).toBe(TimespanUnit.Hours); + expect(naturalUnit(60 * 60 * 24)).toBe(TimespanUnit.Days); + expect(naturalUnit(60 * 60 * 24 * 30)).toBe(TimespanUnit.Months); +}); + +test("natural whole unit", () => { + expect(naturalWholeUnit(5)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(59)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(60)).toBe(TimespanUnit.Minutes); + expect(naturalWholeUnit(61)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(90)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(60 * 60 - 1)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(60 * 60 + 1)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(60 * 60)).toBe(TimespanUnit.Hours); + expect(naturalWholeUnit(24 * 60 * 60 - 1)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(24 * 60 * 60 + 1)).toBe(TimespanUnit.Seconds); + expect(naturalWholeUnit(24 * 60 * 60)).toBe(TimespanUnit.Days); +}); diff --git a/ts/lib/time.ts b/ts/lib/time.ts index cd455c4fe..c59e4fd8a 100644 --- a/ts/lib/time.ts +++ b/ts/lib/time.ts @@ -10,7 +10,7 @@ export const DAY = 24.0 * HOUR; export const MONTH = 30.0 * DAY; export const YEAR = 12.0 * MONTH; -enum TimespanUnit { +export enum TimespanUnit { Seconds, Minutes, Hours, @@ -53,23 +53,41 @@ export function naturalUnit(secs: number): TimespanUnit { } } -export function unitAmount(unit: TimespanUnit, secs: number): number { +/// Number of seconds in a given unit. +export function unitSeconds(unit: TimespanUnit): number { switch (unit) { case TimespanUnit.Seconds: - return secs; + return SECOND; case TimespanUnit.Minutes: - return secs / MINUTE; + return MINUTE; case TimespanUnit.Hours: - return secs / HOUR; + return HOUR; case TimespanUnit.Days: - return secs / DAY; + return DAY; case TimespanUnit.Months: - return secs / MONTH; + return MONTH; case TimespanUnit.Years: - return secs / YEAR; + return YEAR; } } +export function unitAmount(unit: TimespanUnit, secs: number): number { + return secs / unitSeconds(unit); +} + +/// Largest unit provided seconds can be divided by without a remainder. +export function naturalWholeUnit(secs: number): TimespanUnit { + let unit = naturalUnit(secs); + while (unit != TimespanUnit.Seconds) { + const amount = Math.round(unitAmount(unit, secs)); + if (Math.abs(secs - amount * unitSeconds(unit)) < Number.EPSILON) { + return unit; + } + unit -= 1; + } + return unit; +} + export function studiedToday(cards: number, secs: number): string { const unit = naturalUnit(secs); const amount = unitAmount(unit, secs);