fix(FN-3221): streamline list defaults and gate bulk selection UI

- Reduce default ListView columns to title, status, and column while keeping optional columns user-configurable
- Move task ID into the title cell and add supporting ListView styles for stacked ID/title presentation
- Add explicit Bulk Edit mode toggle so selection checkboxes and bulk toolbar only appear when enabled
- Update ListView tests for new defaults, sorting behavior, conditional bulk-edit controls, and column span expectations

Fusion-Task-Id: FN-3221
This commit is contained in:
Fusion
2026-05-02 14:18:53 -07:00
committed by gsxdsm
parent 117224ba8f
commit f6c8ac7a5a
3 changed files with 148 additions and 83 deletions

View File

@@ -465,6 +465,23 @@
}
.list-title-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--space-xs);
min-width: 0;
max-width: 100%;
}
.list-title-id {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
line-height: 1.2;
}
.list-title-row {
display: inline-flex;
align-items: center;
gap: var(--space-xs);

View File

@@ -30,7 +30,8 @@ type SortField = "id" | "title" | "status" | "column";
type SortDirection = "asc" | "desc";
// Column visibility types
const ALL_LIST_COLUMNS = ["id", "title", "status", "column", "dependencies", "progress"] as const;
const ALL_LIST_COLUMNS = ["title", "status", "column", "dependencies", "progress"] as const;
const DEFAULT_LIST_COLUMNS = ["title", "status", "column"] as const;
type ListColumn = typeof ALL_LIST_COLUMNS[number];
function getNodeStatusLabel(status: NodeInfo["status"]): string {
@@ -63,7 +64,7 @@ function readVisibleColumns(projectId?: string): Set<ListColumn> {
// Invalid localStorage data - fall through to default
}
return new Set(ALL_LIST_COLUMNS);
return new Set(DEFAULT_LIST_COLUMNS);
}
function readHideDoneTasks(projectId?: string): boolean {
@@ -241,7 +242,7 @@ export function ListView({
const isMobile = viewportMode === "mobile";
const { confirm } = useConfirm();
// Column visibility state - initialize from localStorage or default to all columns
// Column visibility state - initialize from localStorage or reduced default columns
const [visibleColumns, setVisibleColumns] = useState<Set<ListColumn>>(() => readVisibleColumns(projectId));
// Hide done tasks state - initialize from localStorage
@@ -278,6 +279,7 @@ export function ListView({
const columnDropdownRef = useRef<HTMLDivElement>(null);
// Selection state - initialize from localStorage
const [bulkEditEnabled, setBulkEditEnabled] = useState(false);
const [selectedTaskIds, setSelectedTaskIds] = useState<Set<string>>(() => readSelectedTaskIds(projectId));
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(() => readSelectedTaskId(projectId));
const [sidebarWidth, setSidebarWidth] = useState<number>(() => readSidebarWidth(projectId));
@@ -366,6 +368,15 @@ export function ListView({
};
}, [isMobile, projectId]);
const toggleBulkEdit = useCallback(() => {
setBulkEditEnabled((prev) => {
if (prev) {
setSelectedTaskIds(new Set());
}
return !prev;
});
}, []);
// Toggle task selection
const toggleTaskSelection = useCallback((taskId: string) => {
setSelectedTaskIds((prev) => {
@@ -427,7 +438,6 @@ export function ListView({
// Column display labels
const COLUMN_LABELS_MAP: Record<ListColumn, string> = {
id: "ID",
title: "Title",
status: "Status",
column: "Column",
@@ -930,7 +940,10 @@ export function ListView({
</button>
)}
</div>
{selectedTaskIds.size > 0 && (
<button className="btn btn-sm" onClick={toggleBulkEdit} aria-pressed={bulkEditEnabled}>
{bulkEditEnabled ? "Done Editing" : "Bulk Edit"}
</button>
{bulkEditEnabled && selectedTaskIds.size > 0 && (
<div className="list-selection-stats">
<span className="selection-count">{selectedTaskIds.size} selected</span>
<button className="btn btn-sm btn-link" onClick={clearSelection}>
@@ -939,7 +952,7 @@ export function ListView({
</div>
)}
{/* Bulk Edit Toolbar */}
{selectedTaskIds.size > 0 && availableModels && availableModels.length > 0 && (
{bulkEditEnabled && selectedTaskIds.size > 0 && availableModels && availableModels.length > 0 && (
<div className="bulk-edit-toolbar">
<span className="bulk-edit-label">Bulk Edit Models &amp; Node:</span>
<div className="bulk-edit-dropdown">
@@ -1114,7 +1127,7 @@ export function ListView({
const hasDependencies = Boolean(task.dependencies && task.dependencies.length > 0);
const taskProgress = getTaskProgress(task);
const hasProgress = taskProgress.hasProgress;
const isSelectionMode = selectedTaskIds.size > 0;
const isSelectionMode = bulkEditEnabled;
return (
<div
@@ -1202,20 +1215,17 @@ export function ListView({
<table className="list-table">
<thead>
<tr>
<th className="list-header-cell list-header-checkbox">
<input
type="checkbox"
checked={isSelectAll}
ref={(el) => {
if (el) el.indeterminate = isSelectIndeterminate;
}}
onChange={toggleSelectAll}
aria-label="Select all visible tasks"
/>
</th>
{visibleColumns.has("id") && (
<th className="list-header-cell" onClick={() => handleSort("id")}>
ID {getSortIcon("id")}
{bulkEditEnabled && (
<th className="list-header-cell list-header-checkbox">
<input
type="checkbox"
checked={isSelectAll}
ref={(el) => {
if (el) el.indeterminate = isSelectIndeterminate;
}}
onChange={toggleSelectAll}
aria-label="Select all visible tasks"
/>
</th>
)}
{visibleColumns.has("title") && (
@@ -1265,7 +1275,7 @@ export function ListView({
onClick={() => toggleSection(column)}
aria-expanded={!isCollapsed}
>
<th colSpan={visibleColumns.size + 1} className="list-section-cell">
<th colSpan={visibleColumns.size + (bulkEditEnabled ? 1 : 0)} className="list-section-cell">
<ChevronRight
size={14}
className={`list-section-chevron${!isCollapsed ? " list-section-chevron--expanded" : ""}`}
@@ -1281,7 +1291,7 @@ export function ListView({
<>
{isEmpty ? (
<tr className="list-section-empty">
<td colSpan={visibleColumns.size + 1} className="list-empty-cell">
<td colSpan={visibleColumns.size + (bulkEditEnabled ? 1 : 0)} className="list-empty-cell">
No tasks
</td>
</tr>
@@ -1312,36 +1322,38 @@ export function ListView({
onDragEnd={handleDragEnd}
data-id={task.id}
>
<td className="list-cell list-cell-checkbox">
<input
type="checkbox"
checked={selectedTaskIds.has(task.id)}
onChange={(e) => {
e.stopPropagation();
toggleTaskSelection(task.id);
}}
onClick={(e) => e.stopPropagation()}
disabled={task.column === "archived"}
aria-label={`Select ${task.id}`}
/>
</td>
{visibleColumns.has("id") && (
<td className="list-cell list-cell-id">{task.id}</td>
{bulkEditEnabled && (
<td className="list-cell list-cell-checkbox">
<input
type="checkbox"
checked={selectedTaskIds.has(task.id)}
onChange={(e) => {
e.stopPropagation();
toggleTaskSelection(task.id);
}}
onClick={(e) => e.stopPropagation()}
disabled={task.column === "archived"}
aria-label={`Select ${task.id}`}
/>
</td>
)}
{visibleColumns.has("title") && (
<td className="list-cell list-cell-title">
<div className="list-title-content">
{task.executionMode === "fast" && (
<span
className="list-execution-mode-badge list-execution-mode-badge--fast"
title="Fast mode"
aria-label="Fast mode"
>
<Zap aria-hidden="true" />
<span className="visually-hidden">Fast mode</span>
</span>
)}
<span className="list-title-text">{task.title || task.description}</span>
<span className="list-title-id">{task.id}</span>
<div className="list-title-row">
{task.executionMode === "fast" && (
<span
className="list-execution-mode-badge list-execution-mode-badge--fast"
title="Fast mode"
aria-label="Fast mode"
>
<Zap aria-hidden="true" />
<span className="visually-hidden">Fast mode</span>
</span>
)}
<span className="list-title-text">{task.title || task.description}</span>
</div>
</div>
</td>
)}

View File

@@ -64,6 +64,17 @@ const renderListView = (props: Partial<React.ComponentProps<typeof ListView>> =
return render(<ListView {...defaultProps} {...props} />);
};
const enterBulkEditMode = () => {
fireEvent.click(screen.getByRole("button", { name: "Bulk Edit" }));
};
const showAllColumnsByDefault = () => {
localStorage.setItem(
scopedStorageKey("kb-dashboard-list-columns"),
JSON.stringify(["title", "status", "column", "dependencies", "progress"]),
);
};
function ensureMatchMedia() {
if (!window.matchMedia) {
Object.defineProperty(window, "matchMedia", {
@@ -270,6 +281,7 @@ describe("ListView", () => {
renderListView({ tasks });
const row = screen.getByText("FN-001").closest("tr")!;
enterBulkEditMode();
const checkbox = within(row).getByRole("checkbox", { name: "Select FN-001" });
fireEvent.click(checkbox);
@@ -380,8 +392,8 @@ describe("ListView", () => {
renderListView({ tasks });
// First click - ascending
const idHeader = screen.getByText("ID");
fireEvent.click(idHeader);
const titleHeader = screen.getByText("Title");
fireEvent.click(titleHeader);
// Get all data rows (excluding section headers by using data-id attribute)
const rows = screen.getAllByRole("row").filter(r => r.getAttribute("data-id"));
@@ -390,7 +402,7 @@ describe("ListView", () => {
expect(rows[2].textContent).toContain("FN-003");
// Second click - descending
fireEvent.click(idHeader);
fireEvent.click(titleHeader);
const rowsDesc = screen.getAllByRole("row").filter(r => r.getAttribute("data-id"));
expect(rowsDesc[0].textContent).toContain("FN-003");
@@ -592,6 +604,7 @@ describe("ListView", () => {
}),
];
showAllColumnsByDefault();
renderListView({ tasks });
expect(screen.getByText("3/5")).toBeDefined();
@@ -615,6 +628,7 @@ describe("ListView", () => {
}),
];
showAllColumnsByDefault();
renderListView({ tasks });
const row = screen.getByText("FN-001").closest("tr")!;
@@ -625,6 +639,7 @@ describe("ListView", () => {
it("shows - for tasks with no steps", () => {
const tasks = [createMockTask({ id: "FN-001", steps: [] })];
showAllColumnsByDefault();
renderListView({ tasks });
// Find the task row and check its progress cell
@@ -643,6 +658,7 @@ describe("ListView", () => {
}),
];
showAllColumnsByDefault();
renderListView({ tasks });
const row = screen.getByText("FN-002").closest("tr")!;
@@ -658,6 +674,7 @@ describe("ListView", () => {
}),
];
showAllColumnsByDefault();
renderListView({ tasks });
expect(screen.getByText("2")).toBeDefined();
@@ -666,12 +683,12 @@ describe("ListView", () => {
it("shows - for tasks with no dependencies", () => {
const tasks = [createMockTask({ id: "FN-001", dependencies: [] })];
showAllColumnsByDefault();
renderListView({ tasks });
const depCells = screen.getAllByRole("cell");
// Find the cell that should contain deps (6th column, index 5 - after checkbox column)
const depCell = depCells[5];
expect(depCell.textContent).toBe("-");
const row = screen.getByText("FN-001").closest("tr")!;
const depCell = row.querySelector(".list-cell-deps");
expect(depCell?.textContent).toBe("-");
});
it("displays correct task count in stats", () => {
@@ -881,8 +898,8 @@ describe("ListView", () => {
// The full 100-character description should be visible
const titleCell = screen.getByText(longDescription).closest("td")!;
expect(titleCell.textContent).toBe(longDescription);
expect(titleCell.textContent?.length).toBe(100);
expect(titleCell.textContent).toContain(longDescription);
expect(titleCell.textContent?.length).toBeGreaterThanOrEqual(100);
});
// Grouped view tests
@@ -941,23 +958,24 @@ describe("ListView", () => {
];
renderListView({ tasks });
enterBulkEditMode();
// Find section header rows
const sectionHeaders = screen.getAllByRole("row").filter(r => r.className.includes("list-section-header"));
// Verify each section header has colSpan that includes the checkbox column
// Default visible columns: id, title, status, column, dependencies, progress (6 columns)
// Plus checkbox column = 7 total
// Default visible columns: title, status, column (3 columns)
// Plus checkbox column = 4 total
for (const header of sectionHeaders) {
const th = header.querySelector("th.list-section-cell");
expect(th).not.toBeNull();
expect(th!.getAttribute("colSpan")).toBe("7"); // visibleColumns.size (6) + 1 for checkbox
expect(th!.getAttribute("colSpan")).toBe("4"); // visibleColumns.size (3) + 1 for checkbox
}
// Also verify empty section cells span full width
const emptyCells = screen.getAllByRole("cell").filter(c => c.className.includes("list-empty-cell"));
for (const cell of emptyCells) {
expect(cell.getAttribute("colSpan")).toBe("7");
expect(cell.getAttribute("colSpan")).toBe("4");
}
});
@@ -1196,7 +1214,7 @@ describe("ListView Column Visibility", () => {
fireEvent.click(columnsButton);
// Dropdown should be visible with checkboxes for each column
expect(screen.getByText("ID")).toBeDefined();
expect(screen.queryByText("ID")).toBeNull();
expect(screen.getByText("Title")).toBeDefined();
expect(screen.getByText("Status")).toBeDefined();
expect(screen.getByText("Column")).toBeDefined();
@@ -1275,16 +1293,14 @@ describe("ListView Column Visibility", () => {
});
it("initializes column visibility from localStorage", () => {
// Set up localStorage with only ID and Status visible
localStorage.setItem(scopedStorageKey("kb-dashboard-list-columns"), JSON.stringify(["id", "status"]));
// Set up localStorage with only Status visible
localStorage.setItem(scopedStorageKey("kb-dashboard-list-columns"), JSON.stringify(["status"]));
const tasks = [createMockTask({ id: "FN-001", title: "Test Task", status: "pending" })];
renderListView({ tasks });
// ID should be visible
expect(screen.getByText("FN-001")).toBeDefined();
// Title should NOT be visible (hidden by localStorage)
expect(screen.queryByText("FN-001")).toBeNull();
const table = document.querySelector(".list-table");
expect(table?.textContent).not.toContain("Test Task");
});
@@ -1315,9 +1331,9 @@ describe("ListView Column Visibility", () => {
it("sorting still works when some columns are hidden", () => {
const tasks = [
createMockTask({ id: "FN-003", column: "triage" }),
createMockTask({ id: "FN-001", column: "triage" }),
createMockTask({ id: "FN-002", column: "triage" }),
createMockTask({ id: "FN-003", title: "Charlie", column: "triage" }),
createMockTask({ id: "FN-001", title: "Alpha", column: "triage" }),
createMockTask({ id: "FN-002", title: "Bravo", column: "triage" }),
];
renderListView({ tasks });
@@ -1325,17 +1341,15 @@ describe("ListView Column Visibility", () => {
const columnsButton = screen.getByRole("button", { name: /columns/i });
fireEvent.click(columnsButton);
const checkboxes = screen.getAllByRole("checkbox");
const titleCheckbox = checkboxes.find(
cb => cb.parentElement?.textContent?.includes("Title")
const columnCheckbox = checkboxes.find(
cb => cb.parentElement?.textContent?.includes("Column")
);
expect(titleCheckbox).toBeDefined();
fireEvent.click(titleCheckbox!);
expect(columnCheckbox).toBeDefined();
fireEvent.click(columnCheckbox!);
// Find and click ID header to sort (use getAllByText and find the header cell)
const idHeaders = screen.getAllByText("ID");
const idHeader = idHeaders.find(el => el.tagName === "TH" || el.closest("th"));
expect(idHeader).toBeDefined();
fireEvent.click(idHeader!);
// Find and click Title header to sort
const titleHeader = screen.getByRole("columnheader", { name: /title/i });
fireEvent.click(titleHeader);
// Get sorted rows and verify sorting still works
const rows = screen.getAllByRole("row").filter(r => r.getAttribute("data-id"));
@@ -1344,19 +1358,22 @@ describe("ListView Column Visibility", () => {
expect(rows[2].textContent).toContain("FN-003");
});
it("all columns visible by default when no localStorage", () => {
it("shows reduced default columns when no localStorage", () => {
const tasks = [
createMockTask({ id: "FN-001", title: "Test Task", status: "pending", column: "triage" }),
];
renderListView({ tasks });
// All columns should be visible by default
// Reduced default columns should be visible
expect(screen.getByText("FN-001")).toBeDefined();
expect(screen.getByText("Test Task")).toBeDefined();
expect(screen.getByText("pending")).toBeDefined();
// Check for column badge specifically using the class
const columnBadge = document.querySelector(".list-column-badge");
expect(columnBadge?.textContent).toContain("Planning");
// Optional columns should be hidden by default
expect(document.querySelector(".list-cell-deps")).toBeNull();
expect(document.querySelector(".list-cell-progress")).toBeNull();
});
});
@@ -2104,6 +2121,7 @@ describe("ListView - Bulk Selection", () => {
it("shows selection checkbox in header", () => {
const tasks = [createMockTask({ id: "FN-001" })];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const headerCheckbox = screen.getByLabelText("Select all visible tasks");
expect(headerCheckbox).toBeDefined();
@@ -2115,6 +2133,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-002" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkboxes = screen.getAllByLabelText(/Select FN-/);
expect(checkboxes).toHaveLength(2);
@@ -2125,6 +2144,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-001", column: "archived" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
expect(checkbox).toBeDisabled();
@@ -2136,6 +2156,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-002" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
fireEvent.click(checkbox);
@@ -2148,6 +2169,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-001" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
fireEvent.click(checkbox);
@@ -2165,6 +2187,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-002" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const selectAllCheckbox = screen.getByLabelText("Select all visible tasks");
fireEvent.click(selectAllCheckbox);
@@ -2193,6 +2216,7 @@ describe("ListView - Bulk Selection", () => {
onToggleModelFavorite={onToggleModelFavorite}
/>
);
enterBulkEditMode();
// Select a task to show bulk edit toolbar with dropdowns
const checkbox = screen.getByLabelText("Select FN-001");
@@ -2216,6 +2240,7 @@ describe("ListView - Bulk Selection", () => {
availableModels={availableModels}
/>
);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
fireEvent.click(checkbox);
@@ -2238,6 +2263,7 @@ describe("ListView - Bulk Selection", () => {
availableModels={availableModels}
/>
);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
fireEvent.click(checkbox);
@@ -2249,6 +2275,7 @@ describe("ListView - Bulk Selection", () => {
it("persists selection to localStorage", () => {
const tasks = [createMockTask({ id: "FN-001" })];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkbox = screen.getByLabelText("Select FN-001");
fireEvent.click(checkbox);
@@ -2262,6 +2289,7 @@ describe("ListView - Bulk Selection", () => {
createMockTask({ id: "FN-002" }),
];
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} />);
enterBulkEditMode();
const checkboxes = screen.getAllByLabelText(/Select FN-/);
// Select only first task
@@ -2301,6 +2329,7 @@ describe("ListView - Bulk Selection", () => {
availableModels={availableModels}
/>
);
enterBulkEditMode();
await user.click(screen.getByLabelText("Select FN-001"));
@@ -2370,6 +2399,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(fetchNodes).mockResolvedValue([{ id: "node-1", name: "Node One", status: "online" } as never]);
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
fireEvent.click(screen.getByLabelText("Select FN-001"));
expect(await screen.findByLabelText("Node Override")).toBeInTheDocument();
@@ -2381,6 +2411,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(fetchNodes).mockResolvedValue([{ id: "node-2", name: "Node Two", status: "offline" } as never]);
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
fireEvent.click(screen.getByLabelText("Select FN-001"));
expect(await screen.findByRole("option", { name: "○ Node Two (Offline)" })).toBeInTheDocument();
@@ -2392,6 +2423,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(fetchNodes).mockResolvedValue([{ id: "node-abc", name: "Node ABC", status: "online" } as never]);
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
await user.click(screen.getByLabelText("Select FN-001"));
const nodeSelect = await screen.findByLabelText("Node Override");
@@ -2408,6 +2440,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(batchUpdateTaskModels).mockResolvedValue({ updated: tasks, count: 1 });
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
await user.click(screen.getByLabelText("Select FN-001"));
const nodeSelect = await screen.findByLabelText("Node Override");
@@ -2427,6 +2460,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(batchUpdateTaskModels).mockResolvedValue({ updated: tasks, count: 1 });
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
await user.click(screen.getByLabelText("Select FN-001"));
await user.selectOptions(await screen.findByLabelText("Node Override"), "");
await user.click(screen.getByRole("button", { name: "Apply" }));
@@ -2442,6 +2476,7 @@ describe("ListView - Bulk Selection", () => {
vi.mocked(fetchNodes).mockResolvedValue([{ id: "node-abc", name: "Node ABC", status: "online" } as never]);
render(<ListView tasks={tasks} onMoveTask={vi.fn()} onOpenDetail={vi.fn()} addToast={mockAddToast} projectId={TEST_PROJECT_ID} availableModels={availableModels} />);
enterBulkEditMode();
fireEvent.click(screen.getByLabelText("Select FN-001"));
expect(await screen.findByRole("button", { name: "Apply" })).toBeDisabled();
@@ -2667,6 +2702,7 @@ describe("ListView - Bulk Selection", () => {
],
});
enterBulkEditMode();
fireEvent.click(screen.getByLabelText("Select FN-002"));
expect(screen.getByText("2 selected")).toBeInTheDocument();