* fix(web): filter toggle hover as border, not background (#1460) The Logging/History/Network filter toggles used the same --mantine-primary-color-light background for both their hover and their active (toggled-on) state, so clicking a button to turn it off while the cursor was still over it looked like a no-op — the hover background masked the removal of the active background. - Add a shared FilterToggleButton element used by LogControls, NetworkControls, and MessageDirectionFilter, replacing the duplicated UnstyledButton + inline `bg={active ? … : undefined}` in each. - Add a `filterToggle` UnstyledButton theme variant + `.filter-toggle` rules: hover is now a thin border (reserved via a transparent 1px border so it doesn't shift layout); the active state is the filled background, keyed off `aria-pressed="true"`. - This leaves the `.list-item` selectable rows (Prompts/Apps/Tools/ Resources lists, section headers) untouched — they keep their hover-fill since they aren't toggles. - Add `aria-pressed` to the Logging level toggles (previously missing). Tests + stories cover the active/hover distinction via aria-pressed for FilterToggleButton and all three controls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): move filter-toggle border to CSS so :hover actually shows (#1460) The previous attempt set the reserved `1px solid transparent` border via the theme's `styles` callback, which Mantine emits as an *inline* style. Inline styles outrank stylesheet `:hover` rules, so `.filter-toggle:hover { border-color }` never applied — hovering showed no border at all (verified in a real browser via Playwright). Move the border (and border-radius) out of the inline theme styles into the `.filter-toggle` class in App.css, so `:hover` can override the transparent border-color. The active fill (`[aria-pressed="true"]`) was already correct. Verified with real Playwright page.hover: inactive+hover now shows a 1px --inspector-brand-primary border, active shows the fill, active+hover shows both, all distinct. Story play functions assert the active fill and the reserved transparent border (the hover border can't be asserted in the synthetic-event test runner, so it's documented as Playwright-verified). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
172 lines
5.7 KiB
TypeScript
172 lines
5.7 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import userEvent from "@testing-library/user-event";
|
|
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
|
|
import { renderWithMantine, screen } from "../../../test/renderWithMantine";
|
|
import { LogControls } from "./LogControls";
|
|
|
|
const allVisible: Record<LoggingLevel, boolean> = {
|
|
debug: true,
|
|
info: true,
|
|
notice: true,
|
|
warning: true,
|
|
error: true,
|
|
critical: true,
|
|
alert: true,
|
|
emergency: true,
|
|
};
|
|
|
|
const someVisible: Record<LoggingLevel, boolean> = {
|
|
debug: false,
|
|
info: false,
|
|
notice: false,
|
|
warning: true,
|
|
error: true,
|
|
critical: true,
|
|
alert: true,
|
|
emergency: true,
|
|
};
|
|
|
|
const baseProps = {
|
|
currentLevel: "info" as LoggingLevel,
|
|
filterText: "",
|
|
visibleLevels: allVisible,
|
|
onSetLevel: vi.fn(),
|
|
onFilterChange: vi.fn(),
|
|
onToggleLevel: vi.fn(),
|
|
onToggleAllLevels: vi.fn(),
|
|
};
|
|
|
|
describe("LogControls", () => {
|
|
it("renders the title and search input", () => {
|
|
renderWithMantine(<LogControls {...baseProps} />);
|
|
expect(screen.getByText("Logging")).toBeInTheDocument();
|
|
expect(screen.getByPlaceholderText("Search...")).toBeInTheDocument();
|
|
expect(screen.getByText("Set Active Level")).toBeInTheDocument();
|
|
expect(screen.getByText("Filter by Level")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders a button for each log level", () => {
|
|
renderWithMantine(<LogControls {...baseProps} />);
|
|
expect(screen.getByRole("button", { name: "debug" })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "info" })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "notice" })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "warning" })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "error" })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("button", { name: "critical" }),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "alert" })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("button", { name: "emergency" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("displays the current active level in the select", () => {
|
|
renderWithMantine(<LogControls {...baseProps} currentLevel="warning" />);
|
|
const inputs = screen.getAllByDisplayValue("warning");
|
|
expect(inputs.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("invokes onFilterChange when typing in search", async () => {
|
|
const user = userEvent.setup();
|
|
const onFilterChange = vi.fn();
|
|
renderWithMantine(
|
|
<LogControls {...baseProps} onFilterChange={onFilterChange} />,
|
|
);
|
|
await user.type(screen.getByPlaceholderText("Search..."), "x");
|
|
expect(onFilterChange).toHaveBeenCalledWith("x");
|
|
});
|
|
|
|
it("invokes onSetLevel when Set is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onSetLevel = vi.fn();
|
|
renderWithMantine(
|
|
<LogControls
|
|
{...baseProps}
|
|
currentLevel="warning"
|
|
onSetLevel={onSetLevel}
|
|
/>,
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "Set" }));
|
|
expect(onSetLevel).toHaveBeenCalledWith("warning");
|
|
});
|
|
|
|
it("invokes onSetLevel when a level is selected from dropdown", async () => {
|
|
const user = userEvent.setup();
|
|
const onSetLevel = vi.fn();
|
|
renderWithMantine(<LogControls {...baseProps} onSetLevel={onSetLevel} />);
|
|
// Open the select dropdown
|
|
const inputs = screen.getAllByDisplayValue("info");
|
|
await user.click(inputs[0]);
|
|
const errorOption = await screen.findByRole("option", {
|
|
name: "error",
|
|
hidden: true,
|
|
});
|
|
await user.click(errorOption);
|
|
expect(onSetLevel).toHaveBeenCalledWith("error");
|
|
});
|
|
|
|
it("renders Deselect All when all levels are visible", () => {
|
|
renderWithMantine(<LogControls {...baseProps} />);
|
|
expect(
|
|
screen.getByRole("button", { name: "Deselect All" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Select All when not all levels are visible", () => {
|
|
renderWithMantine(
|
|
<LogControls {...baseProps} visibleLevels={someVisible} />,
|
|
);
|
|
expect(
|
|
screen.getByRole("button", { name: "Select All" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("invokes onToggleAllLevels when toggle-all button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onToggleAllLevels = vi.fn();
|
|
renderWithMantine(
|
|
<LogControls {...baseProps} onToggleAllLevels={onToggleAllLevels} />,
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "Deselect All" }));
|
|
expect(onToggleAllLevels).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("reflects level visibility via aria-pressed", () => {
|
|
renderWithMantine(
|
|
<LogControls {...baseProps} visibleLevels={someVisible} />,
|
|
);
|
|
expect(screen.getByRole("button", { name: "debug" })).toHaveAttribute(
|
|
"aria-pressed",
|
|
"false",
|
|
);
|
|
expect(screen.getByRole("button", { name: "warning" })).toHaveAttribute(
|
|
"aria-pressed",
|
|
"true",
|
|
);
|
|
});
|
|
|
|
it("invokes onToggleLevel when a level button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onToggleLevel = vi.fn();
|
|
renderWithMantine(
|
|
<LogControls {...baseProps} onToggleLevel={onToggleLevel} />,
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "debug" }));
|
|
expect(onToggleLevel).toHaveBeenCalledWith("debug", false);
|
|
});
|
|
|
|
it("invokes onToggleLevel with true when an inactive level is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onToggleLevel = vi.fn();
|
|
renderWithMantine(
|
|
<LogControls
|
|
{...baseProps}
|
|
visibleLevels={someVisible}
|
|
onToggleLevel={onToggleLevel}
|
|
/>,
|
|
);
|
|
await user.click(screen.getByRole("button", { name: "debug" }));
|
|
expect(onToggleLevel).toHaveBeenCalledWith("debug", true);
|
|
});
|
|
});
|