feat(FN-5210): wrap TaskCard footer chip cluster for mobile
Wraps the TaskCard footer chip cluster with CSS changes to prevent layout overflow, backed by a new regression test file and updated board-mobile tests registered in the curated dashboard test gate. Fusion-Task-Id: FN-5210
This commit is contained in:
committed by
gsxdsm
parent
c047b25654
commit
35112e6070
@@ -656,9 +656,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
row-gap: var(--space-xs);
|
||||
margin-top: var(--space-sm);
|
||||
min-width: 0;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-session-files {
|
||||
@@ -708,9 +709,11 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
row-gap: var(--space-xs);
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
min-width: 0;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-source-provenance .provider-icon {
|
||||
@@ -1318,6 +1321,7 @@
|
||||
.card-footer-row {
|
||||
margin-top: var(--space-xs);
|
||||
gap: var(--space-xs);
|
||||
row-gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.card-time-indicator,
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { TaskCard } from "../TaskCard";
|
||||
import { loadAllAppCss } from "../../test/cssFixture";
|
||||
|
||||
vi.mock("lucide-react", () => ({
|
||||
Link: () => <svg />,
|
||||
GitBranch: () => <svg />,
|
||||
Clock: () => <svg />,
|
||||
Pencil: () => <svg />,
|
||||
Layers: () => <svg />,
|
||||
ChevronDown: () => <svg />,
|
||||
Folder: () => <svg />,
|
||||
GitPullRequest: () => <svg />,
|
||||
CircleDot: () => <svg />,
|
||||
CheckCircle2: () => <svg />,
|
||||
XCircle: () => <svg />,
|
||||
Target: () => <svg />,
|
||||
Bot: () => <svg />,
|
||||
Trash2: () => <svg />,
|
||||
RotateCw: () => <svg />,
|
||||
Zap: () => <svg />,
|
||||
}));
|
||||
|
||||
vi.mock("../ProviderIcon", () => ({
|
||||
ProviderIcon: () => <span data-testid="provider-icon" />,
|
||||
}));
|
||||
|
||||
vi.mock("../PluginSlot", () => ({
|
||||
PluginSlot: () => null,
|
||||
}));
|
||||
|
||||
vi.mock("../../hooks/useTaskDiffStats", () => ({
|
||||
useTaskDiffStats: () => ({ stats: null, loading: false }),
|
||||
}));
|
||||
|
||||
vi.mock("../../hooks/useBadgeWebSocket", () => ({
|
||||
useBadgeWebSocket: () => ({
|
||||
badgeUpdates: new Map(),
|
||||
isConnected: true,
|
||||
subscribeToBadge: vi.fn(),
|
||||
unsubscribeFromBadge: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../hooks/useBatchBadgeFetch", () => ({
|
||||
getFreshBatchData: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
fetchTaskDetail: vi.fn(),
|
||||
uploadAttachment: vi.fn(),
|
||||
fetchMission: vi.fn(),
|
||||
fetchAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../hooks/useConfirm", () => ({
|
||||
useConfirm: () => ({ confirm: vi.fn(async () => true) }),
|
||||
}));
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
function makeTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "FN-5210",
|
||||
title: "Wrap footer chips when the card gets narrow",
|
||||
description: "",
|
||||
column: "in-progress",
|
||||
status: "executing" as Task["status"],
|
||||
steps: [],
|
||||
dependencies: [],
|
||||
sourceType: "dashboard_ui",
|
||||
githubTracking: {
|
||||
enabled: true,
|
||||
issue: {
|
||||
owner: "runfusion",
|
||||
repo: "fusion",
|
||||
number: 5210,
|
||||
url: "https://github.com/runfusion/fusion/issues/5210",
|
||||
createdAt: "2026-05-19T12:00:00.000Z",
|
||||
},
|
||||
},
|
||||
retrySummary: { total: 3 } as Task["retrySummary"],
|
||||
executionStartedAt: "2026-05-19T12:00:00.000Z",
|
||||
updatedAt: "2026-05-19T12:05:00.000Z",
|
||||
...overrides,
|
||||
} as Task;
|
||||
}
|
||||
|
||||
describe("TaskCard footer wrapping (FN-5210)", () => {
|
||||
let cleanupCss: (() => void) | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-19T12:10:00.000Z"));
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = await Promise.resolve(loadAllAppCss());
|
||||
document.head.appendChild(style);
|
||||
cleanupCss = () => style.remove();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanupCss?.();
|
||||
cleanupCss = undefined;
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("FN-5210 wraps the footer row and right chip cluster with a non-zero row gap", () => {
|
||||
const { container } = render(
|
||||
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} onOpenDetailWithTab={noop} />,
|
||||
);
|
||||
|
||||
const footerRow = container.querySelector(".card-footer-row") as HTMLElement;
|
||||
const rightCluster = container.querySelector(".card-footer-row-right") as HTMLElement;
|
||||
const retryChip = container.querySelector(".card-retry-badge") as HTMLElement;
|
||||
const githubChip = container.querySelector(".card-github-tracking-chip") as HTMLElement;
|
||||
const timeChip = container.querySelector(".card-time-indicator") as HTMLElement;
|
||||
|
||||
expect(footerRow).toBeTruthy();
|
||||
expect(rightCluster).toBeTruthy();
|
||||
expect(retryChip).toBeTruthy();
|
||||
expect(githubChip).toBeTruthy();
|
||||
expect(timeChip).toBeTruthy();
|
||||
|
||||
const footerStyles = getComputedStyle(footerRow);
|
||||
expect(footerStyles.flexWrap).toBe("wrap");
|
||||
expect(footerStyles.rowGap).toMatch(/^(var\(--space-xs\)|(?!0(?:px)?$)\d+(?:\.\d+)?px)$/);
|
||||
|
||||
const rightClusterStyles = getComputedStyle(rightCluster);
|
||||
expect(rightClusterStyles.flexWrap).toBe("wrap");
|
||||
expect(rightClusterStyles.rowGap).toMatch(/^(var\(--space-xs\)|(?!0(?:px)?$)\d+(?:\.\d+)?px)$/);
|
||||
expect(rightClusterStyles.justifyContent).toBe("flex-end");
|
||||
});
|
||||
|
||||
it.each([
|
||||
".card-retry-badge",
|
||||
".card-github-tracking-chip",
|
||||
".card-time-indicator",
|
||||
])("FN-5210 keeps %s internally nowrap so wrapping happens at chip boundaries", (selector) => {
|
||||
const { container } = render(
|
||||
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} onOpenDetailWithTab={noop} />,
|
||||
);
|
||||
|
||||
const chip = container.querySelector(selector) as HTMLElement;
|
||||
expect(chip, `${selector} should render for the FN-5210 fixture`).toBeTruthy();
|
||||
|
||||
const styles = getComputedStyle(chip);
|
||||
expect(styles.whiteSpace).toBe("nowrap");
|
||||
expect(styles.flexShrink).toBe("0");
|
||||
});
|
||||
});
|
||||
@@ -308,9 +308,10 @@ describe("TaskCard mobile", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps TaskCard footer row on one line", () => {
|
||||
it("wraps TaskCard footer chips instead of overflowing", () => {
|
||||
const css = loadAllAppCss();
|
||||
expectRuleToContain(css, ".card-footer-row", "flex-wrap: nowrap;");
|
||||
expectRuleToContain(css, ".card-footer-row", "flex-wrap: wrap;");
|
||||
expectRuleToContain(css, ".card-footer-row", "row-gap: var(--space-xs);");
|
||||
});
|
||||
|
||||
it("keeps TaskCard footer-chip cluster anchored by first chip rules", () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ const qualityAppTests = [
|
||||
"app/api/**/*.test.ts",
|
||||
// Representative workflow/component coverage. Exhaustive modal/view suites
|
||||
// stay available in the full `dashboard-app` project.
|
||||
"app/components/__tests__/{ActiveAgentsPanel,ActivityLogModal,AgentMentionPopup,AgentMetricsBar,AgentOnboardingModal,AgentReflectionsTab,AgentTokenStatsPanel,App,AuthTokenRecoveryDialog,Board,board-mobile-view-switch,ChatView,ChatView.autosize,ChatView.chat-input-autosize,ChatView.default-model-icon,ChatView.draft,ChatView.rooms,ChatView.swipe-back,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DevServerView.mobile,DirectoryPicker,DuplicateWarningModal,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,GitHubBadge,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MessageComposer.autosize,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,PlanningModeModal.autosize,PrChecksList,PrCreateModal,PrCreateModal.layout,ProjectCard,ProjectSelector,ProviderIcon,PrPanel,PrPanel.merge,PrPanel.reviews,QuickChatFAB,ReliabilityView,ResearchView,SecretsView,SecretsView.mobile,SettingsModal,SettingsModal.worktrunk,StashRecoveryView,TaskCard,TaskCard.badge-height,TaskCard.badge-wrap,TaskChangesTab,TaskComments,TaskDetailModal,TaskDetailModal.create-pr-e2e,TaskDetailModal.create-pr-integration,TaskDetailModal.github-tracking-header,TaskDetailModal.github-tracking-stale,TaskDetailModal.rebind-banner,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,TrackingRepoSelect,WorkflowResultsTab,WorktrunkInstallApprovalDetails}.test.tsx",
|
||||
"app/components/__tests__/{ActiveAgentsPanel,ActivityLogModal,AgentMentionPopup,AgentMetricsBar,AgentOnboardingModal,AgentReflectionsTab,AgentTokenStatsPanel,App,AuthTokenRecoveryDialog,Board,board-mobile,board-mobile-view-switch,ChatView,ChatView.autosize,ChatView.chat-input-autosize,ChatView.default-model-icon,ChatView.draft,ChatView.rooms,ChatView.swipe-back,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DevServerView.mobile,DirectoryPicker,DuplicateWarningModal,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,GitHubBadge,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MessageComposer.autosize,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,PlanningModeModal.autosize,PrChecksList,PrCreateModal,PrCreateModal.layout,ProjectCard,ProjectSelector,ProviderIcon,PrPanel,PrPanel.merge,PrPanel.reviews,QuickChatFAB,ReliabilityView,ResearchView,SecretsView,SecretsView.mobile,SettingsModal,SettingsModal.worktrunk,StashRecoveryView,TaskCard,TaskCard.badge-height,TaskCard.badge-wrap,TaskCard.footer-wrap,TaskChangesTab,TaskComments,TaskDetailModal,TaskDetailModal.create-pr-e2e,TaskDetailModal.create-pr-integration,TaskDetailModal.github-tracking-header,TaskDetailModal.github-tracking-stale,TaskDetailModal.rebind-banner,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,TrackingRepoSelect,WorkflowResultsTab,WorktrunkInstallApprovalDetails}.test.tsx",
|
||||
// Hooks and utilities are fast, user-visible state/formatting behavior.
|
||||
"app/context/**/*.test.tsx",
|
||||
"app/hooks/__tests__/{useAgents,useAgentLogs,useAppSettings,useAuthOnboarding,useConfirm,useCurrentProject,useNodes,useNodeSettingsSync,useProjects,useQuickChat,useTasks,useTerminalSessions,useTheme,useToast,useUsageData,useViewState}.test.{ts,tsx}",
|
||||
|
||||
Reference in New Issue
Block a user