feat(ui): a visible way into the command palette
Ctrl/Cmd+K and the shortcut sheet had no entry point but a palette row, behind the other undiscoverable chord. Owner's pick from the S7 mockups (2026-09-19): a key cap at the end of the header's search field opens the palette, and a page with no search field shows a small "Jump to…" pill with the same key cap in its place, so the entry is on every page. The cap reads the chord the palette row binds from the keymap table (Ctrl K, or ⌘ K on a Mac) and stays out of a phone's header, where there is no keyboard to press it on.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// Ctrl/Cmd+K had no visible entry point (debt burn B28). The owner's pick: a key cap in the header's
|
||||
// search field, and a "Jump to…" pill in its place on a page that has no search field.
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent, cleanup, act } from "@testing-library/react";
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("react-i18next")>()),
|
||||
useTranslation: () => ({ t: (k: string) => k, i18n: { language: "en" } }),
|
||||
}));
|
||||
|
||||
let page = "home";
|
||||
vi.mock("./navigationContext", () => ({ useNavigation: () => ({ page }) }));
|
||||
vi.mock("./feedFiltersContext", () => ({
|
||||
useFeedFilters: () => ({ q: "", sort: "newest" }),
|
||||
useFeedFiltersActions: () => ({ setFilters: vi.fn() }),
|
||||
}));
|
||||
vi.mock("./playlistsContext", () => ({
|
||||
usePlaylists: () => ({ search: "" }),
|
||||
usePlaylistsActions: () => ({ setSearch: vi.fn() }),
|
||||
}));
|
||||
vi.mock("./channelsContext", () => ({
|
||||
useChannels: () => ({ search: "" }),
|
||||
useChannelsActions: () => ({ setSearch: vi.fn(), goToFullHistory: vi.fn() }),
|
||||
}));
|
||||
vi.mock("./SyncStatus", () => ({ default: () => null }));
|
||||
|
||||
import Header from "./Header";
|
||||
import { closePalette, usePaletteOpen } from "../lib/keymap";
|
||||
import type { Me } from "../lib/api";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
act(() => closePalette());
|
||||
});
|
||||
|
||||
function mount() {
|
||||
let open = false;
|
||||
function Probe() {
|
||||
open = usePaletteOpen();
|
||||
return null;
|
||||
}
|
||||
render(
|
||||
<>
|
||||
<Header me={{ is_demo: false, plex_enabled: false } as Me} onYtSearch={() => {}} />
|
||||
<Probe />
|
||||
</>,
|
||||
);
|
||||
return () => open;
|
||||
}
|
||||
|
||||
describe("the command palette's visible entry", () => {
|
||||
it("is a key cap inside the search field, naming the chord the palette row binds", () => {
|
||||
page = "feed";
|
||||
const isOpen = mount();
|
||||
const cap = screen.getByRole("button", { name: "palette.open" });
|
||||
expect(cap.textContent).toMatch(/^(Ctrl|⌘) K$/);
|
||||
expect(screen.getByRole("textbox")).toBeTruthy();
|
||||
fireEvent.click(cap);
|
||||
expect(isOpen()).toBe(true);
|
||||
});
|
||||
|
||||
it("is a Jump-to pill on a page with no search field", () => {
|
||||
page = "home";
|
||||
const isOpen = mount();
|
||||
expect(screen.queryByRole("textbox")).toBeNull();
|
||||
const pill = screen.getByRole("button", { name: "palette.open" });
|
||||
expect(pill.textContent).toContain("palette.title");
|
||||
fireEvent.click(pill);
|
||||
expect(isOpen()).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user