diff --git a/test/package.json b/test/package.json index e3cb94f6..4a408464 100644 --- a/test/package.json +++ b/test/package.json @@ -1,5 +1,5 @@ { - "#": "the types and generates conflicts with mocha.", + "#": "We must put jest in a sub-directory otherwise VS Code somehow picks up the types and generates conflicts with mocha.", "devDependencies": { "@types/jest": "^26.0.20", "@types/jsdom": "^16.2.6", diff --git a/test/util.test.ts b/test/util.test.ts index 45179bdc..418756a5 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -1,9 +1,12 @@ +import { JSDOM } from "jsdom" // Note: we need to import logger from the root // because this is the logger used in logError in ../src/common/util import { logger } from "../node_modules/@coder/logger" import { arrayify, + generateUuid, getFirstString, + getOptions, logError, normalize, plural, @@ -12,6 +15,10 @@ import { trimSlashes, } from "../src/common/util" +const dom = new JSDOM() +global.document = dom.window.document +// global.window = (dom.window as unknown) as Window & typeof globalThis + type LocationLike = Pick describe("util", () => { @@ -49,6 +56,20 @@ describe("util", () => { }) }) + describe("generateUuid", () => { + it("should generate a unique uuid", () => { + const uuid = generateUuid() + const uuid2 = generateUuid() + expect(uuid).toHaveLength(24) + expect(typeof uuid).toBe("string") + expect(uuid).not.toBe(uuid2) + }) + it("should generate a uuid of a specific length", () => { + const uuid = generateUuid(10) + expect(uuid).toHaveLength(10) + }) + }) + describe("trimSlashes", () => { it("should remove leading slashes", () => { expect(trimSlashes("/hello-world")).toBe("hello-world") @@ -103,6 +124,76 @@ describe("util", () => { }) }) + describe("getOptions", () => { + // Things to mock + // logger + // location + // document + beforeEach(() => { + const location: LocationLike = { + pathname: "/healthz", + origin: "http://localhost:8080", + // search: "?environmentId=600e0187-0909d8a00cb0a394720d4dce", + } + + // Because resolveBase is not a pure function + // and relies on the global location to be set + // we set it before all the tests + // and tell TS that our location should be looked at + // as Location (even though it's missing some properties) + global.location = location as Location + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it("should return options with base and cssStaticBase even if it doesn't exist", () => { + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "", + }) + }) + + it("should return options when they do exist", () => { + // Mock getElementById + const spy = jest.spyOn(document, "getElementById") + // Create a fake element and set the attribute + const mockElement = document.createElement("div") + mockElement.setAttribute( + "data-settings", + '{"base":".","csStaticBase":"./static/development/Users/jp/Dev/code-server","logLevel":2,"disableTelemetry":false,"disableUpdateCheck":false}', + ) + // Return mockElement from the spy + // this way, when we call "getElementById" + // it returns the element + spy.mockImplementation(() => mockElement) + + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "/static/development/Users/jp/Dev/code-server", + disableTelemetry: false, + disableUpdateCheck: false, + logLevel: 2, + }) + }) + + it("should include queryOpts", () => { + // Trying to understand how the implementation works + // 1. It grabs the search params from location.search (i.e. ?) + // 2. it then grabs the "options" param if it exists + // 3. then it creates a new options object + // spreads the original options + // then parses the queryOpts + location.search = '?options={"logLevel":2}' + expect(getOptions()).toStrictEqual({ + base: "", + csStaticBase: "", + logLevel: 2, + }) + }) + }) + describe("arrayify", () => { it("should return value it's already an array", () => { expect(arrayify(["hello", "world"])).toStrictEqual(["hello", "world"])