Skip to content
Source
Sign inSign up
Open History to see the last commit

RightPanelTabs.test.tsx

166 lines6.2 KB

1import { EnvironmentId, type ThreadPullRequestLink } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";
3​
4import { surfaceShortcutActionForKey, surfaceShortcutTargetsTypingContext } from "./RightPanelTabs";
5import { resolvePullRequestTabLink } from "./SurfaceTabIcon";
6​
7function shortcutEvent(
8 key: string,
9 overrides: Partial<Parameters<typeof surfaceShortcutActionForKey>[1]> = {},
10): Parameters<typeof surfaceShortcutActionForKey>[1] {
11 return {
12 key,
13 altKey: false,
14 ctrlKey: false,
15 defaultPrevented: false,
16 isComposing: false,
17 metaKey: false,
18 ...overrides,
19 };
21​
22describe("surface shortcuts", () => {
23 const actions = [
24 { shortcut: "B", available: true, label: "Browser" },
25 { shortcut: "D", available: false, label: "Diff" },
26 { shortcut: "T", available: true, label: "Terminal" },
27 { shortcut: "M", available: true, label: "Device" },
28 ] as const;
29​
30 it("falls back to the physical key when the layout types a non-Latin letter or mark", () => {
31 // Ukrainian layout: the B key types "и".
32 expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u0438", { code: "KeyB" }))).toBe(
33 actions[0],
34 );
35 // Greek layout: the B key types "β".
36 expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u03b2", { code: "KeyB" }))).toBe(
37 actions[0],
38 );
39 // Thai layout: the B key types the vowel sign "\u0e34", a combining mark.
40 expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u0e34", { code: "KeyB" }))).toBe(
41 actions[0],
42 );
43 // Arabic (101) layout: the B key types lam alef "\u0644\u0627", two code points.
44 expect(
45 surfaceShortcutActionForKey(actions, shortcutEvent("\u0644\u0627", { code: "KeyB" })),
46 ).toBe(actions[0]);
47 // Ukrainian layout: the D key types "\u0432", but the Diff surface is unavailable.
48 expect(
49 surfaceShortcutActionForKey(actions, shortcutEvent("\u0432", { code: "KeyD" })),
50 ).toBeNull();
51 });
52​
53 it("follows the typed letter on Latin layouts that move it", () => {
54 // Dvorak: the key at QWERTY B types "x", and "b" sits on QWERTY N.
55 expect(surfaceShortcutActionForKey(actions, shortcutEvent("x", { code: "KeyB" }))).toBeNull();
56 // The same key with Shift or Caps Lock types "X".
57 expect(surfaceShortcutActionForKey(actions, shortcutEvent("X", { code: "KeyB" }))).toBeNull();
58 expect(surfaceShortcutActionForKey(actions, shortcutEvent("b", { code: "KeyN" }))).toBe(
59 actions[0],
60 );
61 });
62​
63 it("keeps accented Latin letters typed on a letter key", () => {
64 // Bépo: the T key types "è".
65 expect(
66 surfaceShortcutActionForKey(actions, shortcutEvent("\u00e8", { code: "KeyT" })),
67 ).toBeNull();
68 });
69​
70 it("keeps punctuation typed on a letter key", () => {
71 // French AZERTY: the M key types ",".
72 expect(surfaceShortcutActionForKey(actions, shortcutEvent(",", { code: "KeyM" }))).toBeNull();
73 });
74​
75 it("keeps a dead key on a letter key", () => {
76 // A dead key starts an accented letter on a Latin layout.
77 expect(
78 surfaceShortcutActionForKey(actions, shortcutEvent("Dead", { code: "KeyT" })),
79 ).toBeNull();
80 });
81​
82 it("leaves modified, composing, and already-handled key events alone", () => {
83 expect(surfaceShortcutActionForKey(actions, shortcutEvent("b", { metaKey: true }))).toBeNull();
84 expect(
85 surfaceShortcutActionForKey(actions, shortcutEvent("b", { isComposing: true })),
86 ).toBeNull();
87 expect(
88 surfaceShortcutActionForKey(actions, shortcutEvent("b", { defaultPrevented: true })),
89 ).toBeNull();
90 });
91​
92 it("leaves AltGr text alone unless it is a plain letter", () => {
93 const getModifierState = (key: string) => key === "AltGraph";
94 // German layout: AltGr+M types "\u00b5" and sets only AltGraph on Linux and Windows.
95 expect(
96 surfaceShortcutActionForKey(
97 actions,
98 shortcutEvent("\u00b5", { code: "KeyM", getModifierState }),
99 ),
100 ).toBeNull();
101 // Finnish layout: AltGr+B types "b", which still matches, as it does in keybindings.ts.
102 expect(
103 surfaceShortcutActionForKey(actions, shortcutEvent("b", { code: "KeyB", getModifierState })),
104 ).toBe(actions[0]);
105 });
106});
107​
108describe("surface shortcut typing contexts", () => {
109 const makeTarget = (matches: string | null) => ({
110 closest(selectors: string) {
111 if (matches === null || !selectors.includes(matches)) return null;
112 return {};
113 },
114 });
115​
116 it("treats form fields and every editable region as typing contexts", () => {
117 expect(surfaceShortcutTargetsTypingContext(makeTarget("input"))).toBe(true);
118 expect(surfaceShortcutTargetsTypingContext(makeTarget("textarea"))).toBe(true);
119 expect(surfaceShortcutTargetsTypingContext(makeTarget("select"))).toBe(true);
120 expect(surfaceShortcutTargetsTypingContext(makeTarget("[contenteditable]"))).toBe(true);
121 });
122});
123​
124describe("pull request tab snapshots", () => {
125 const environmentId = EnvironmentId.make("local");
126 const link: ThreadPullRequestLink = {
127 host: "github.com",
128 repository: "acme/api",
129 number: 7,
130 url: "https://github.com/acme/api/pull/7",
131 source: "manual",
132 linkedAt: "2026-01-01T00:00:00Z",
133 stack: null,
134 snapshot: null,
135 };
136 it("keeps unknown linked state authoritative and scopes matches to environment and host", () => {
137 const threads = [{ environmentId, pullRequests: [link] }];
138 expect(resolvePullRequestTabLink(threads, environmentId, "github.com", link)).toBe(link);
139 expect(
140 resolvePullRequestTabLink(threads, EnvironmentId.make("remote"), "github.com", link),
141 ).toBeUndefined();
142 expect(
143 resolvePullRequestTabLink(threads, environmentId, "github.enterprise.test", link),
144 ).toBeUndefined();
145 });
146 it("uses the newest snapshot when several threads link the same PR", () => {
147 const snapshot = {
148 state: "merged" as const,
149 title: "API",
150 headBranch: "api",
151 baseBranch: "main",
152 isDraft: false,
153 updatedAt: null,
154 syncedAt: "2026-02-01T00:00:00Z",
155 };
156 const newer = { ...link, snapshot };
157 expect(
158 resolvePullRequestTabLink(
159 [{ environmentId, pullRequests: [link, newer] }],
160 environmentId,
161 "github.com",
162 link,
163 ),
164 ).toBe(newer);
165 });
166});