Ctrl K
ComunidadGuías…

app/utils/splitView.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { MemoryHistory } from "history";
import { createMemoryHistory } from "history";
import { reaction } from "mobx";
import {
  closeSplitPane,
  getFocusedSplitPane,
  getSplitPath,
  initSplitViewNavigation,
  isSplittablePath,
  isSplitViewModifierEvent,
  isSplitViewModifierPressed,
  openRouteInSplit,
  setFocusedSplitPane,
  setSplitPath,
} from "./splitView";

beforeEach(() => {
  setFocusedSplitPane("primary");
});

describe("getSplitPath", () => {
  it("returns the split path from a search string", () => {
    expect(getSplitPath("?split=%2Fdoc%2Fmy-doc")).toEqual("/doc/my-doc");
    expect(getSplitPath("split=%2Fdoc%2Fmy-doc")).toEqual("/doc/my-doc");
  });

  it("returns undefined when no split parameter is present", () => {
    expect(getSplitPath("")).toBeUndefined();
    expect(getSplitPath("?foo=bar")).toBeUndefined();
  });

  it("returns undefined for values that are not internal paths", () => {
    expect(getSplitPath("?split=https%3A%2F%2Fexample.com")).toBeUndefined();
    expect(getSplitPath("?split=%2F%2Fexample.com")).toBeUndefined();
    expect(getSplitPath("?split=doc%2Fmy-doc")).toBeUndefined();
  });

  it("returns undefined for routes that cannot render in a pane", () => {
    expect(getSplitPath("?split=%2Fsettings%2Fmembers")).toBeUndefined();
    expect(getSplitPath("?split=%2Flogout")).toBeUndefined();
  });

  it("uses the last value when the parameter is repeated", () => {
    expect(getSplitPath("?split=%2Fone&split=%2Ftwo")).toEqual("/two");
  });
});

describe("setSplitPath", () => {
  it("adds the split parameter to a search string", () => {
    expect(setSplitPath("", "/doc/my-doc")).toEqual("?split=%2Fdoc%2Fmy-doc");
  });

  it("preserves other query parameters", () => {
    expect(setSplitPath("?foo=bar", "/doc/my-doc")).toEqual(
      "?foo=bar&split=%2Fdoc%2Fmy-doc"
    );
  });

  it("removes the split parameter when the path is undefined", () => {
    expect(setSplitPath("?split=%2Fdoc%2Fmy-doc", undefined)).toEqual("");
    expect(setSplitPath("?foo=bar&split=%2Fdoc%2Fmy-doc", undefined)).toEqual(
      "?foo=bar"
    );
  });

  it("round trips with getSplitPath", () => {
    const search = setSplitPath("?commentId=123", "/doc/my-doc?revision=1");
    expect(getSplitPath(search)).toEqual("/doc/my-doc?revision=1");
  });
});

describe("isSplittablePath", () => {
  it("allows content routes", () => {
    expect(isSplittablePath("/home")).toBe(true);
    expect(isSplittablePath("/doc/my-doc-a1b2c3d4e5")).toBe(true);
    expect(isSplittablePath("/collection/engineering-a1b2c3d4e5")).toBe(true);
    expect(isSplittablePath("/search")).toBe(true);
    expect(isSplittablePath("/drafts")).toBe(true);
  });

  it("rejects routes that render their own chrome", () => {
    expect(isSplittablePath("/")).toBe(false);
    expect(isSplittablePath("/api/documents.export")).toBe(false);
    expect(isSplittablePath("/settings")).toBe(false);
    expect(isSplittablePath("/settings/users")).toBe(false);
    expect(isSplittablePath("/s/abc123")).toBe(false);
    expect(isSplittablePath("/logout")).toBe(false);
    expect(isSplittablePath("/oauth/authorize")).toBe(false);
    expect(isSplittablePath("/404")).toBe(false);
  });

  it("does not reject routes sharing a prefix with an excluded route", () => {
    expect(isSplittablePath("/search")).toBe(true);
    expect(isSplittablePath("/settings-doc-a1b2c3d4e5")).toBe(true);
  });

  it("rejects values that are not internal absolute paths", () => {
    expect(isSplittablePath("https://example.com/doc/my-doc")).toBe(false);
    expect(isSplittablePath("//example.com")).toBe(false);
    expect(isSplittablePath("doc/my-doc")).toBe(false);
    expect(isSplittablePath("")).toBe(false);
  });
});

describe("focused split pane", () => {
  it("defaults to the primary pane", () => {
    expect(getFocusedSplitPane()).toEqual("primary");
  });

  it("is observable", () => {
    const observed: string[] = [];
    const dispose = reaction(
      () => getFocusedSplitPane(),
      (pane) => observed.push(pane)
    );

    setFocusedSplitPane("secondary");
    setFocusedSplitPane("secondary");
    setFocusedSplitPane("primary");
    dispose();
    setFocusedSplitPane("secondary");

    expect(observed).toEqual(["secondary", "primary"]);
  });
});

describe("openRouteInSplit", () => {
  it("adds the split parameter and focuses the secondary pane", () => {
    const history = createMemoryHistory({
      initialEntries: ["/doc/my-doc?commentId=123"],
    });

    openRouteInSplit(history, "/doc/other-doc");

    expect(history.location.pathname).toEqual("/doc/my-doc");
    expect(getSplitPath(history.location.search)).toEqual("/doc/other-doc");
    expect(history.location.search).toContain("commentId=123");
    expect(getFocusedSplitPane()).toEqual("secondary");
  });
});

