| ... | @@ -2,7 +2,12 @@ import type { DesktopPreviewFavicon, PreviewSessionSnapshot } from "@t3tools/con | ... | @@ -2,7 +2,12 @@ import type { DesktopPreviewFavicon, PreviewSessionSnapshot } from "@t3tools/con |
| 2 | import { renderToStaticMarkup } from "react-dom/server"; | 2 | import { renderToStaticMarkup } from "react-dom/server"; |
| 3 | import { describe, expect, it } from "vite-plus/test"; | 3 | import { describe, expect, it } from "vite-plus/test"; |
| 4 | | 4 | |
| 5 | import { RightPanelTabs, surfaceShortcutActionForKey, tabMuteMenuItem } from "./RightPanelTabs"; | 5 | import { |
| | 6 | RightPanelTabs, |
| | 7 | surfaceShortcutActionForKey, |
| | 8 | surfaceShortcutTargetsTypingContext, |
| | 9 | tabMuteMenuItem, |
| | 10 | } from "./RightPanelTabs"; |
| 6 | | 11 | |
| 7 | function shortcutEvent( | 12 | function shortcutEvent( |
| 8 | key: string, | 13 | key: string, |
| ... | @@ -166,6 +171,33 @@ describe("surface shortcuts", () => { | ... | @@ -166,6 +171,33 @@ describe("surface shortcuts", () => { |
| 166 | }); | 171 | }); |
| 167 | }); | 172 | }); |
| 168 | | 173 | |
| | 174 | describe("surface shortcut typing contexts", () => { |
| | 175 | // Selector-aware stub: closest() answers only tokens the combined selector |
| | 176 | // would actually match, mirroring how the browser resolves it. |
| | 177 | const makeTarget = (matches: string | null) => ({ |
| | 178 | closest(selectors: string) { |
| | 179 | if (matches === null || !selectors.includes(matches)) return null; |
| | 180 | return {}; |
| | 181 | }, |
| | 182 | }); |
| | 183 | |
| | 184 | it("treats form fields and every editable region as typing contexts", () => { |
| | 185 | expect(surfaceShortcutTargetsTypingContext(makeTarget("input"))).toBe(true); |
| | 186 | expect(surfaceShortcutTargetsTypingContext(makeTarget("textarea"))).toBe(true); |
| | 187 | expect(surfaceShortcutTargetsTypingContext(makeTarget("select"))).toBe(true); |
| | 188 | // The chat composer is a contenteditable that sits empty until a draft |
| | 189 | // exists; launcher letters claimed from it redirected prompts into shells. |
| | 190 | // The :not clause sees past contenteditable="false" islands to an editable |
| | 191 | // host around them, so nested editors stay protected too. |
| | 192 | expect(surfaceShortcutTargetsTypingContext(makeTarget("[contenteditable]"))).toBe(true); |
| | 193 | }); |
| | 194 | |
| | 195 | it("claims letters when focus sits outside any editable region", () => { |
| | 196 | expect(surfaceShortcutTargetsTypingContext(null)).toBe(false); |
| | 197 | expect(surfaceShortcutTargetsTypingContext(makeTarget(null))).toBe(false); |
| | 198 | }); |
| | 199 | }); |
| | 200 | |
| 169 | describe("RightPanelTabs audio indicator", () => { | 201 | describe("RightPanelTabs audio indicator", () => { |
| 170 | // A muted tab only shows the indicator while it is actually making sound: | 202 | // A muted tab only shows the indicator while it is actually making sound: |
| 171 | // arming mute on a quiet tab is deliberate and stays invisible until there | 203 | // arming mute on a quiet tab is deliberate and stays invisible until there |