memos/web/tests/sidebar-row-grammar.test.tsx
boojack 04be0d9ee6 chore(web): unify sidebar rows on a shared row grammar
Align every sidebar list (tags in both modes, views, settings, memo
detail) on shared slot and count-rail primitives so icons, labels, and
digits sit on the same vertical lines. Tree branches show their # mark
at rest and swap in the chevron on hover or keyboard focus; views rows
gain glyphs and move onto the same grammar, with ViewsSection extracted
to its own module.
2026-08-31 23:40:47 +08:00

79 lines
2.8 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { HashIcon } from "lucide-react";
import { describe, expect, it, vi } from "vitest";
import SidebarRow, {
SIDEBAR_ROW_BOX_CLASSES,
SIDEBAR_ROW_COUNT_RAIL_CLASSES,
SIDEBAR_ROW_SLOT_CLASSES,
} from "@/components/AppSidebar/SidebarRow";
import SidebarSection, { SIDEBAR_SECTION_CONTENT_CLASSES } from "@/components/AppSidebar/SidebarSection";
import TagTree from "@/components/TagTree";
vi.mock("@/utils/i18n", () => ({ useTranslate: () => (key: string) => key }));
/**
* Tag rows drifted off the rail's row grammar once already (they were 26px/12px against
* everything else's 30px/13px). These lock the shared box: the sizes must come from
* `SIDEBAR_ROW_BOX_CLASSES`, not from values copied into each list.
*/
const boxClasses = SIDEBAR_ROW_BOX_CLASSES.split(" ");
describe("sidebar row grammar", () => {
it("gives every content section the same title and row rhythm", () => {
render(
<SidebarSection label="Statistics">
<SidebarRow label="August 2026" />
</SidebarSection>,
);
expect(screen.getByRole("heading", { name: "Statistics", level: 2 })).toHaveClass("text-2xs", "font-normal");
expect(screen.getByRole("button", { name: "August 2026" }).parentElement).toHaveClass(...SIDEBAR_SECTION_CONTENT_CLASSES.split(" "));
});
it("gives a nav row the shared row box", () => {
render(<SidebarRow icon={HashIcon} label="Tasks" count={3} />);
const row = screen.getByRole("button", { name: "Tasks3" });
expect(row).toHaveClass(...boxClasses);
// Icon in the shared slot and count in the shared rail, so every list — nav rows,
// views, tags in both modes — keeps its icons and digits on the same vertical lines.
expect(row.firstElementChild).toHaveClass(...SIDEBAR_ROW_SLOT_CLASSES.split(" "));
expect(screen.getByText("3")).toHaveClass(...SIDEBAR_ROW_COUNT_RAIL_CLASSES.split(" "));
});
it("gives tag tree rows the same box as a nav row", () => {
render(
<TagTree
tagAmounts={[
["a", 2],
["a/b", 1],
]}
scope="home"
onTagClick={vi.fn()}
/>,
);
for (const item of screen.getAllByRole("treeitem")) {
expect(item).toHaveClass(...boxClasses);
}
});
it("indents nested tags by the same step the memo outline uses", () => {
render(
<TagTree
tagAmounts={[
["a", 2],
["a/b", 1],
]}
// The active tag keeps the nested row expanded into view.
activeTag="a/b"
scope="home"
onTagClick={vi.fn()}
/>,
);
const [root, child] = screen.getAllByRole("treeitem");
expect(root).toHaveStyle({ paddingInlineStart: "8px" });
expect(child).toHaveStyle({ paddingInlineStart: "20px" });
});
});