FN-070: streamline mobile navigation and task creation

Refine mobile project navigation so task creation and view switching remain accessible without redundant header controls.

- Add a Header New Task action backed by the existing full task form.
- Move mobile Board/List destinations to the bottom navigation and preserve Planning quick entry.
- Update responsive documentation, changeset metadata, and regression coverage.

Files changed:
 .changeset/fn-070-mobile-navigation.md             |  7 ++++
 docs/dashboard-guide.md                            |  2 +-
 packages/dashboard/app/App.tsx                     |  1 +
 packages/dashboard/app/__tests__/mobile-feature-access-regression.test.tsx      | 25 +++++++------
 packages/dashboard/app/components/Board.tsx        |  9 ++++-
 packages/dashboard/app/components/Header.tsx       | 43 ++++++++++-------------
 packages/dashboard/app/components/ListView.tsx     |  2 +-
 packages/dashboard/app/components/MobileNavBar.tsx | 20 ++++++++--
 .../app/components/__tests__/App.test.tsx          | 14 ++++---
 .../app/components/__tests__/Board.test.tsx        | 17 +++++++++
 .../app/components/__tests__/Header.test.tsx       | 16 +++++++-
 .../app/components/__tests__/ListView.test.tsx     |  5 ++-
 .../app/components/__tests__/MobileNavBar.test.tsx | 24 +++++++-----
 13 files changed, 123 insertions(+), 62 deletions(-)

Fusion-Task-Id: FN-070
Fusion-Task-Lineage: 17bdd4fe-a2ef-481e-9083-6fb1c2d6425f
Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
Fusion Agent
2026-08-20 06:15:23 +00:00
parent 0fce621729
commit 0b71e9f55e
13 changed files with 123 additions and 62 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
summary: Make mobile task creation and Board/List navigation available from every project view.
category: feature
dev: Moves the mobile full-task trigger to Header while preserving Planning quick entry.

View File

@@ -198,7 +198,7 @@ While the sidebar is active on desktop/tablet project screens, Board and List wo
The active nav-item highlight and the resize-handle hover/focus accent track the active color theme's `--accent` token across all themes, so shadcn, forest, ocean, and other themes no longer show a fixed blue selected state. The Header retains the Fusion brand and project selector, keeps non-navigation controls, and hides duplicate desktop view-toggle entries while the sidebar is active.
On mobile viewports (`<=768px`), the sidebar is not rendered even when the default-on setting is enabled. The existing bottom `MobileNavBar` remains the navigation surface on project task screens, with mobile-only More-sheet entries for compact tools such as Git Manager, Terminal, Files, and **Import from GitHub**.
On mobile viewports (`<=768px`), the sidebar is not rendered even when the default-on setting is enabled. The Header's **New Task** action opens the existing full form from any active project view. The bottom `MobileNavBar` provides separate **Tasks** (Board) and **List** destinations, while the Planning column keeps its inline quick-entry composer. Mobile-only More-sheet entries remain available for compact tools such as Git Manager, Terminal, Files, and **Import from GitHub**.
<!-- FNXC:DashboardResponsiveDocs 2026-07-25-22:57: Project overview has no task destinations, so the mobile navigation and its published height must be absent rather than leaving dead bottom space. Document the 320px responsive and token conventions alongside the operator-visible behavior. -->
<!-- FNXC:ProjectOverviewHealthHydration 2026-08-01-15:40: Registered projects are the navigation-critical content of the all-projects view, so optional per-project health telemetry hydrates progressively rather than delaying cards and their controls. -->

View File

