Skip to content
Source
Sign inSign up

openPullRequestLink.browser.test.tsx

159 lines5.8 KB

1import { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { scopeThreadRef, scopedThreadKey } from "@t3tools/client-runtime/environment";
3import { AsyncResult } from "effect/reactivity";
4import { expect, it, vi } from "vite-plus/test";
5import { page } from "vite-plus/test/browser";
6​
7import { act, render, stubGlobal } from "../../test/render";
8​
9const state = vi.hoisted(() => ({ preview: vi.fn(), openLink: vi.fn() }));
10vi.mock("../state/use-atom-query-runner", () => ({ useAtomQueryRunner: () => state.preview }));
11vi.mock("../browser/useOpenLink", () => ({ useOpenLink: () => state.openLink }));
12vi.mock(import("../state/environments"), async (original) => ({
13 ...(await original()),
14 usePrimaryEnvironmentId: () => EnvironmentId.make("local"),
15}));
16vi.mock("../state/entities", async (original) => ({
17 ...(await original<typeof import("../state/entities")>()),
18 readProjects: () => [project],
19 useProjects: () => [project],
20 readProject: () => project,
21 readThreadShell: () => ({ projectId: "project", worktreePath: null }),
22}));
23vi.mock("../state/server", async (original) => {
24 const { Atom } = await import("effect/reactivity");
25 return {
26 ...(await original<typeof import("../state/server")>()),
27 environmentServerConfigsAtom: Atom.make(new Map([["local", {}]])),
28 };
29});
30​
31import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings";
32import { __setClientSettingsForTests } from "../hooks/useSettings";
33import { ToastProvider } from "../components/ui/toast";
34import { AppAtomRegistryProvider } from "../rpc/atomRegistry";
35import { useRightPanelStore } from "../rightPanelStore";
36import { useDiffPanelStore } from "../diffPanelStore";
37import { useOpenChangeRequestLink } from "./openPullRequestLink";
38​
39const environmentId = EnvironmentId.make("local");
40const threadRef = scopeThreadRef(environmentId, ThreadId.make("thread"));
41const project = {
42 id: "project",
43 environmentId,
44 workspaceRoot: "/repo",
45 repositoryIdentity: {
46 provider: "github",
47 canonicalKey: "github.com/effect-ts/effect",
48 displayName: "Effect-TS/effect",
49 locator: {
50 source: "git-remote",
51 remoteName: "origin",
52 remoteUrl: "https://github.com/Effect-TS/effect.git",
53 },
54 },
55};
56const commitUrl = `https://github.com/Effect-TS/effect/commit/${"a".repeat(40)}?plain=1#diff-original`;
57const pullRequestCommitUrl = `https://github.com/Effect-TS/effect/pull/1/commits/${"a".repeat(40)}`;
58​
59function CommitLink({ url = commitUrl }: { url?: string }) {
60 const open = useOpenChangeRequestLink(threadRef);
61 return (
62 <a href={url} onClick={(event) => open(event, url)}>
63 Commit
64 </a>
65 );
67​
68function setup() {
69 state.preview.mockReset().mockResolvedValue(AsyncResult.success({ sources: [] }));
70 state.openLink.mockReset().mockResolvedValue(undefined);
71 __setClientSettingsForTests({
72 ...DEFAULT_CLIENT_SETTINGS,
73 pullRequestLinksOpenInApp: true,
74 browserLinkTarget: "system",
75 });
76 useDiffPanelStore.setState({
77 byThreadKey: {},
78 branchBaseRefByThreadKey: {},
79 });
80 useRightPanelStore.setState({ byThreadKey: {}, userActionRevisionByThreadKey: {} });
82​
83it.each([
84 ["shiftKey", commitUrl],
85 ["altKey", commitUrl],
86 ["shiftKey", pullRequestCommitUrl],
87 ["shiftKey", `${pullRequestCommitUrl}#diff-abcR5`],
88 ["altKey", `${pullRequestCommitUrl}#diff-abcR5`],
89] as const)("keeps %s clicks on %s native", async (modifier, url) => {
90 setup();
91 await using screen = await render(
92 <AppAtomRegistryProvider>
93 <CommitLink url={url} />
94 </AppAtomRegistryProvider>,
95 );
96 const event = new MouseEvent("click", { bubbles: true, cancelable: true, [modifier]: true });
97 const defaults: boolean[] = [];
98 screen.container.addEventListener(
99 "click",
100 (nativeEvent) => {
101 defaults.push(nativeEvent.defaultPrevented);
102 nativeEvent.preventDefault();
103 },
104 { once: true },
105 );
106 await act(() => screen.getByRole("link", { name: "Commit" }).element().dispatchEvent(event));
107 expect(defaults).toEqual([false]);
108 expect(state.preview).not.toHaveBeenCalled();
109 expect(useRightPanelStore.getState().byThreadKey).toEqual({});
110});
111​
112it.each(["system", "app"] as const)(
113 "offers the original unavailable commit URL when web popups are blocked (%s)",
114 async (browserLinkTarget) => {
115 setup();
116 // Without preview access, the "app" preference opens a popup too.
117 __setClientSettingsForTests({
118 ...DEFAULT_CLIENT_SETTINGS,
119 pullRequestLinksOpenInApp: true,
120 browserLinkTarget,
121 });
122 using _desktop = stubGlobal("desktopBridge", undefined);
123 await using screen = await render(
124 <AppAtomRegistryProvider>
125 <ToastProvider>
126 <CommitLink />
127 </ToastProvider>
128 </AppAtomRegistryProvider>,
129 );
130 using open = vi.spyOn(window, "open").mockReturnValue(null);
131 await screen.getByRole("link", { name: "Commit" }).click();
132 await expect
133 .element(page.getByRole("button", { name: "Open link", exact: true }))
134 .toBeVisible();
135 expect(open).not.toHaveBeenCalled();
136 await page.getByRole("button", { name: "Open link", exact: true }).click();
137 expect(open).toHaveBeenCalledExactlyOnceWith(commitUrl, "_blank", "noopener,noreferrer");
138 expect(state.openLink).not.toHaveBeenCalled();
139 },
140);
141​
142it("opens an available commit in the closed Diff pane without the pane resetting it", async () => {
143 setup();
144 const sha = "a".repeat(40);
145 state.preview.mockResolvedValue(
146 AsyncResult.success({ sources: [{ kind: "commit", headRef: sha }] }),
147 );
148 await using screen = await render(
149 <AppAtomRegistryProvider>
150 <CommitLink />
151 </AppAtomRegistryProvider>,
152 );
153 await screen.getByRole("link", { name: "Commit" }).click();
154 await vi.waitFor(() =>
155 expect(useDiffPanelStore.getState().byThreadKey).toEqual({
156 [scopedThreadKey(threadRef)]: { kind: "commit", sha, baseRef: null },
157 }),
158 );
159});