adding monkeytype
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled

This commit is contained in:
Benjamin Falch
2026-04-23 13:53:44 +02:00
parent e214a2fd35
commit 2bc741fb78
1930 changed files with 7590652 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
import { describe, it, expect, afterAll, vi } from "vitest";
import { configMetadata } from "../../src/ts/config/metadata";
import { __testing } from "../../src/ts/config/testing";
import { setConfig } from "../../src/ts/config/setters";
import { ConfigKey, Config as ConfigType } from "@monkeytype/schemas/configs";
const { replaceConfig, getConfig } = __testing;
type TestsByConfig<T> = Partial<{
[K in keyof ConfigType]: (T & { value: ConfigType[K] })[];
}>;
describe("ConfigMeta", () => {
afterAll(() => {
replaceConfig({});
vi.resetModules();
});
it("should have changeRequiresRestart defined", () => {
const configsRequiringRestarts = Object.entries(configMetadata)
.filter(([_key, value]) => value.changeRequiresRestart)
.map(([key]) => key)
.sort();
expect(configsRequiringRestarts).toEqual(
[
"punctuation",
"numbers",
"words",
"time",
"mode",
"quoteLength",
"language",
"difficulty",
"minWpmCustomSpeed",
"minWpm",
"minAcc",
"minAccCustom",
"minBurst",
"minBurstCustomSpeed",
"britishEnglish",
"funbox",
"customLayoutfluid",
"strictSpace",
"stopOnError",
"lazyMode",
"layout",
"codeUnindentOnBackspace",
].sort(),
);
});
it("should have triggerResize defined", () => {
const configsWithTriggeResize = Object.entries(configMetadata)
.filter(([_key, value]) => value.triggerResize === true)
.map(([key]) => key)
.sort();
expect(configsWithTriggeResize).toEqual(
[
"fontSize",
"keymapSize",
"maxLineWidth",
"tapeMode",
"tapeMargin",
].sort(),
);
});
describe("overrideValue", () => {
const testCases: TestsByConfig<{
given?: Partial<ConfigType>;
expected: Partial<ConfigType>;
}> = {
punctuation: [
{ value: true, expected: { punctuation: true } },
{
value: true,
given: { mode: "quote" },
expected: { punctuation: false },
},
],
numbers: [
{ value: true, expected: { numbers: true } },
{
value: true,
given: { mode: "quote" },
expected: { numbers: false },
},
],
customLayoutfluid: [
{
value: ["qwerty", "qwerty", "qwertz"],
expected: { customLayoutfluid: ["qwerty", "qwertz"] },
},
],
customPolyglot: [
{
value: ["english", "polish", "english"],
expected: { customPolyglot: ["english", "polish"] },
},
],
keymapSize: [
{ value: 1, expected: { keymapSize: 1 } },
{ value: 1.234, expected: { keymapSize: 1.2 } },
{ value: 0.4, expected: { keymapSize: 0.5 } },
{ value: 3.6, expected: { keymapSize: 3.5 } },
],
customBackground: [
{
value: " https://example.com/test.jpg ",
expected: { customBackground: "https://example.com/test.jpg" },
},
],
accountChart: [
{
value: ["on", "off", "off", "off"],
expected: { accountChart: ["on", "off", "off", "off"] },
},
{
value: ["off", "off", "off", "off"],
given: { accountChart: ["on", "off", "off", "off"] },
expected: { accountChart: ["off", "on", "off", "off"] },
},
{
value: ["off", "off", "on", "on"],
given: { accountChart: ["off", "on", "off", "off"] },
expected: { accountChart: ["on", "off", "on", "on"] },
},
],
};
it.for(
Object.entries(testCases).flatMap(([key, value]) =>
value.flatMap((it) => ({ key: key as ConfigKey, ...it })),
),
)(
`$key value=$value given=$given expect=$expected`,
({ key, value, given, expected }) => {
//GIVEN
replaceConfig(given ?? {});
//WHEN
setConfig(key, value as any);
//THEN
expect(getConfig()).toMatchObject(expected);
},
);
});
describe("isBlocked", () => {
const testCases: TestsByConfig<{
given?: Partial<ConfigType>;
fail?: true;
}> = {
funbox: [
{
value: ["gibberish"],
given: { mode: "quote" },
fail: true,
},
],
showAllLines: [
{ value: true, given: { tapeMode: "off" } },
{ value: false, given: { tapeMode: "word" } },
{ value: true, given: { tapeMode: "word" }, fail: true },
],
monkey: [{ value: false, given: { liveSpeedStyle: "text" } }],
liveSpeedStyle: [
{ value: "mini", given: { monkey: true } },
{ value: "text", given: { monkey: true } },
],
liveAccStyle: [
{ value: "mini", given: { monkey: true } },
{ value: "text", given: { monkey: true } },
],
};
it.for(
Object.entries(testCases).flatMap(([key, value]) =>
value.flatMap((it) => ({ key: key as ConfigKey, ...it })),
),
)(
`$key value=$value given=$given fail=$fail`,
({ key, value, given, fail }) => {
//GIVEN
replaceConfig(given ?? {});
//WHEN
const applied = setConfig(key, value as any);
//THEN
expect(applied).toEqual(!fail);
},
);
});
describe("overrideConfig", () => {
const testCases: TestsByConfig<{
given: Partial<ConfigType>;
expected?: Partial<ConfigType>;
}> = {
mode: [
{ value: "time", given: { numbers: true, punctuation: true } },
{
value: "custom",
given: { numbers: true, punctuation: true },
expected: { numbers: false, punctuation: false },
},
{
value: "quote",
given: { numbers: true, punctuation: true },
expected: { numbers: false, punctuation: false },
},
{
value: "zen",
given: { numbers: true, punctuation: true },
expected: { numbers: false, punctuation: false },
},
],
numbers: [{ value: false, given: { mode: "quote" } }],
freedomMode: [
{
value: false,
given: { confidenceMode: "on" },
expected: { confidenceMode: "on" },
},
{
value: true,
given: { confidenceMode: "on" },
expected: { confidenceMode: "off" },
},
],
stopOnError: [
{
value: "off",
given: { confidenceMode: "on" },
expected: { confidenceMode: "on" },
},
{
value: "word",
given: { confidenceMode: "on" },
expected: { confidenceMode: "off" },
},
],
confidenceMode: [
{
value: "off",
given: { freedomMode: true, stopOnError: "word" },
expected: { freedomMode: true, stopOnError: "word" },
},
{
value: "on",
given: { freedomMode: true, stopOnError: "word" },
expected: { freedomMode: false, stopOnError: "off" },
},
],
monkey: [
{
value: false,
given: { liveSpeedStyle: "text", liveAccStyle: "text" },
expected: {
liveSpeedStyle: "text",
liveAccStyle: "text",
},
},
{
value: true,
given: { liveSpeedStyle: "text", liveAccStyle: "text" },
expected: { liveSpeedStyle: "mini", liveAccStyle: "mini" },
},
],
liveSpeedStyle: [
{
value: "mini",
given: { monkey: true },
expected: { monkey: true },
},
{
value: "text",
given: { monkey: true },
expected: { monkey: false },
},
],
liveAccStyle: [
{
value: "mini",
given: { monkey: true },
expected: { monkey: true },
},
{
value: "text",
given: { monkey: true },
expected: { monkey: false },
},
],
tapeMode: [
{
value: "off",
given: { showAllLines: true },
expected: { showAllLines: true },
},
{
value: "letter",
given: { showAllLines: true },
expected: { showAllLines: false },
},
],
theme: [
{
value: "8008",
given: { customTheme: true },
expected: { customTheme: false },
},
],
keymapLayout: [
{
value: "3l",
given: { keymapMode: "react" },
expected: { keymapMode: "react" },
},
{
value: "3l",
given: { keymapMode: "off" },
expected: { keymapMode: "static" },
},
],
keymapStyle: [
{
value: "alice",
given: { keymapMode: "react" },
expected: { keymapMode: "react" },
},
{
value: "alice",
given: { keymapMode: "off" },
expected: { keymapMode: "static" },
},
],
keymapLegendStyle: [
{
value: "dynamic",
given: { keymapMode: "react" },
expected: { keymapMode: "react" },
},
{
value: "dynamic",
given: { keymapMode: "off" },
expected: { keymapMode: "static" },
},
],
keymapShowTopRow: [
{
value: "always",
given: { keymapMode: "react" },
expected: { keymapMode: "react" },
},
{
value: "always",
given: { keymapMode: "off" },
expected: { keymapMode: "static" },
},
],
keymapSize: [
{
value: 2,
given: { keymapMode: "react" },
expected: { keymapMode: "react" },
},
{
value: 2,
given: { keymapMode: "off" },
expected: { keymapMode: "static" },
},
],
};
it.for(
Object.entries(testCases).flatMap(([key, value]) =>
value.flatMap((it) => ({ key: key as ConfigKey, ...it })),
),
)(
`$key value=$value given=$given expected=$expected`,
({ key, value, given, expected }) => {
//GIVEN
replaceConfig(given);
//WHEN
setConfig(key, value as any);
//THEN
expect(getConfig()).toMatchObject(expected ?? {});
},
);
});
});