// The test environment is neither macOS nor Electron, so the modifier is
// control together with shift.
describe("isSplitViewModifierEvent", () => {
  it("matches the modifier combination for the platform", () => {
    expect(
      isSplitViewModifierEvent(
        new MouseEvent("click", { ctrlKey: true, shiftKey: true })
      )
    ).toBe(true);
  });

  it("does not match other combinations", () => {
    expect(
      isSplitViewModifierEvent(new MouseEvent("click", { ctrlKey: true }))
    ).toBe(false);
    expect(
      isSplitViewModifierEvent(new MouseEvent("click", { shiftKey: true }))
    ).toBe(false);
    expect(
      isSplitViewModifierEvent(
        new MouseEvent("click", { ctrlKey: true, shiftKey: true, altKey: true })
      )
    ).toBe(false);
  });
});

describe("initSplitViewNavigation", () => {
  let history: MemoryHistory;
  let stop: () => void;
  let anchor: HTMLAnchorElement;

  // Returns whether the click was left to follow the link, stopping the
  // navigation the test environment cannot perform.
  const click = (target: Element, init: MouseEventInit = {}) => {
    let followed = true;
    const suppressNavigation = (event: Event) => {
      followed = !event.defaultPrevented;
      event.preventDefault();
    };

    window.addEventListener("click", suppressNavigation);
    target.dispatchEvent(
      new MouseEvent("click", {
        bubbles: true,
        cancelable: true,
        detail: 1,
        ctrlKey: true,
        shiftKey: true,
        ...init,
      })
    );
    window.removeEventListener("click", suppressNavigation);

    return followed;
  };

  beforeEach(() => {
    history = createMemoryHistory({ initialEntries: ["/doc/my-doc"] });
    stop = initSplitViewNavigation(history);
    anchor = window.document.createElement("a");
    anchor.href = "/doc/other-doc?commentId=123";
    window.document.body.append(anchor);
  });

  afterEach(() => {
    stop();
    anchor.remove();
  });

  it("opens an internal link in the secondary pane", () => {
    const followed = click(anchor);

    expect(followed).toBe(false);
    expect(history.location.pathname).toEqual("/doc/my-doc");
    expect(getSplitPath(history.location.search)).toEqual(
      "/doc/other-doc?commentId=123"
    );
    expect(getFocusedSplitPane()).toEqual("secondary");
  });

  it("opens a link nested inside other elements", () => {
    const child = window.document.createElement("span");
    anchor.append(child);

    click(child);

    expect(getSplitPath(history.location.search)).toEqual(
      "/doc/other-doc?commentId=123"
    );
  });

  it("follows the link without the modifier", () => {
    const followed = click(anchor, { ctrlKey: false, shiftKey: false });

    expect(followed).toBe(true);
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });

  it("follows clicks that are not a primary button press", () => {
    expect(click(anchor, { button: 1 })).toBe(true);
    expect(click(anchor, { detail: 0 })).toBe(true);
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });

  it("follows links to routes that cannot render in a pane", () => {
    anchor.href = "/settings/members";

    expect(click(anchor)).toBe(true);
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });

  it("follows external links", () => {
    anchor.href = "https://example.com/doc/other-doc";

    expect(click(anchor)).toBe(true);
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });

  it("follows links that open elsewhere", () => {
    anchor.target = "_blank";

    expect(click(anchor)).toBe(true);
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });

  it("records the modifier for the duration of the event only", () => {
    vi.useFakeTimers();

    try {
      const target = window.document.createElement("div");
      window.document.body.append(target);
      click(target);
      expect(isSplitViewModifierPressed()).toBe(true);

      vi.runAllTimers();
      expect(isSplitViewModifierPressed()).toBe(false);
      target.remove();
    } finally {
      vi.useRealTimers();
    }
  });
});

describe("closeSplitPane", () => {
  it("keeps the primary route when closing the secondary pane", () => {
    const history = createMemoryHistory({
      initialEntries: ["/doc/my-doc?split=%2Fdoc%2Fother-doc"],
    });
    setFocusedSplitPane("secondary");

    closeSplitPane(history, "secondary");

    expect(history.location.pathname).toEqual("/doc/my-doc");
    expect(getSplitPath(history.location.search)).toBeUndefined();
    expect(getFocusedSplitPane()).toEqual("primary");
  });

  it("promotes the secondary route when closing the primary pane", () => {
    const history = createMemoryHistory({
      initialEntries: [
        "/doc/my-doc?split=%2Fdoc%2Fother-doc%3FcommentId%3D123",
      ],
    });
    setFocusedSplitPane("secondary");

    closeSplitPane(history, "primary");

    expect(history.location.pathname).toEqual("/doc/other-doc");
    expect(history.location.search).toContain("commentId=123");
    expect(getSplitPath(history.location.search)).toBeUndefined();
    expect(getFocusedSplitPane()).toEqual("primary");
  });

  it("removes the split parameter when closing the primary pane without a secondary route", () => {
    const history = createMemoryHistory({
      initialEntries: ["/doc/my-doc"],
    });

    closeSplitPane(history, "primary");

    expect(history.location.pathname).toEqual("/doc/my-doc");
    expect(getSplitPath(history.location.search)).toBeUndefined();
  });
});