refactor: cleanup schema and types for CustomText (@fehmer) (#6605)

This commit is contained in:
Christian Fehmer 2025-05-27 16:30:49 +02:00 committed by GitHub
parent 1cada77ea8
commit 47de0c8a40
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 59 additions and 67 deletions

View file

@ -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
);
});

View file

@ -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,

View file

@ -16,7 +16,7 @@ function getCheckboxValue(checkbox: string): boolean {
type SharedTestSettings = [
Mode | null,
Mode2<Mode> | null,
CustomText.CustomTextData | null,
CustomText.CustomTextSettings | null,
boolean | null,
boolean | null,
string | null,

View file

@ -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<typeof CustomTextSettingsSchema>;
@ -139,17 +136,8 @@ export function setPipeDelimiter(val: boolean): void {
});
}
export type CustomTextData = Omit<CustomTextDataWithTextLen, "textLen"> & {
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[] {

View file

@ -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 = {

View file

@ -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,

View file

@ -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 = {

View file

@ -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 {

View file

@ -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<CustomTextDataWithTextLen, "textLen">,
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);

View file

@ -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;
}
}

View file

@ -30,7 +30,7 @@ export const KeyStatsSchema = z.object({
});
export type KeyStats = z.infer<typeof KeyStatsSchema>;
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<typeof CustomTextSchema>;
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")),