This commit is contained in:
173
frontend/__tests__/controllers/preset-controller.spec.ts
Normal file
173
frontend/__tests__/controllers/preset-controller.spec.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import * as PresetController from "../../src/ts/controllers/preset-controller";
|
||||
import { Preset } from "@monkeytype/schemas/presets";
|
||||
import * as DB from "../../src/ts/db";
|
||||
import { setConfig } from "../../src/ts/config/setters";
|
||||
import { Config } from "../../src/ts/config/store";
|
||||
import * as Lifecycle from "../../src/ts/config/lifecycle";
|
||||
import * as ConfigUtils from "../../src/ts/config/utils";
|
||||
import * as Persistence from "../../src/ts/config/persistence";
|
||||
import * as Notifications from "../../src/ts/states/notifications";
|
||||
import * as TestLogic from "../../src/ts/test/test-logic";
|
||||
import * as TagController from "../../src/ts/controllers/tag-controller";
|
||||
|
||||
describe("PresetController", () => {
|
||||
describe("apply", () => {
|
||||
vi.mock("../../src/ts/test/test-logic", () => ({
|
||||
restart: vi.fn(),
|
||||
}));
|
||||
vi.mock("../../src/ts/test/pace-caret", () => ({
|
||||
//
|
||||
}));
|
||||
const dbGetSnapshotMock = vi.spyOn(DB, "getSnapshot");
|
||||
const configApplyMock = vi.spyOn(Lifecycle, "applyConfig");
|
||||
const configSaveFullConfigMock = vi.spyOn(
|
||||
Persistence,
|
||||
"saveFullConfigToLocalStorage",
|
||||
);
|
||||
const configGetConfigChangesMock = vi.spyOn(
|
||||
ConfigUtils,
|
||||
"getConfigChanges",
|
||||
);
|
||||
const notificationAddMock = vi.spyOn(
|
||||
Notifications,
|
||||
"showSuccessNotification",
|
||||
);
|
||||
const testRestartMock = vi.spyOn(TestLogic, "restart");
|
||||
const tagControllerClearMock = vi.spyOn(TagController, "clear");
|
||||
const tagControllerSetMock = vi.spyOn(TagController, "set");
|
||||
const tagControllerSaveActiveMock = vi.spyOn(
|
||||
TagController,
|
||||
"saveActiveToLocalStorage",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
[
|
||||
dbGetSnapshotMock,
|
||||
configApplyMock,
|
||||
configSaveFullConfigMock,
|
||||
configGetConfigChangesMock,
|
||||
notificationAddMock,
|
||||
testRestartMock,
|
||||
tagControllerClearMock,
|
||||
tagControllerSetMock,
|
||||
tagControllerSaveActiveMock,
|
||||
].forEach((it) => it.mockClear());
|
||||
|
||||
configApplyMock.mockResolvedValue();
|
||||
});
|
||||
|
||||
it("should apply for full preset", async () => {
|
||||
//GIVEN
|
||||
const preset = givenPreset({ config: { punctuation: true } });
|
||||
|
||||
//WHEN
|
||||
await PresetController.apply(preset._id);
|
||||
|
||||
//THEN
|
||||
expect(configApplyMock).toHaveBeenCalledWith(preset.config);
|
||||
expect(tagControllerClearMock).toHaveBeenCalled();
|
||||
expect(testRestartMock).toHaveBeenCalled();
|
||||
expect(notificationAddMock).toHaveBeenCalledWith("Preset applied", {
|
||||
durationMs: 2000,
|
||||
});
|
||||
expect(configSaveFullConfigMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should apply for full preset with tags", async () => {
|
||||
//GIVEN
|
||||
const preset = givenPreset({
|
||||
config: { tags: ["tagOne", "tagTwo"] },
|
||||
});
|
||||
|
||||
//WHEN
|
||||
await PresetController.apply(preset._id);
|
||||
|
||||
//THEN
|
||||
expect(tagControllerClearMock).toHaveBeenCalled();
|
||||
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"tagOne",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"tagTwo",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
expect(tagControllerSaveActiveMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should ignore unknown preset", async () => {
|
||||
//WHEN
|
||||
await PresetController.apply("unknown");
|
||||
//THEN
|
||||
expect(notificationAddMock).not.toHaveBeenCalled();
|
||||
expect(configApplyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should apply for partial preset", async () => {
|
||||
//GIVEN
|
||||
const preset = givenPreset({
|
||||
config: { punctuation: true },
|
||||
settingGroups: ["test"],
|
||||
});
|
||||
|
||||
setConfig("numbers", true);
|
||||
const oldConfig = structuredClone(Config);
|
||||
|
||||
//WHEN
|
||||
await PresetController.apply(preset._id);
|
||||
|
||||
//THEN
|
||||
expect(configApplyMock).toHaveBeenCalledWith({
|
||||
...oldConfig,
|
||||
numbers: true,
|
||||
punctuation: true,
|
||||
});
|
||||
expect(testRestartMock).toHaveBeenCalled();
|
||||
expect(notificationAddMock).toHaveBeenCalledWith("Preset applied", {
|
||||
durationMs: 2000,
|
||||
});
|
||||
expect(configSaveFullConfigMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should apply for partial preset with tags", async () => {
|
||||
//GIVEN
|
||||
const preset = givenPreset({
|
||||
config: { tags: ["tagOne", "tagTwo"] },
|
||||
settingGroups: ["test", "behavior"],
|
||||
});
|
||||
|
||||
//WHEN
|
||||
await PresetController.apply(preset._id);
|
||||
|
||||
//THEN
|
||||
expect(tagControllerClearMock).toHaveBeenCalled();
|
||||
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"tagOne",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
expect(tagControllerSetMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"tagTwo",
|
||||
true,
|
||||
false,
|
||||
);
|
||||
expect(tagControllerSaveActiveMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const givenPreset = (partialPreset: Partial<Preset>): Preset => {
|
||||
const preset: Preset = {
|
||||
_id: "1",
|
||||
...partialPreset,
|
||||
} as any;
|
||||
dbGetSnapshotMock.mockReturnValue({ presets: [preset] } as any);
|
||||
return preset;
|
||||
};
|
||||
});
|
||||
});
|
||||
299
frontend/__tests__/controllers/url-handler.spec.ts
Normal file
299
frontend/__tests__/controllers/url-handler.spec.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Difficulty, Mode, Mode2 } from "@monkeytype/schemas/shared";
|
||||
import { compressToURI } from "lz-ts";
|
||||
import * as UpdateConfig from "../../src/ts/config/setters";
|
||||
import * as Notifications from "../../src/ts/states/notifications";
|
||||
import * as TestLogic from "../../src/ts/test/test-logic";
|
||||
import * as TestState from "../../src/ts/test/test-state";
|
||||
import * as Misc from "../../src/ts/utils/misc";
|
||||
import { FunboxName } from "@monkeytype/schemas/configs";
|
||||
import { CustomTextSettings } from "@monkeytype/schemas/results";
|
||||
import { loadTestSettingsFromUrl } from "../../src/ts/controllers/url-handler";
|
||||
|
||||
//mock modules to avoid dependencies
|
||||
vi.mock("../../src/ts/test/test-logic", () => ({
|
||||
restart: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("url-handler", () => {
|
||||
describe("loadTestSettingsFromUrl", () => {
|
||||
const findGetParameterMock = vi.spyOn(Misc, "findGetParameter");
|
||||
|
||||
const setConfigMock = vi.spyOn(UpdateConfig, "setConfig");
|
||||
const setSelectedQuoteIdMock = vi.spyOn(TestState, "setSelectedQuoteId");
|
||||
const restartTestMock = vi.spyOn(TestLogic, "restart");
|
||||
const notifySuccessMock = vi.spyOn(
|
||||
Notifications,
|
||||
"showSuccessNotification",
|
||||
);
|
||||
const notifyMock = vi.spyOn(Notifications, "showNoticeNotification");
|
||||
|
||||
beforeEach(() => {
|
||||
[
|
||||
setConfigMock,
|
||||
findGetParameterMock,
|
||||
setSelectedQuoteIdMock,
|
||||
restartTestMock,
|
||||
notifySuccessMock,
|
||||
notifyMock,
|
||||
].forEach((it) => it.mockClear());
|
||||
|
||||
findGetParameterMock.mockImplementation((override) => override);
|
||||
});
|
||||
|
||||
it("handles null", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue("null");
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).not.toHaveBeenCalled();
|
||||
});
|
||||
it("handles mode2 as number", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ mode: "time", mode2: 60 }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("mode", "time", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(setConfigMock).toHaveBeenCalledWith("time", 60, {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets time", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ mode: "time", mode2: "30" }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("mode", "time", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(setConfigMock).toHaveBeenCalledWith("time", 30, {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets word count", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ mode: "words", mode2: "50" }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("mode", "words", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(setConfigMock).toHaveBeenCalledWith("words", 50, {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets quote length", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ mode: "quote", mode2: "512" }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("mode", "quote", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(setConfigMock).toHaveBeenCalledWith("quoteLength", [-2]);
|
||||
expect(setSelectedQuoteIdMock).toHaveBeenCalledWith(512);
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets punctuation", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(urlData({ punctuation: true }));
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("punctuation", true, {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets numbers", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(urlData({ numbers: false }));
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("numbers", false, {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets language", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(urlData({ language: "english" }));
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("language", "english", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets difficulty", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(urlData({ difficulty: "master" }));
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith("difficulty", "master", {
|
||||
nosave: true,
|
||||
});
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets funbox", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ funbox: ["crt", "choo_choo"] }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith(
|
||||
"funbox",
|
||||
["crt", "choo_choo"],
|
||||
{
|
||||
nosave: true,
|
||||
},
|
||||
);
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets funbox legacy", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ funbox: "crt#choo_choo" }),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setConfigMock).toHaveBeenCalledWith(
|
||||
"funbox",
|
||||
["crt", "choo_choo"],
|
||||
{
|
||||
nosave: true,
|
||||
},
|
||||
);
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("adds notification", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({
|
||||
mode: "time",
|
||||
mode2: "60",
|
||||
customText: {
|
||||
text: ["abcabc"],
|
||||
limit: { value: 5, mode: "time" },
|
||||
mode: "random",
|
||||
pipeDelimiter: true,
|
||||
},
|
||||
punctuation: true,
|
||||
numbers: true,
|
||||
language: "english",
|
||||
difficulty: "master",
|
||||
funbox: ["ascii", "crt"],
|
||||
}),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(notifySuccessMock).toHaveBeenCalledWith(expect.anything(), {
|
||||
durationMs: 10000,
|
||||
useInnerHtml: true,
|
||||
});
|
||||
});
|
||||
it("rejects invalid values", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({
|
||||
mode: "invalidMode",
|
||||
mode2: "invalidMode2",
|
||||
customText: {
|
||||
text: "invalid",
|
||||
limit: "invalid",
|
||||
mode: "invalid",
|
||||
pipeDelimiter: "invalid",
|
||||
},
|
||||
punctuation: "invalid",
|
||||
numbers: "invalid",
|
||||
language: "invalid",
|
||||
difficulty: "invalid",
|
||||
funbox: ["invalid"],
|
||||
} as any),
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(notifyMock).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.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`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const urlData = (
|
||||
data: Partial<{
|
||||
mode: Mode | undefined;
|
||||
mode2: Mode2<any> | number;
|
||||
customText: CustomTextSettings;
|
||||
punctuation: boolean;
|
||||
numbers: boolean;
|
||||
language: string;
|
||||
difficulty: Difficulty;
|
||||
funbox: FunboxName[] | string;
|
||||
}>,
|
||||
): string => {
|
||||
return compressToURI(
|
||||
JSON.stringify([
|
||||
data.mode ?? null,
|
||||
data.mode2 ?? null,
|
||||
data.customText ?? null,
|
||||
data.punctuation ?? null,
|
||||
data.numbers ?? null,
|
||||
data.language ?? null,
|
||||
data.difficulty ?? null,
|
||||
data.funbox ?? null,
|
||||
]),
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user