import { describe, it, expect, vi } from "vitest"; import { isObject, escapeHTML, promiseWithResolvers, } from "../../src/ts/utils/misc"; import { getLanguageDisplayString, removeLanguageSize, } from "../../src/ts/utils/strings"; import { Language } from "@monkeytype/schemas/languages"; import { getErrorMessage } from "../../src/ts/utils/error"; describe("misc.ts", () => { describe("getLanguageDisplayString", () => { it("should return correctly formatted strings", () => { const tests: { input: Language; noSizeString: boolean; expected: string; }[] = [ { input: "english", noSizeString: false, expected: "english", }, { input: "english_1k", noSizeString: false, expected: "english 1k", }, { input: "english_1k", noSizeString: true, expected: "english", }, { input: "english_medical", noSizeString: false, expected: "english medical", }, { input: "arabic_egypt_1k", noSizeString: false, expected: "arabic egypt 1k", }, { input: "arabic_egypt_1k", noSizeString: true, expected: "arabic egypt", }, ]; tests.forEach((test) => { const result = getLanguageDisplayString(test.input, test.noSizeString); expect(result).toBe(test.expected); }); }); }); describe("removeLanguageSize", () => { it("should remove language size", () => { const tests: { input: Language; expected: Language }[] = [ { input: "english", expected: "english", }, { input: "english_1k", expected: "english", }, { input: "arabic_egypt", expected: "arabic_egypt", }, { input: "arabic_egypt_1k", expected: "arabic_egypt", }, ]; tests.forEach((test) => { const result = removeLanguageSize(test.input); expect(result).toBe(test.expected); }); }); }); describe("isObject", () => { it("should correctly identify objects", () => { const tests = [ { input: {}, expected: true, }, { input: { a: 1 }, expected: true, }, { input: [], expected: false, }, { input: [1, 2, 3], expected: false, }, { input: "string", expected: false, }, { input: 1, expected: false, }, { input: null, expected: false, }, { input: undefined, expected: false, }, ]; tests.forEach((test) => { const result = isObject(test.input); expect(result).toBe(test.expected); }); }); }); describe("escapeHTML", () => { it("should escape HTML characters correctly", () => { const tests = [ { input: "hello world", expected: "hello world", }, { input: "", expected: "<script>alert('xss')</script>", }, { input: 'Hello "world" & friends', expected: "Hello "world" & friends", }, { input: "Click `here` to continue", expected: "Click `here` to continue", }, { input: null, expected: null, }, { input: undefined, expected: undefined, }, { input: "", expected: "", }, ]; tests.forEach((test) => { const result = escapeHTML(test.input); expect(result).toBe(test.expected); }); }); }); describe("getErrorMesssage", () => { it("should correctly get the error message", () => { const tests = [ { input: null, expected: undefined, }, { input: undefined, expected: undefined, }, { input: "", expected: undefined, }, { input: {}, expected: undefined, }, { input: "error message", expected: "error message", }, { input: 1, expected: "1", }, { input: { message: "error message" }, expected: "error message", }, { input: { message: 1 }, expected: "1", }, { input: { message: "" }, expected: undefined, }, { input: { message: {} }, expected: undefined, }, { input: new Error("error message"), expected: "error message", }, ]; tests.forEach((test) => { const result = getErrorMessage(test.input); expect(result).toBe(test.expected); }); }); }); describe("promiseWithResolvers", () => { it("should resolve the promise from outside", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); //WHEN resolve(42); //THEN await expect(promise).resolves.toBe(42); }); it("should resolve new promise after reset using same promise reference", async () => { const { promise, resolve, reset } = promiseWithResolvers(); const firstPromise = promise; reset(); resolve(10); await expect(firstPromise).resolves.toBe(10); expect(promise).toBe(firstPromise); }); it("should reject the promise from outside", async () => { //GIVEN const { promise, reject } = promiseWithResolvers(); const error = new Error("test error"); //WHEN reject(error); //THEN await expect(promise).rejects.toThrow("test error"); }); it("should work with void type", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); //WHEN resolve(); //THEN await expect(promise).resolves.toBeUndefined(); }); it("should allow multiple resolves (only first takes effect)", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); //WHEN resolve(42); resolve(100); // This should have no effect //THEN await expect(promise).resolves.toBe(42); }); it("should reset and create a new promise", async () => { //GIVEN const { promise, resolve, reset } = promiseWithResolvers(); resolve(42); //WHEN reset(); resolve(100); //THEN await expect(promise).resolves.toBe(100); }); it("should keep the same promise reference after reset", async () => { //GIVEN const wrapper = promiseWithResolvers(); const firstPromise = wrapper.promise; wrapper.resolve(42); await expect(firstPromise).resolves.toBe(42); //WHEN wrapper.reset(); const secondPromise = wrapper.promise; wrapper.resolve(100); //THEN expect(firstPromise).toBe(secondPromise); // Same reference await expect(wrapper.promise).resolves.toBe(100); }); it("should allow reject after reset", async () => { //GIVEN const wrapper = promiseWithResolvers(); wrapper.resolve(42); await wrapper.promise; //WHEN wrapper.reset(); const error = new Error("after reset"); wrapper.reject(error); //THEN await expect(wrapper.promise).rejects.toThrow("after reset"); }); it("should work with complex types", async () => { //GIVEN type ComplexType = { id: number; data: string[] }; const { promise, resolve } = promiseWithResolvers(); const data: ComplexType = { id: 1, data: ["a", "b", "c"] }; //WHEN resolve(data); //THEN await expect(promise).resolves.toEqual(data); }); it("should handle rejection with non-Error values", async () => { //GIVEN const { promise, reject } = promiseWithResolvers(); //WHEN reject("string error"); //THEN await expect(promise).rejects.toBe("string error"); }); it("should allow chaining with then/catch", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); const onFulfilled = vi.fn((value) => value * 2); const chained = promise.then(onFulfilled); //WHEN resolve(21); //THEN await expect(chained).resolves.toBe(42); expect(onFulfilled).toHaveBeenCalledWith(21); }); it("should support async/await patterns", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); //WHEN setTimeout(() => resolve("delayed"), 10); //THEN const result = await promise; expect(result).toBe("delayed"); }); it("should resolve old promise reference after reset", async () => { //GIVEN const wrapper = promiseWithResolvers(); const oldPromise = wrapper.promise; //WHEN wrapper.reset(); wrapper.resolve(42); //THEN // Old promise reference should still resolve with the same value await expect(oldPromise).resolves.toBe(42); expect(oldPromise).toBe(wrapper.promise); }); it("should handle catch", async () => { //GIVEN const { promise, reject } = promiseWithResolvers(); const error = new Error("test error"); //WHEN const caught = promise.catch(() => "recovered"); reject(error); //THEN await expect(caught).resolves.toBe("recovered"); }); it("should call finally handler on resolution", async () => { //GIVEN const { promise, resolve } = promiseWithResolvers(); const onFinally = vi.fn(); //WHEN const final = promise.finally(onFinally); resolve(42); //THEN await expect(final).resolves.toBe(42); expect(onFinally).toHaveBeenCalledOnce(); }); it("should call finally handler on rejection", async () => { //GIVEN const { promise, reject } = promiseWithResolvers(); const onFinally = vi.fn(); const error = new Error("test error"); //WHEN const final = promise.finally(onFinally); reject(error); //THEN await expect(final).rejects.toThrow("test error"); expect(onFinally).toHaveBeenCalledOnce(); }); it("should preserve rejection through finally", async () => { //GIVEN const { promise, reject } = promiseWithResolvers(); const onFinally = vi.fn(); const error = new Error("preserved error"); //WHEN const final = promise.finally(onFinally); reject(error); //THEN await expect(final).rejects.toThrow("preserved error"); expect(onFinally).toHaveBeenCalled(); }); }); });