feat(KB-188): reorganize header button layout for mobile
- Reorganize Header component button layout for mobile view - Update Header tests with mobile layout coverage - Improve responsive button arrangement in mobile breakpoint
This commit is contained in:
@@ -1,10 +1,26 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { Header } from "./Header";
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
function renderHeader(props = {}) {
|
||||
// Helper to mock mobile/desktop viewport
|
||||
function mockMatchMedia(matches: boolean) {
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query: string) => ({
|
||||
matches: matches && query.includes("max-width: 768px"),
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
function renderHeader(props = {}, isMobile = false) {
|
||||
mockMatchMedia(isMobile);
|
||||
return render(
|
||||
<Header
|
||||
onOpenSettings={noop}
|
||||
@@ -98,20 +114,25 @@ describe("Header", () => {
|
||||
});
|
||||
|
||||
describe("terminal button", () => {
|
||||
it("renders terminal button with correct title", () => {
|
||||
renderHeader({ onToggleTerminal: noop });
|
||||
it("renders terminal button with correct title on desktop", () => {
|
||||
renderHeader({ onToggleTerminal: noop }, false);
|
||||
expect(screen.getByTitle("Open Terminal")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render terminal button inline on mobile", () => {
|
||||
renderHeader({ onToggleTerminal: noop }, true);
|
||||
expect(screen.queryByTitle("Open Terminal")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onToggleTerminal when terminal button is clicked", () => {
|
||||
const onToggleTerminal = vi.fn();
|
||||
renderHeader({ onToggleTerminal });
|
||||
renderHeader({ onToggleTerminal }, false);
|
||||
fireEvent.click(screen.getByTitle("Open Terminal"));
|
||||
expect(onToggleTerminal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("is always enabled regardless of task state", () => {
|
||||
renderHeader({ onToggleTerminal: noop });
|
||||
renderHeader({ onToggleTerminal: noop }, false);
|
||||
const btn = screen.getByTitle("Open Terminal");
|
||||
expect(btn.hasAttribute("disabled")).toBe(false);
|
||||
});
|
||||
@@ -155,42 +176,108 @@ describe("Header", () => {
|
||||
|
||||
describe("usage button", () => {
|
||||
it("does not render usage button when onOpenUsage is not provided", () => {
|
||||
renderHeader();
|
||||
renderHeader({}, false);
|
||||
expect(screen.queryByTitle("View usage")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders usage button with correct title when onOpenUsage is provided", () => {
|
||||
renderHeader({ onOpenUsage: vi.fn() });
|
||||
it("does not render usage button when onOpenUsage is not provided on mobile", () => {
|
||||
renderHeader({}, true);
|
||||
expect(screen.queryByTitle("View usage")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders usage button with correct title when onOpenUsage is provided on desktop", () => {
|
||||
renderHeader({ onOpenUsage: vi.fn() }, false);
|
||||
expect(screen.getByTitle("View usage")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders usage button inline on mobile when onOpenUsage is provided", () => {
|
||||
renderHeader({ onOpenUsage: vi.fn() }, true);
|
||||
expect(screen.getByTitle("View usage")).toBeDefined();
|
||||
});
|
||||
|
||||
it("calls onOpenUsage when usage button is clicked", () => {
|
||||
const onOpenUsage = vi.fn();
|
||||
renderHeader({ onOpenUsage });
|
||||
renderHeader({ onOpenUsage }, false);
|
||||
fireEvent.click(screen.getByTitle("View usage"));
|
||||
expect(onOpenUsage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls onOpenUsage when usage button is clicked on mobile", () => {
|
||||
const onOpenUsage = vi.fn();
|
||||
renderHeader({ onOpenUsage }, true);
|
||||
fireEvent.click(screen.getByTitle("View usage"));
|
||||
expect(onOpenUsage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("planning button", () => {
|
||||
it("renders planning button with correct title", () => {
|
||||
renderHeader({ onOpenPlanning: vi.fn() });
|
||||
it("renders planning button with correct title on desktop", () => {
|
||||
renderHeader({ onOpenPlanning: vi.fn() }, false);
|
||||
expect(screen.getByTitle("Create a task with AI planning")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render planning button inline on mobile", () => {
|
||||
renderHeader({ onOpenPlanning: vi.fn() }, true);
|
||||
expect(screen.queryByTitle("Create a task with AI planning")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onOpenPlanning when planning button is clicked", () => {
|
||||
const onOpenPlanning = vi.fn();
|
||||
renderHeader({ onOpenPlanning });
|
||||
renderHeader({ onOpenPlanning }, false);
|
||||
fireEvent.click(screen.getByTitle("Create a task with AI planning"));
|
||||
expect(onOpenPlanning).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("has correct data-testid for testing", () => {
|
||||
renderHeader({ onOpenPlanning: vi.fn() });
|
||||
it("has correct data-testid for testing on desktop", () => {
|
||||
renderHeader({ onOpenPlanning: vi.fn() }, false);
|
||||
expect(screen.getByTestId("planning-btn")).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("mobile overflow menu", () => {
|
||||
it("renders overflow trigger on mobile", () => {
|
||||
renderHeader({}, true);
|
||||
expect(screen.getByTitle("More header actions")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render overflow trigger on desktop", () => {
|
||||
renderHeader({}, false);
|
||||
expect(screen.queryByTitle("More header actions")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows terminal in overflow menu on mobile", () => {
|
||||
renderHeader({ onToggleTerminal: noop }, true);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
expect(screen.getByTestId("overflow-terminal-btn")).toBeDefined();
|
||||
});
|
||||
|
||||
it("shows GitHub import in overflow menu on mobile", () => {
|
||||
renderHeader({}, true);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
expect(screen.getByText("Import from GitHub")).toBeDefined();
|
||||
});
|
||||
|
||||
it("shows planning in overflow menu on mobile", () => {
|
||||
renderHeader({ onOpenPlanning: noop }, true);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
expect(screen.getByTestId("overflow-planning-btn")).toBeDefined();
|
||||
});
|
||||
|
||||
it("shows settings in overflow menu on mobile", () => {
|
||||
renderHeader({}, true);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
expect(screen.getByText("Settings")).toBeDefined();
|
||||
});
|
||||
|
||||
it("calls onToggleTerminal when overflow terminal button is clicked", () => {
|
||||
const onToggleTerminal = vi.fn();
|
||||
renderHeader({ onToggleTerminal }, true);
|
||||
fireEvent.click(screen.getByTitle("More header actions"));
|
||||
fireEvent.click(screen.getByTestId("overflow-terminal-btn"));
|
||||
expect(onToggleTerminal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("search functionality", () => {
|
||||
it("does not render search input when onSearchChange is not provided", () => {
|
||||
renderHeader({ view: "board" });
|
||||
|
||||
Reference in New Issue
Block a user