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:
@@ -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);
|
||||
|
||||
@@ -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 & 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>
|
||||
)}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user