mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Sync inbox hover with j/k selection
Hovering an inbox row now sets it as the selected index used by the mail-client j/k keyboard shortcuts, so the keyboard cursor follows the mouse instead of disagreeing with it. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
af7c2bdff5
commit
575311283d
2 files changed
+68
-1
No files matched your search
@@ -138,6 +138,11 @@ vi.mock("@/lib/router", () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
// jsdom doesn't implement scrollIntoView; the inbox calls it from a passive effect.
|
||||
if (typeof Element !== "undefined" && !Element.prototype.scrollIntoView) {
|
||||
Element.prototype.scrollIntoView = () => {};
|
||||
}
|
||||
|
||||
function createIssue(overrides: Partial<Issue> = {}): Issue {
|
||||
return {
|
||||
id: "issue-1",
|
||||
@@ -289,6 +294,59 @@ describe("Inbox toolbar", () => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("syncs hover with j/k selection on inbox rows", async () => {
|
||||
routerMock.location.pathname = "/inbox/mine";
|
||||
const issueA = createIssue({ id: "issue-a", identifier: "PAP-1001", title: "First inbox row" });
|
||||
const issueB = createIssue({ id: "issue-b", identifier: "PAP-1002", title: "Second inbox row" });
|
||||
apiMocks.issuesList.mockResolvedValue([issueA, issueB]);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, staleTime: 0, gcTime: 0 } },
|
||||
});
|
||||
const root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Inbox />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const rows = container.querySelectorAll("[data-inbox-item]");
|
||||
expect(rows.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
const linkOf = (row: Element): HTMLAnchorElement | null =>
|
||||
row.querySelector("a[data-inbox-issue-link]");
|
||||
|
||||
// Nothing selected before hover — both rows show the hover-accent class.
|
||||
expect(linkOf(rows[0]!)?.className).toContain("hover:bg-accent/50");
|
||||
expect(linkOf(rows[1]!)?.className).toContain("hover:bg-accent/50");
|
||||
|
||||
await act(async () => {
|
||||
rows[1]!.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
||||
});
|
||||
|
||||
// After hovering row 1, that row is "selected" — same visual state as j/k selection.
|
||||
expect(linkOf(rows[1]!)?.className).toContain("hover:bg-transparent");
|
||||
expect(linkOf(rows[0]!)?.className).toContain("hover:bg-accent/50");
|
||||
|
||||
await act(async () => {
|
||||
rows[0]!.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
||||
});
|
||||
|
||||
// Hovering a different row moves the selection to follow the mouse.
|
||||
expect(linkOf(rows[0]!)?.className).toContain("hover:bg-transparent");
|
||||
expect(linkOf(rows[1]!)?.className).toContain("hover:bg-accent/50");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("FailedRunInboxRow", () => {
|
||||
|
||||
+10
-1
@@ -2440,6 +2440,9 @@ export function Inbox() {
|
||||
onClick={() => {
|
||||
if (groupNavIdx >= 0) setSelectedIndex(groupNavIdx);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (groupNavIdx >= 0) setSelectedIndex(groupNavIdx);
|
||||
}}
|
||||
>
|
||||
<IssueGroupHeader
|
||||
label={group.label}
|
||||
@@ -2476,6 +2479,7 @@ export function Inbox() {
|
||||
data-inbox-item
|
||||
className="relative"
|
||||
onClick={() => setSelectedIndex(navIdx)}
|
||||
onMouseEnter={() => setSelectedIndex(navIdx)}
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
@@ -2643,7 +2647,12 @@ export function Inbox() {
|
||||
key={`sel-issue:${child.id}`}
|
||||
data-inbox-item
|
||||
className="relative"
|
||||
onClick={() => setSelectedIndex(childNavIdx)}
|
||||
onClick={() => {
|
||||
if (childNavIdx >= 0) setSelectedIndex(childNavIdx);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (childNavIdx >= 0) setSelectedIndex(childNavIdx);
|
||||
}}
|
||||
>
|
||||
{canArchiveIssue ? (
|
||||
<SwipeToArchive
|
||||
|
||||
Reference in new issue
Block a user