diff --git a/frontend/__tests__/utils/url-handler.spec.ts b/frontend/__tests__/utils/url-handler.spec.ts index 744377be9..6321d8131 100644 --- a/frontend/__tests__/utils/url-handler.spec.ts +++ b/frontend/__tests__/utils/url-handler.spec.ts @@ -251,7 +251,7 @@ describe("url-handler", () => { //THEN expect(addNotificationMock).toHaveBeenCalledWith( - `Failed to load test settings from URL: JSON does not match schema: \"0\" invalid enum value. expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'invalidmode', \"1\" needs to be a number or a number represented as a string e.g. \"10\"., \"2.text\" expected array, received string, \"2.mode\" invalid enum value. expected 'repeat' | 'random' | 'shuffle', received 'invalid', \"2.limit\" expected object, received string, \"2.pipeDelimiter\" expected boolean, received string, \"3\" expected boolean, received string, \"4\" expected boolean, received string, \"6\" invalid enum value. expected 'normal' | 'expert' | 'master', received 'invalid', \"7\" invalid input`, + `Failed to load test settings from URL: JSON does not match schema: \"0\" invalid enum value. expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'invalidmode', \"1\" needs to be a number or a number represented as a string e.g. \"10\"., \"2.mode\" invalid enum value. expected 'repeat' | 'random' | 'shuffle', received 'invalid', \"2.pipeDelimiter\" expected boolean, received string, \"2.limit\" expected object, received string, \"2.text\" expected array, received string, \"3\" expected boolean, received string, \"4\" expected boolean, received string, \"6\" invalid enum value. expected 'normal' | 'expert' | 'master', received 'invalid', \"7\" invalid input`, 0 ); }); diff --git a/frontend/src/ts/controllers/input-controller.ts b/frontend/src/ts/controllers/input-controller.ts index 0389f8db4..ce7cdad7a 100644 --- a/frontend/src/ts/controllers/input-controller.ts +++ b/frontend/src/ts/controllers/input-controller.ts @@ -42,6 +42,7 @@ import { getActiveFunboxNames, } from "../test/funbox/list"; import { tryCatchSync } from "@monkeytype/util/trycatch"; +import { canQuickRestart } from "../utils/quick-restart"; let dontInsertSpace = false; let correctShiftUsed = true; @@ -1073,7 +1074,7 @@ $(document).on("keydown", async (event) => { if (Config.mode === "zen") { void TestLogic.finish(); } else if ( - !Misc.canQuickRestart( + !canQuickRestart( Config.mode, Config.words, Config.time, diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts index f86d2abb2..96bd38e26 100644 --- a/frontend/src/ts/modals/share-test-settings.ts +++ b/frontend/src/ts/modals/share-test-settings.ts @@ -16,7 +16,7 @@ function getCheckboxValue(checkbox: string): boolean { type SharedTestSettings = [ Mode | null, Mode2 | null, - CustomText.CustomTextData | null, + CustomText.CustomTextSettings | null, boolean | null, boolean | null, string | null, diff --git a/frontend/src/ts/test/custom-text.ts b/frontend/src/ts/test/custom-text.ts index 844e4ec62..908a94973 100644 --- a/frontend/src/ts/test/custom-text.ts +++ b/frontend/src/ts/test/custom-text.ts @@ -1,12 +1,10 @@ import { CustomTextLimitMode, - CustomTextLimitModeSchema, CustomTextMode, - CustomTextModeSchema, } from "@monkeytype/contracts/schemas/util"; import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; import { z } from "zod"; -import { CustomTextDataWithTextLen } from "@monkeytype/contracts/schemas/results"; +import { CompletedEventCustomTextSchema } from "@monkeytype/contracts/schemas/results"; import { deepClone } from "../utils/misc"; const CustomTextObjectSchema = z.record(z.string(), z.string()); @@ -30,11 +28,10 @@ const customTextLongLS = new LocalStorageWithSchema({ fallback: {}, }); -export const CustomTextSettingsSchema = z.object({ +export const CustomTextSettingsSchema = CompletedEventCustomTextSchema.omit({ + textLen: true, +}).extend({ text: z.array(z.string()).min(1), - mode: CustomTextModeSchema, - limit: z.object({ value: z.number(), mode: CustomTextLimitModeSchema }), - pipeDelimiter: z.boolean(), }); export type CustomTextSettings = z.infer; @@ -139,17 +136,8 @@ export function setPipeDelimiter(val: boolean): void { }); } -export type CustomTextData = Omit & { - text: string[]; -}; - -export function getData(): CustomTextData { - return { - text: getText(), - mode: getMode(), - limit: getLimit(), - pipeDelimiter: getPipeDelimiter(), - }; +export function getData(): CustomTextSettings { + return customTextSettings.get(); } export function getCustomText(name: string, long = false): string[] { diff --git a/frontend/src/ts/test/practise-words.ts b/frontend/src/ts/test/practise-words.ts index 7e7cbb0e4..b527ac5ba 100644 --- a/frontend/src/ts/test/practise-words.ts +++ b/frontend/src/ts/test/practise-words.ts @@ -11,7 +11,7 @@ type Before = { mode: Mode | null; punctuation: boolean | null; numbers: boolean | null; - customText: CustomText.CustomTextData | null; + customText: CustomText.CustomTextSettings | null; }; export const before: Before = { diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index f79abc773..89054021d 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -41,6 +41,7 @@ import { getActiveFunboxes, isFunboxActiveWithProperty } from "./funbox/list"; import { getFunbox } from "@monkeytype/funbox"; import { SnapshotUserTag } from "../constants/default-snapshot"; import { Language } from "@monkeytype/contracts/schemas/languages"; +import { canQuickRestart as canQuickRestartFn } from "../utils/quick-restart"; let result: CompletedEvent; let maxChartVal: number; @@ -966,7 +967,7 @@ export async function update( Misc.applyReducedMotion(125) ); - const canQuickRestart = Misc.canQuickRestart( + const canQuickRestart = canQuickRestartFn( Config.mode, Config.words, Config.time, diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 5e75590a5..e128c3b4f 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -62,7 +62,7 @@ import { QuoteLength } from "@monkeytype/contracts/schemas/configs"; import { Mode } from "@monkeytype/contracts/schemas/shared"; import { CompletedEvent, - CustomTextDataWithTextLen, + CompletedEventCustomText, } from "@monkeytype/contracts/schemas/results"; import * as XPBar from "../elements/xp-bar"; import { @@ -78,6 +78,7 @@ import { tryCatch } from "@monkeytype/util/trycatch"; import { captureException } from "../sentry"; import * as Loader from "../elements/loader"; import * as TestInitFailed from "../elements/test-init-failed"; +import { canQuickRestart } from "../utils/quick-restart"; let failReason = ""; const koInputVisual = document.getElementById("koInputVisual") as HTMLElement; @@ -170,7 +171,7 @@ export function restart(options = {} as RestartOptions): void { if (!ManualRestart.get()) { if (Config.mode !== "zen") event?.preventDefault(); if ( - !Misc.canQuickRestart( + !canQuickRestart( Config.mode, Config.words, Config.time, @@ -801,7 +802,7 @@ function buildCompletedEvent( const wpmCons = Numbers.roundTo2(Numbers.kogasa(stddev3 / avg3)); const wpmConsistency = isNaN(wpmCons) ? 0 : wpmCons; - let customText: CustomTextDataWithTextLen | undefined = undefined; + let customText: CompletedEventCustomText | undefined = undefined; if (Config.mode === "custom") { const temp = CustomText.getData(); customText = { diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index f5783d1f7..85db946a0 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -6,8 +6,9 @@ import * as ConfigEvent from "./observables/config-event"; import { debounce, throttle } from "throttle-debounce"; import * as TestUI from "./test/test-ui"; import { get as getActivePage } from "./states/active-page"; -import { canQuickRestart, isDevEnvironment } from "./utils/misc"; +import { isDevEnvironment } from "./utils/misc"; import { isCustomTextLong } from "./states/custom-text-name"; +import { canQuickRestart } from "./utils/quick-restart"; let isPreviewingFont = false; export function previewFontFamily(font: string): void { diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 3eaebfb85..e0f0abf54 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -7,10 +7,7 @@ import { Mode2, PersonalBests, } from "@monkeytype/contracts/schemas/shared"; -import { - CustomTextDataWithTextLen, - Result, -} from "@monkeytype/contracts/schemas/results"; +import { Result } from "@monkeytype/contracts/schemas/results"; import { z } from "zod"; export function whorf(speed: number, wordlen: number): number { @@ -195,39 +192,6 @@ export function isUsernameValid(name: string): boolean { return /^[0-9a-zA-Z_.-]+$/.test(name); } -export function canQuickRestart( - mode: string, - words: number, - time: number, - CustomText: Omit, - customTextIsLong: boolean -): boolean { - const wordsLong = mode === "words" && (words >= 1000 || words === 0); - const timeLong = mode === "time" && (time >= 900 || time === 0); - const customTextLong = mode === "custom" && customTextIsLong; - - const customTextRandomWordsLong = - mode === "custom" && - (CustomText.limit.mode === "word" || CustomText.limit.mode === "section") && - (CustomText.limit.value >= 1000 || CustomText.limit.value === 0); - const customTextRandomTimeLong = - mode === "custom" && - CustomText.limit.mode === "time" && - (CustomText.limit.value >= 900 || CustomText.limit.value === 0); - - if ( - wordsLong || - timeLong || - customTextLong || - customTextRandomWordsLong || - customTextRandomTimeLong - ) { - return false; - } else { - return true; - } -} - export function clearTimeouts(timeouts: (number | NodeJS.Timeout)[]): void { timeouts.forEach((to) => { if (typeof to === "number") clearTimeout(to); diff --git a/frontend/src/ts/utils/quick-restart.ts b/frontend/src/ts/utils/quick-restart.ts new file mode 100644 index 000000000..4bf06b45d --- /dev/null +++ b/frontend/src/ts/utils/quick-restart.ts @@ -0,0 +1,34 @@ +import { CustomTextSettings } from "../test/custom-text"; + +export function canQuickRestart( + mode: string, + words: number, + time: number, + CustomText: CustomTextSettings, + customTextIsLong: boolean +): boolean { + const wordsLong = mode === "words" && (words >= 1000 || words === 0); + const timeLong = mode === "time" && (time >= 900 || time === 0); + const customTextLong = mode === "custom" && customTextIsLong; + + const customTextRandomWordsLong = + mode === "custom" && + (CustomText.limit.mode === "word" || CustomText.limit.mode === "section") && + (CustomText.limit.value >= 1000 || CustomText.limit.value === 0); + const customTextRandomTimeLong = + mode === "custom" && + CustomText.limit.mode === "time" && + (CustomText.limit.value >= 900 || CustomText.limit.value === 0); + + if ( + wordsLong || + timeLong || + customTextLong || + customTextRandomWordsLong || + customTextRandomTimeLong + ) { + return false; + } else { + return true; + } +} diff --git a/packages/contracts/src/schemas/results.ts b/packages/contracts/src/schemas/results.ts index 909eb8a6e..35210513c 100644 --- a/packages/contracts/src/schemas/results.ts +++ b/packages/contracts/src/schemas/results.ts @@ -30,7 +30,7 @@ export const KeyStatsSchema = z.object({ }); export type KeyStats = z.infer; -export const CustomTextSchema = z.object({ +export const CompletedEventCustomTextSchema = z.object({ textLen: z.number().int().nonnegative(), mode: CustomTextModeSchema, pipeDelimiter: z.boolean(), @@ -39,7 +39,9 @@ export const CustomTextSchema = z.object({ value: z.number().nonnegative(), }), }); -export type CustomTextDataWithTextLen = z.infer; +export type CompletedEventCustomText = z.infer< + typeof CompletedEventCustomTextSchema +>; export const CharStatsSchema = z.tuple([ z.number().int().nonnegative(), @@ -120,7 +122,7 @@ export const CompletedEventSchema = ResultBaseSchema.required({ .extend({ charTotal: z.number().int().nonnegative(), challenge: token().max(100).optional(), - customText: CustomTextSchema.optional(), + customText: CompletedEventCustomTextSchema.optional(), hash: token().max(100), keyDuration: z.array(z.number().nonnegative()).or(z.literal("toolong")), keySpacing: z.array(z.number().nonnegative()).or(z.literal("toolong")),