@@ -1913,6 +1913,7 @@ function AppInner() {
filesOpen={modalManager.filesOpen}
view={taskView}
onChangeView={viewMode === "project" && currentProject ? handleTaskViewChange : undefined}
onNewTask={viewMode === "project" && currentProject ? openNewTaskWithNav : undefined}
showSkillsTab={skillsEnabled}
showAgentsTab={agentsEnabled}
searchQuery={searchQuery}

View File

@@ -133,26 +133,29 @@ describe("Mobile Feature Access Regression Guard", () => {
document.documentElement.style.removeProperty("--mobile-nav-height");
});
it("list view is accessible via mobile nav bar", () => {
it("list view is independently accessible via mobile nav bar", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} view="board" />);
const tasksTab = screen.getByTestId("mobile-nav-tab-tasks");
expect(tasksTab.textContent).toContain("Tasks");
fireEvent.click(tasksTab);
expect(props.onChangeView).toHaveBeenCalledWith("board");
fireEvent.click(screen.getByTestId("mobile-nav-tab-list"));
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("board view is accessible via mobile nav bar", () => {
it("board view is accessible from Tasks on mobile nav bar", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} view="list" />);
const tasksTab = screen.getByTestId("mobile-nav-tab-tasks");
expect(tasksTab.textContent).toContain("Tasks");
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
fireEvent.click(tasksTab);
expect(props.onChangeView).toHaveBeenCalledWith("list");
it("mobile Header exposes New Task without the retired view toggle", () => {
const onNewTask = vi.fn();
render(<Header projectId="proj_1" mobileNavEnabled onNewTask={onNewTask} />);
expect(screen.queryByTestId("mobile-view-toggle")).toBeNull();
fireEvent.click(screen.getByTestId("mobile-header-new-task"));
expect(onNewTask).toHaveBeenCalledOnce();
});
it("agents view is accessible via mobile nav bar", () => {

View File

@@ -226,6 +226,11 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
setBoardElement((current) => current === element ? current : element);
}, []);
const viewportMode = useViewportMode();
/*
FNXC:MobileTaskNavigation 2026-08-20-05:47:
Issue #2226 moves only the mobile full-task modal trigger to Header. Keep intake quick-create props on every viewport so Planning remains an inline composer.
*/
const mobileFullTaskModalHidden = viewportMode === "mobile";
useColumnScrollSnap(boardElement, { mobileOnly: true });
/*
FNXC:BoardNavigation 2026-08-20-04:47:
@@ -1006,7 +1011,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
mergeStrategy={mergeStrategy}
// FNXC:PlanApproval 2026-07-07-00:00: FN-7653 — the plan auto-approve shortcut belongs only to the intake/planning column, never to hold (Todo-like) columns; the built-in Coding workflow's Todo column carries the hold trait and was wrongly receiving this prop pair.
{...((columnDef.flags.intake && !columnDef.flags.archived && !columnDef.flags.complete && !columnDef.flags.countsTowardWip && !columnDef.flags.mergeBlocker && !columnDef.flags.humanReview) ? { planAutoApproveEnabled, onTogglePlanAutoApprove } : {})}
{...(isCreateColumn && aggregateQuickCreateTarget ? { workflowId: aggregateQuickCreateTarget.workflowId, workflowOptions, defaultWorkflowId: boardWorkflows?.defaultWorkflowId ?? null, onQuickCreate: handleAggregateWorkflowQuickCreate, onNewTask: handleAggregateWorkflowNewTask, onSubtaskBreakdown } : {})}
{...(isCreateColumn && aggregateQuickCreateTarget ? { workflowId: aggregateQuickCreateTarget.workflowId, workflowOptions, defaultWorkflowId: boardWorkflows?.defaultWorkflowId ?? null, onQuickCreate: handleAggregateWorkflowQuickCreate, ...(!mobileFullTaskModalHidden ? { onNewTask: handleAggregateWorkflowNewTask } : {}), onSubtaskBreakdown } : {})}
{...(columnDef.flags.mergeBlocker || columnDef.flags.humanReview ? { onToggleAutoMerge: handleToggleAutoMerge } : {})}
{...(columnDefOffersArchiveAllDone(columnDef) ? { onArchiveAllDone } : {})}
{...(laneSortModeChange ? { sortMode: laneSortMode, onSortModeChange: laneSortModeChange } : {})}
@@ -1101,7 +1106,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
mergeStrategy={mergeStrategy}
// FNXC:PlanApproval 2026-07-07-00:00: FN-7653 — the plan auto-approve shortcut belongs only to the intake/planning column, never to hold (Todo-like) columns; the built-in Coding workflow's Todo column carries the hold trait and was wrongly receiving this prop pair.
{...((columnDef.flags.intake && !columnDef.flags.archived && !columnDef.flags.complete && !columnDef.flags.countsTowardWip && !columnDef.flags.mergeBlocker && !columnDef.flags.humanReview) ? { planAutoApproveEnabled, onTogglePlanAutoApprove } : {})}
{...(isCreateColumn ? { workflowOptions, defaultWorkflowId: selectedWorkflow.id, onQuickCreate: handleWorkflowQuickCreate, onNewTask: handleSelectedWorkflowNewTask, onSubtaskBreakdown } : {})}
{...(isCreateColumn ? { workflowOptions, defaultWorkflowId: selectedWorkflow.id, onQuickCreate: handleWorkflowQuickCreate, ...(!mobileFullTaskModalHidden ? { onNewTask: handleSelectedWorkflowNewTask } : {}), onSubtaskBreakdown } : {})}
{...(columnDef.flags.mergeBlocker || columnDef.flags.humanReview ? { onToggleAutoMerge: handleToggleAutoMerge } : {})}
{...(columnDefOffersArchiveAllDone(columnDef) ? { onArchiveAllDone } : {})}
{...{ sortMode: laneSortMode, onSortModeChange: laneSortModeChange, doneSortMode: laneSortMode, onDoneSortModeChange: laneSortModeChange }}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useCallback, useMemo, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Settings, LayoutGrid, List, Search, X, Activity, MoreHorizontal, Clock, Folder, History, GitBranch, Monitor, Workflow, Bot, Target, Grid3X3, Mail, MessageSquare, Check, Zap, Sparkles, FileText, Brain, Lock, Gauge, Lightbulb, ChevronDown, ChevronRight, PanelRight, Star } from "lucide-react";
import { Settings, LayoutGrid, List, Search, X, Activity, MoreHorizontal, Clock, Folder, History, GitBranch, Monitor, Workflow, Bot, Target, Grid3X3, Mail, MessageSquare, Check, Zap, Sparkles, FileText, Brain, Lock, Gauge, Lightbulb, ChevronDown, ChevronRight, PanelRight, Plus, Star } from "lucide-react";
import "./Header.css";
// ProjectSelector styles used by the imported standalone component.
import "./ProjectSelector.css";
@@ -74,6 +74,8 @@ export interface HeaderProps {
filesOpen?: boolean;
view?: TaskView;
onChangeView?: (view: TaskView) => void;
/** Opens the existing App-owned full New Task modal. */
onNewTask?: () => void;
/** Whether to show the skills tab in the view toggle */
showSkillsTab?: boolean;
/** When true, shows the Agents view tab button. Hidden by default (experimental feature). */
@@ -137,6 +139,7 @@ export function Header({
onOpenFiles,
view = "board",
onChangeView,
onNewTask,
showSkillsTab,
showAgentsTab,
searchQuery = "",
@@ -621,30 +624,20 @@ export function Header({
<div className="header-actions">
{shellConnectionControl}
{/* Mobile View Toggle - compact board/list switcher in header when mobile nav is active */}
{hideFullNav && onChangeView && (view === "board" || view === "list") && (
<div className="view-toggle" data-testid="mobile-view-toggle">
<button
className={`view-toggle-btn${view === "board" ? " active" : ""}`}
onClick={() => onChangeView("board")}
title={t("header.boardView", "Board view")}
aria-label={t("header.boardView", "Board view")}
aria-pressed={view === "board"}
data-testid="mobile-view-toggle-board"
>
<LayoutGrid size={16} />
</button>
<button
className={`view-toggle-btn${view === "list" ? " active" : ""}`}
onClick={() => onChangeView("list")}
title={t("header.listView", "List view")}
aria-label={t("header.listView", "List view")}
aria-pressed={view === "list"}
data-testid="mobile-view-toggle-list"
>
<List size={16} />
</button>
</div>
{/*
FNXC:MobileTaskNavigation 2026-08-20-05:47:
Issue #2226 moves mobile Board/List navigation to the footer so Header can expose App's single full-task modal entry point from every active project view. The Planning column keeps its separate quick-entry composer.
*/}
{isMobile && mobileNavEnabled && projectId && onNewTask && (
<button
className="btn-icon"
onClick={onNewTask}
title={t("newTaskModal.title", "New Task")}
aria-label={t("newTaskModal.title", "New Task")}
data-testid="mobile-header-new-task"
>
<Plus />
</button>
)}
{/* Mobile Search Trigger - only on mobile, show trigger button in header */}

View File

@@ -2710,7 +2710,7 @@ export function ListView({
<Columns3 size={14} />
{t("listView.viewOptions", "View")}
</button>
{onNewTask ? (
{onNewTask && !isMobile ? (
<button
className="btn btn-task-create btn-sm list-new-task-action"
onClick={() => onNewTask(isAllWorkflowsSelected ? undefined : selectedWorkflow?.id)}

View File

@@ -13,6 +13,7 @@ import {
GitBranch,
Grid3X3,
LayoutGrid,
List,
Lightbulb,
Loader2,
Lock,
@@ -440,7 +441,11 @@ export function MobileNavBar({
badge?: number;
}> = {
"command-center": { icon: <Gauge />, labelKey: "nav.commandCenter", fallback: "Dashboard", moreTestId: "mobile-more-item-command-center", isActive: view === "command-center", isAvailable: true, navigate: () => onChangeView("command-center") },
tasks: { icon: <LayoutGrid />, labelKey: "nav.tasks", fallback: "Tasks", moreTestId: "mobile-more-item-tasks", isActive: view === "board" || view === "list", isAvailable: true, navigate: () => onChangeView(view === "board" || view === "list" ? view : "board") },
/*
FNXC:MobileTaskNavigation 2026-08-20-05:47:
Issue #2226 requires independent mobile footer destinations: Tasks always returns to Board and List remains directly reachable without restoring Header's retired segmented switcher.
*/
tasks: { icon: <LayoutGrid />, labelKey: "nav.tasks", fallback: "Tasks", moreTestId: "mobile-more-item-tasks", isActive: view === "board", isAvailable: true, navigate: () => onChangeView("board") },
agents: { icon: <Bot />, labelKey: "nav.agents", fallback: "Agents", moreTestId: "mobile-more-item-agents", isActive: view === "agents", isAvailable: true, navigate: () => onChangeView("agents") },
missions: { icon: <Target />, labelKey: "nav.missions", fallback: "Missions", moreTestId: "mobile-more-item-missions", isActive: view === "missions", isAvailable: true, navigate: () => onChangeView("missions") },
chat: { icon: <MessageSquare />, labelKey: "nav.chat", fallback: "Chat", moreTestId: "mobile-more-item-chat", isActive: view === "chat", isAvailable: true, navigate: () => onChangeView("chat"), indicator: chatHasUnreadResponse && view !== "chat", indicatorLabel: t("nav.chatUnreadAriaLabel", "Unread chat response") },
@@ -489,8 +494,17 @@ export function MobileNavBar({
aria-label={t("nav.primaryNavAriaLabel", "Primary navigation")}
>
{effectivePrimaryItems.map((item) => renderSelectableItem(item, "primary"))}
<button
type="button"
className={`mobile-nav-tab${view === "list" ? " mobile-nav-tab--active" : ""}`}
data-testid="mobile-nav-tab-list"
role="tab"
aria-selected={view === "list"}
onClick={() => onChangeView("list")}
>
<span className="mobile-nav-tab-icon-wrapper"><List /></span>
<span className="mobile-nav-tab-label">{t("nav.list", "List")}</span>
</button>
{topLevelPrimaryPluginViews.map((entry) => {
const pluginTaskView = buildPluginTaskViewId(entry.pluginId, entry.view.viewId);

View File

@@ -707,7 +707,8 @@ async function waitForAppShell(): Promise<void> {
await waitFor(() => {
expect(fetchSettings).toHaveBeenCalled();
if (mockUseViewportMode() === "mobile") {
expect(screen.getByTestId("mobile-view-toggle")).toBeTruthy();
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeTruthy();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeTruthy();
} else {
expect(screen.getByTitle("Settings")).toBeTruthy();
}
@@ -781,7 +782,7 @@ describe("FN-8698 retained Board and List task popups", () => {
const overlayFor = (view: "board" | "list") => `floating-window-overlay-task-detail-FN-8698-${view}`;
const showView = async (view: "board" | "list") => {
const navigationTestId = viewport === "mobile"
? `mobile-view-toggle-${view}`
? `mobile-nav-tab-${view === "board" ? "tasks" : "list"}`
: `sidebar-nav-${view}`;
fireEvent.click(screen.getByTestId(navigationTestId));
await waitFor(() => expect(document.querySelector(taskSelector(view))).toBeTruthy());
@@ -4873,7 +4874,8 @@ describe("FN-5817 mobile auto-merge toggle stability", () => {
render(<App />);
const toggle = await screen.findByRole("checkbox", { name: "Auto-merge" });
expect(screen.getByTestId("mobile-view-toggle")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeInTheDocument();
expect(document.querySelector("main.board")).not.toBeNull();
expect(screen.getByText("In review task")).toBeInTheDocument();
expect(screen.queryByText("Something went wrong")).toBeNull();
@@ -4885,7 +4887,8 @@ describe("FN-5817 mobile auto-merge toggle stability", () => {
expect.objectContaining({ autoMerge: expect.any(Boolean) }),
DEFAULT_PROJECT_ID,
);
expect(screen.getByTestId("mobile-view-toggle")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeInTheDocument();
expect(screen.getByRole("checkbox", { name: "Auto-merge" })).toBeInTheDocument();
expect(document.querySelector("main.board")).not.toBeNull();
expect(screen.getByText("In review task")).toBeInTheDocument();
@@ -4955,7 +4958,8 @@ describe("App shell connection status plumbing", () => {
await waitFor(() => {
expect(mockGetShellConnectionNativeResult).toHaveBeenCalledWith(mockShellHostContextValue.host);
expect(screen.getByTestId("mobile-view-toggle")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeInTheDocument();
});
expect(screen.queryByTestId("shell-connection-status-button")).toBeNull();

View File

@@ -1790,6 +1790,23 @@ describe("Board", () => {
expect(onNewTask).toHaveBeenCalledWith(CUSTOM_WORKFLOW.id);
});
it("keeps intake quick entry while omitting its full task button on mobile", async () => {
const mobile = installMobileBoardStabilizationHarness();
try {
const onNewTask = vi.fn();
enableFlag({ "FN-1": CUSTOM_WORKFLOW.id }, [DEFAULT_WORKFLOW, CUSTOM_WORKFLOW]);
renderBoard({ tasks: [mkTask({ id: "FN-1", column: "intake" })], onNewTask });
await selectWorkflow(CUSTOM_WORKFLOW.id);
const intakeColumn = screen.getByTestId("column-intake");
expect(intakeColumn).toHaveAttribute("data-has-quick-create", "yes");
expect(intakeColumn).toHaveAttribute("data-has-new-task", "no");
expect(screen.queryByTestId("mock-new-task-intake")).toBeNull();
} finally {
mobile.restore();
}
});
it("defaults All workflows quick-add to the default workflow and resolves selected workflow columns", async () => {
const onQuickCreate = vi.fn().mockResolvedValue({ id: "FN-new", workflowId: "wf-custom" });
enableFlag({}, [DEFAULT_WORKFLOW, CUSTOM_WORKFLOW]);

View File

@@ -185,8 +185,20 @@ describe("Header", () => {
expect(workflowSlot).toBeInTheDocument();
expect(workflowSlot).toHaveClass("header-workflow-slot--mobile");
expect(workflowSlot.closest(".header-left")).toBeInTheDocument();
expect(screen.getByTestId("mobile-view-toggle-board")).toBeInTheDocument();
expect(screen.getByTestId("mobile-view-toggle-list")).toBeInTheDocument();
expect(screen.queryByTestId("mobile-view-toggle")).toBeNull();
expect(screen.queryByTestId("mobile-view-toggle-board")).toBeNull();
expect(screen.queryByTestId("mobile-view-toggle-list")).toBeNull();
});
it("renders one mobile New Task action only for an active project callback", () => {
const onNewTask = vi.fn();
const rendered = renderHeader({ mobileNavEnabled: true, projectId: "project-1", onNewTask }, "mobile");
fireEvent.click(screen.getByTestId("mobile-header-new-task"));
expect(onNewTask).toHaveBeenCalledOnce();
rendered.unmount();
renderHeader({ mobileNavEnabled: true, onNewTask }, "mobile");
expect(screen.queryByTestId("mobile-header-new-task")).toBeNull();
});
it("shows board view as active by default", () => {

View File

@@ -3300,13 +3300,14 @@ describe("ListView", () => {
expect(desktopSplitRule).toContain("grid-template-columns: auto 0 minmax(0, 1fr)");
});
it("keeps Bulk Edit, View, and + New Task together in the mobile toolbar controls", () => {
it("omits the full New Task button from mobile toolbar controls", () => {
const viewportSpy = mockMobileViewport();
renderListView({}, { openViewOptions: false });
const actions = document.querySelector(".list-toolbar .list-action-cluster");
const actionButtons = Array.from(actions?.querySelectorAll("button") ?? []).map((button) => button.textContent);
expect(actionButtons).toEqual(["Bulk Edit", "View", "+ New Task"]);
expect(actionButtons).toEqual(["Bulk Edit", "View"]);
expect(screen.queryByText("+ New Task")).toBeNull();
viewportSpy.mockRestore();
});

View File

@@ -137,7 +137,7 @@ describe("MobileNavBar", () => {
vi.useRealTimers();
});
it("renders seven top-level tab buttons (command center + tasks + agents + missions + chat + mailbox + more) and keeps skills in More when showSkillsTab is true", () => {
it("renders eight top-level tab buttons including dedicated List and keeps skills in More when showSkillsTab is true", () => {
render(<MobileNavBar {...createDefaultProps()} showSkillsTab={true} />);
expect(screen.getByTestId("mobile-nav-tab-command-center")).toBeDefined();
@@ -146,6 +146,7 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-nav-tab-missions")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-chat")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-list")).toBeDefined();
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
expect(screen.queryByTestId("mobile-nav-tab-roadmaps")).toBeNull();
expect(screen.getByTestId("mobile-nav-tab-more")).toBeDefined();
@@ -243,14 +244,14 @@ describe("MobileNavBar", () => {
mailboxPendingApprovalCount={2}
/>,
);
expectUniformMobileNavColumns(sevenTabRender.container, 7);
expectUniformMobileNavColumns(sevenTabRender.container, 8);
expect(screen.getByTestId("mobile-nav-tab-command-center").className).toContain("mobile-nav-tab--active");
expect(screen.getByLabelText("Unread chat response")).toBeInTheDocument();
expect(screen.getByLabelText("Pending approvals")).toBeInTheDocument();
expect(screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge")?.textContent).toBe("7");
sevenTabRender.unmount();
// Skills is never a top-level tab, so enabling it keeps the top-level column count at seven
// Skills is never a top-level tab, so enabling it keeps the top-level column count at eight
// and the skills destination, plus its active view, lives in the More sheet.
const skillsEnabledRender = render(
<MobileNavBar
@@ -262,7 +263,7 @@ describe("MobileNavBar", () => {
mailboxPendingApprovalCount={1}
/>,
);
expectUniformMobileNavColumns(skillsEnabledRender.container, 7);
expectUniformMobileNavColumns(skillsEnabledRender.container, 8);
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
expect(screen.getByTestId("mobile-nav-tab-more").className).toContain("mobile-nav-tab--active");
expect(screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge")?.textContent).toBe("99+");
@@ -282,7 +283,7 @@ describe("MobileNavBar", () => {
]}
/>,
);
expectUniformMobileNavColumns(pluginVariantRender.container, 7);
expectUniformMobileNavColumns(pluginVariantRender.container, 8);
expect(screen.queryByTestId("mobile-nav-tab-plugin-fusion-plugin-spacing-check-wide")).toBeNull();
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.getByTestId("mobile-more-item-plugin-fusion-plugin-spacing-check-wide")).toBeDefined();
@@ -573,12 +574,12 @@ describe("MobileNavBar", () => {
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
it("tasks tab calls onChangeView with 'list' when already on list", () => {
it("tasks tab returns to board when currently on list", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="list" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(props.onChangeView).toHaveBeenCalledWith("list");
expect(props.onChangeView).toHaveBeenCalledWith("board");
});
it("tasks tab is active when view is 'board'", () => {
@@ -586,9 +587,12 @@ describe("MobileNavBar", () => {
expect(screen.getByTestId("mobile-nav-tab-tasks").className).toContain("mobile-nav-tab--active");
});
it("tasks tab is active when view is 'list'", () => {
render(<MobileNavBar {...createDefaultProps()} view="list" />);
expect(screen.getByTestId("mobile-nav-tab-tasks").className).toContain("mobile-nav-tab--active");
it("list tab is active and routes to list when view is 'list'", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="list" />);
expect(screen.getByTestId("mobile-nav-tab-list").className).toContain("mobile-nav-tab--active");
fireEvent.click(screen.getByTestId("mobile-nav-tab-list"));
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("missions tab calls onChangeView with 'missions'", () => {