This commit is contained in:
471
frontend/__tests__/root/config.spec.ts
Normal file
471
frontend/__tests__/root/config.spec.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user