feat(FN-1330): consolidate mobile Board/List tabs into Tasks tab with header toggle

- Merge mobile Board and List tabs into unified Tasks tab in MobileNavBar
- Add Board/List toggle to Header component for switching views within Tasks tab
- Add comprehensive Header tests covering toggle functionality and keyboard navigation
- Update mobile feature access tests to reflect new tab structure
- Add mobile task view toggle CSS styles
This commit is contained in:
Fusion
2026-04-15 09:10:38 -07:00
committed by gsxdsm
parent 9c31c1136f
commit 13b80f89e4
6 changed files with 150 additions and 47 deletions

View File

@@ -921,10 +921,74 @@ describe("Header", () => {
expect(screen.getByTitle("List view")).toBeDefined();
});
it("hides view toggle when mobileNavEnabled is true", () => {
it("hides desktop view toggle when mobileNavEnabled is true (mobile view toggle shown separately)", () => {
// When mobileNavEnabled, the full desktop-style view toggle (with agents, missions, etc.)
// should be hidden. Instead, a compact board/list-only toggle appears via mobile-view-toggle.
render(<Header view="board" onChangeView={vi.fn()} mobileNavEnabled={true} />);
expect(screen.queryByTitle("Board view")).toBeNull();
expect(screen.queryByTitle("List view")).toBeNull();
// The full desktop toggle is hidden
expect(screen.queryByTitle("Agents view")).toBeNull();
expect(screen.queryByTitle("Missions view")).toBeNull();
// But the mobile compact toggle is shown
expect(screen.getByTestId("mobile-view-toggle")).toBeDefined();
});
it("renders mobile view toggle when mobileNavEnabled and view is board", () => {
const onChangeView = vi.fn();
render(<Header view="board" onChangeView={onChangeView} mobileNavEnabled={true} />);
expect(screen.getByTestId("mobile-view-toggle")).toBeDefined();
expect(screen.getByTestId("mobile-view-toggle-board")).toBeDefined();
expect(screen.getByTestId("mobile-view-toggle-list")).toBeDefined();
});
it("renders mobile view toggle when mobileNavEnabled and view is list", () => {
render(<Header view="list" onChangeView={vi.fn()} mobileNavEnabled={true} />);
expect(screen.getByTestId("mobile-view-toggle")).toBeDefined();
expect(screen.getByTestId("mobile-view-toggle-board")).toBeDefined();
expect(screen.getByTestId("mobile-view-toggle-list")).toBeDefined();
});
it("does not render mobile view toggle when mobileNavEnabled and view is agents", () => {
render(<Header view="agents" onChangeView={vi.fn()} mobileNavEnabled={true} />);
expect(screen.queryByTestId("mobile-view-toggle")).toBeNull();
});
it("does not render mobile view toggle when mobileNavEnabled and view is missions", () => {
render(<Header view="missions" onChangeView={vi.fn()} mobileNavEnabled={true} />);
expect(screen.queryByTestId("mobile-view-toggle")).toBeNull();
});
it("mobile view toggle board button calls onChangeView('board')", () => {
const onChangeView = vi.fn();
render(<Header view="list" onChangeView={onChangeView} mobileNavEnabled={true} />);
fireEvent.click(screen.getByTestId("mobile-view-toggle-board"));
expect(onChangeView).toHaveBeenCalledWith("board");
});
it("mobile view toggle list button calls onChangeView('list')", () => {
const onChangeView = vi.fn();
render(<Header view="board" onChangeView={onChangeView} mobileNavEnabled={true} />);
fireEvent.click(screen.getByTestId("mobile-view-toggle-list"));
expect(onChangeView).toHaveBeenCalledWith("list");
});
it("mobile view toggle board button is active when view is board", () => {
render(<Header view="board" onChangeView={vi.fn()} mobileNavEnabled={true} />);
const boardBtn = screen.getByTestId("mobile-view-toggle-board");
const listBtn = screen.getByTestId("mobile-view-toggle-list");
expect(boardBtn.className).toContain("active");
expect(boardBtn.getAttribute("aria-pressed")).toBe("true");
expect(listBtn.className).not.toContain("active");
expect(listBtn.getAttribute("aria-pressed")).toBe("false");
});
it("mobile view toggle list button is active when view is list", () => {
render(<Header view="list" onChangeView={vi.fn()} mobileNavEnabled={true} />);
const boardBtn = screen.getByTestId("mobile-view-toggle-board");
const listBtn = screen.getByTestId("mobile-view-toggle-list");
expect(listBtn.className).toContain("active");
expect(listBtn.getAttribute("aria-pressed")).toBe("true");
expect(boardBtn.className).not.toContain("active");
expect(boardBtn.getAttribute("aria-pressed")).toBe("false");
});
it("hides overflow trigger when mobileNavEnabled is true", () => {

View File

@@ -56,11 +56,10 @@ describe("MobileNavBar", () => {
mockViewport("mobile");
});
it("renders nine tab buttons (board + list + agents + missions + chat + mailbox + skills + roadmaps + more)", () => {
it("renders eight tab buttons (tasks + agents + missions + chat + mailbox + skills + roadmaps + more)", () => {
render(<MobileNavBar {...createDefaultProps()} />);
expect(screen.getByTestId("mobile-nav-tab-board")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-agents")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-missions")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-chat")).toBeDefined();
@@ -95,38 +94,43 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-nav-tab-agents").className).toContain("mobile-nav-tab--active");
});
it("board tab calls onChangeView with 'board'", () => {
it("tasks tab calls onChangeView with 'board' when coming from a non-tasks view", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="list" />);
render(<MobileNavBar {...props} view="agents" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-board"));
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
it("list tab calls onChangeView with 'list'", () => {
it("tasks tab calls onChangeView with 'board' when already on board", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="board" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-list"));
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
it("tasks tab calls onChangeView with 'list' when already on list", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="list" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("board tab is active when view is 'board'", () => {
it("tasks tab is active when view is 'board'", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" />);
expect(screen.getByTestId("mobile-nav-tab-board").className).toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-list").className).not.toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-tasks").className).toContain("mobile-nav-tab--active");
});
it("list tab is active when view is 'list'", () => {
it("tasks tab is active when view is 'list'", () => {
render(<MobileNavBar {...createDefaultProps()} view="list" />);
expect(screen.getByTestId("mobile-nav-tab-list").className).toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-board").className).not.toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-tasks").className).toContain("mobile-nav-tab--active");
});
it("board and list tabs are not active when view is 'agents'", () => {
it("tasks tab is not active when view is 'agents'", () => {
render(<MobileNavBar {...createDefaultProps()} view="agents" />);
expect(screen.getByTestId("mobile-nav-tab-board").className).not.toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-list").className).not.toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-tasks").className).not.toContain("mobile-nav-tab--active");
});
it("missions tab calls onChangeView with 'missions'", () => {