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 | 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, ]), ); };