View File

@@ -0,0 +1,471 @@
import { describe, it, expect, beforeEach, afterAll, vi } from "vitest";
import * as Config from "../../src/ts/config/setters";
import * as Lifecycle from "../../src/ts/config/lifecycle";
import * as ConfigUtils from "../../src/ts/config/utils";
import { __testing } from "../../src/ts/config/testing";
import * as Misc from "../../src/ts/utils/misc";
import * as Env from "../../src/ts/utils/env";
import {
ConfigKey,
Config as ConfigType,
CaretStyleSchema,
} from "@monkeytype/schemas/configs";
import * as FunboxValidation from "../../src/ts/config/funbox-validation";
import * as ConfigValidation from "../../src/ts/config/validation";
import { configEvent } from "../../src/ts/events/config";
import * as ApeConfig from "../../src/ts/ape/config";
import * as Notifications from "../../src/ts/states/notifications";
const { replaceConfig, getConfig } = __testing;
describe("Config", () => {
const isDevEnvironmentMock = vi.spyOn(Env, "isDevEnvironment");
beforeEach(() => {
isDevEnvironmentMock.mockClear();
replaceConfig({});
});
describe("test with mocks", () => {
const canSetConfigWithCurrentFunboxesMock = vi.spyOn(
FunboxValidation,
"canSetConfigWithCurrentFunboxes",
);
const isConfigValueValidMock = vi.spyOn(
ConfigValidation,
"isConfigValueValid",
);
const dispatchConfigEventMock = vi.spyOn(configEvent, "dispatch");
const dbSaveConfigMock = vi.spyOn(ApeConfig, "saveConfig");
const notificationAddMock = vi.spyOn(
Notifications,
"showNoticeNotification",
);
const miscReloadAfterMock = vi.spyOn(Misc, "reloadAfter");
const miscTriggerResizeMock = vi.spyOn(Misc, "triggerResize");
const mocks = [
canSetConfigWithCurrentFunboxesMock,
isConfigValueValidMock,
dispatchConfigEventMock,
dbSaveConfigMock,
notificationAddMock,
miscReloadAfterMock,
miscTriggerResizeMock,
];
beforeEach(async () => {
vi.useFakeTimers();
mocks.forEach((it) => it.mockClear());
vi.mock("../../src/ts/test/test-state", () => ({
isActive: true,
}));
isConfigValueValidMock.mockReturnValue(true);
canSetConfigWithCurrentFunboxesMock.mockReturnValue(true);
dbSaveConfigMock.mockResolvedValue();
replaceConfig({});
});
afterAll(() => {
mocks.forEach((it) => it.mockRestore());
vi.useRealTimers();
});
beforeEach(() => isDevEnvironmentMock.mockClear());
it("should throw if config key in not found in metadata", () => {
expect(() => {
Config.setConfig("nonExistentKey" as ConfigKey, true);
}).toThrow(`Config metadata for key "nonExistentKey" is not defined.`);
});
it("fails if test is active and funbox no_quit", () => {
//GIVEN
replaceConfig({ funbox: ["no_quit"], numbers: false });
//WHEN
expect(
Config.setConfig("numbers", true, {
nosave: true,
}),
).toBe(false);
//THEN
expect(notificationAddMock).toHaveBeenCalledWith(
"No quit funbox is active. Please finish the test.",
{
important: true,
},
);
});
//TODO isBlocked
it("should fail if config is blocked", () => {
//GIVEN
replaceConfig({ tapeMode: "letter" });
//WHEN / THEN
expect(Config.setConfig("showAllLines", true)).toBe(false);
});
it("disables live text stats when enabling monkey", () => {
//GIVEN
replaceConfig({
liveSpeedStyle: "text",
liveAccStyle: "text",
monkey: false,
});
//WHEN / THEN
expect(Config.setConfig("monkey", true)).toBe(true);
expect(getConfig()).toMatchObject({
monkey: true,
liveSpeedStyle: "mini",
liveAccStyle: "mini",
});
expect(notificationAddMock).not.toHaveBeenCalled();
});
it("disables monkey when enabling live speed text", () => {
//GIVEN
replaceConfig({ monkey: true, liveSpeedStyle: "off" });
//WHEN / THEN
expect(Config.setConfig("liveSpeedStyle", "text")).toBe(true);
expect(getConfig()).toMatchObject({
monkey: false,
liveSpeedStyle: "text",
});
expect(notificationAddMock).not.toHaveBeenCalled();
});
it("disables monkey when enabling live accuracy text", () => {
//GIVEN
replaceConfig({ monkey: true, liveAccStyle: "off" });
//WHEN / THEN
expect(Config.setConfig("liveAccStyle", "text")).toBe(true);
expect(getConfig()).toMatchObject({
monkey: false,
liveAccStyle: "text",
});
expect(notificationAddMock).not.toHaveBeenCalled();
});
it("should use overrideValue", () => {
//WHEN
Config.setConfig("customLayoutfluid", ["3l", "ABNT2", "3l"]);
//THEN
expect(getConfig().customLayoutfluid).toEqual(["3l", "ABNT2"]);
});
it("fails if config is invalid", () => {
//GIVEN
isConfigValueValidMock.mockReturnValue(false);
//WHEN / THEN
expect(Config.setConfig("caretStyle", "banana" as any)).toBe(false);
expect(isConfigValueValidMock).toHaveBeenCalledWith(
"caret style",
"banana",
CaretStyleSchema,
);
});
it("cannot set if funbox disallows", () => {
//GIVEN
canSetConfigWithCurrentFunboxesMock.mockReturnValue(false);
//WHEN / THEN
expect(Config.setConfig("numbers", true)).toBe(false);
});
it("sets overrideConfigs", () => {
//GIVEN
replaceConfig({
confidenceMode: "off",
freedomMode: false, //already set correctly
stopOnError: "letter", //should get updated
});
//WHEN
Config.setConfig("confidenceMode", "max");
//THEN
expect(dispatchConfigEventMock).not.toHaveBeenCalledWith({
key: "freedomMode",
newValue: false,
nosave: true,
previousValue: true,
});
expect(dispatchConfigEventMock).toHaveBeenCalledWith({
key: "stopOnError",
newValue: "off",
nosave: false,
previousValue: "letter",
});
expect(dispatchConfigEventMock).toHaveBeenCalledWith({
key: "confidenceMode",
newValue: "max",
nosave: false,
previousValue: "off",
});
});
it("saves to localstorage if nosave=false", async () => {
//GIVEN
replaceConfig({ numbers: false });
//WHEN
Config.setConfig("numbers", true);
//THEN
//wait for debounce
await vi.advanceTimersByTimeAsync(2500);
//save
expect(dbSaveConfigMock).toHaveBeenCalledWith({ numbers: true });
});
it("saves configOverride values to localstorage if nosave=false", async () => {
//GIVEN
replaceConfig({});
//WHEN
Config.setConfig("minWpmCustomSpeed", 120);
//THEN
//wait for debounce
await vi.advanceTimersByTimeAsync(2500);
//save
expect(dbSaveConfigMock).toHaveBeenCalledWith({
minWpmCustomSpeed: 120,
minWpm: "custom",
});
});
it("does not save to localstorage if nosave=true", async () => {
//GIVEN
replaceConfig({ numbers: false });
//WHEN
Config.setConfig("numbers", true, {
nosave: true,
});
//THEN
//wait for debounce
await vi.advanceTimersByTimeAsync(2500);
expect(dbSaveConfigMock).not.toHaveBeenCalled();
});
it("dispatches event on set", () => {
//GIVEN
replaceConfig({ numbers: false });
//WHEN
Config.setConfig("numbers", true, {
nosave: true,
});
//THEN
expect(dispatchConfigEventMock).toHaveBeenCalledWith({
key: "numbers",
newValue: true,
nosave: true,
previousValue: false,
});
});
it("triggers resize if property is set", () => {
///WHEN
Config.setConfig("maxLineWidth", 50);
expect(miscTriggerResizeMock).toHaveBeenCalled();
});
it("does not triggers resize if property is not set", () => {
///WHEN
Config.setConfig("startGraphsAtZero", true);
expect(miscTriggerResizeMock).not.toHaveBeenCalled();
});
it("does not triggers resize if property on nosave", () => {
///WHEN
Config.setConfig("maxLineWidth", 50, { nosave: true });
expect(miscTriggerResizeMock).not.toHaveBeenCalled();
});
it("calls afterSet", () => {
//GIVEN
isDevEnvironmentMock.mockReturnValue(false);
replaceConfig({ ads: "off" });
//WHEN
Config.setConfig("ads", "sellout");
//THEN
expect(notificationAddMock).toHaveBeenCalledWith(
"Ad settings changed. Refreshing...",
);
expect(miscReloadAfterMock).toHaveBeenCalledWith(3);
});
});
describe("apply", () => {
it("should fill missing values with defaults", async () => {
//GIVEN
replaceConfig({
mode: "words",
});
await Lifecycle.applyConfig({
numbers: true,
punctuation: true,
});
const config = getConfig();
expect(config.mode).toBe("time");
expect(config.numbers).toBe(true);
expect(config.punctuation).toBe(true);
});
describe("should reset to default if setting failed", () => {
const testCases: {
display: string;
value: Partial<ConfigType>;
expected: Partial<ConfigType>;
}[] = [
{
// invalid funbox
display: "invalid funbox",
value: { funbox: ["invalid_funbox"] as any },
expected: { funbox: [] },
},
{
display: "mode incompatible with funbox",
value: { mode: "quote", funbox: ["58008"] },
expected: { funbox: [] },
},
{
display: "invalid combination of funboxes",
value: { funbox: ["58008", "gibberish"] },
expected: { funbox: [] },
},
{
display: "sanitizes config, remove extra keys",
value: { mode: "zen", unknownKey: true, unknownArray: [1, 2] } as any,
expected: { mode: "zen" },
},
{
display: "applies config migration",
value: { mode: "zen", swapEscAndTab: true } as any,
expected: { mode: "zen", quickRestart: "esc" },
},
];
it.each(testCases)("$display", async ({ value, expected }) => {
await Lifecycle.applyConfig(value);
const config = getConfig();
const applied = Object.fromEntries(
Object.entries(config).filter(([key]) =>
Object.keys(expected).includes(key),
),
);
expect(applied).toEqual(expected);
});
});
describe("should apply keys in an order to avoid overrides", () => {
const testCases: {
display: string;
value: Partial<ConfigType>;
expected: Partial<ConfigType>;
}[] = [
{
display:
"quote length shouldnt override mode, punctuation and numbers",
value: {
punctuation: true,
numbers: true,
quoteLength: [0],
mode: "time",
},
expected: {
punctuation: true,
numbers: true,
quoteLength: [0],
mode: "time",
},
},
];
it.each(testCases)("$display", async ({ value, expected }) => {
await Lifecycle.applyConfig(value);
const config = getConfig();
const applied = Object.fromEntries(
Object.entries(config).filter(([key]) =>
Object.keys(expected).includes(key),
),
);
expect(applied).toEqual(expected);
});
});
it("should apply a partial config but keep the rest unchanged", async () => {
replaceConfig({
numbers: true,
});
await Lifecycle.applyConfig({
...ConfigUtils.getConfigChanges(),
punctuation: true,
});
const config = getConfig();
expect(config.numbers).toBe(true);
});
it("should not enable minWpm if not provided", async () => {
replaceConfig({
minWpm: "off",
});
await Lifecycle.applyConfig({
minWpmCustomSpeed: 100,
});
const config = getConfig();
expect(config.minWpm).toBe("off");
expect(config.minWpmCustomSpeed).toEqual(100);
});
it("should apply minWpm if part of the full config", async () => {
replaceConfig({
minWpm: "off",
});
await Lifecycle.applyConfig({
minWpm: "custom",
minWpmCustomSpeed: 100,
});
const config = getConfig();
expect(config.minWpm).toBe("custom");
expect(config.minWpmCustomSpeed).toEqual(100);
});
it("should keep the keymap off when applying keymapLayout", async () => {
replaceConfig({});
await Lifecycle.applyConfig({
keymapLayout: "qwerty",
});
const config = getConfig();
expect(config.keymapLayout).toEqual("qwerty");
expect(config.keymapMode).toEqual("off");
});
});
});