feat(FN-2958): merge fusion/fn-2958

- test(FN-2958): complete Step 2 — cover merging pinning sort behavior

Fusion-Task-Id: FN-2958
This commit is contained in:
Fusion
2026-04-29 09:20:01 -07:00
committed by gsxdsm
parent 91f9f208ad
commit 2c160a7f78
2 changed files with 130 additions and 2 deletions

View File

@@ -53,8 +53,16 @@ interface BoardProps {
lastFetchTimeMs?: number;
}
function sortTasksForColumn(tasks: Task[]): Task[] {
function sortTasksForColumn(tasks: Task[], column: ColumnType): Task[] {
return [...tasks].sort((a, b) => {
if (column === "in-review") {
const aIsMerging = a.status === "merging" || a.status === "merging-pr";
const bIsMerging = b.status === "merging" || b.status === "merging-pr";
if (aIsMerging !== bIsMerging) {
return aIsMerging ? -1 : 1;
}
}
if (a.columnMovedAt && b.columnMovedAt) {
return b.columnMovedAt.localeCompare(a.columnMovedAt);
}
@@ -124,7 +132,7 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask
const stableGrouped = {} as Record<ColumnType, Task[]>;
for (const column of COLUMNS) {
const sortedTasks = sortTasksForColumn(nextGrouped[column]);
const sortedTasks = sortTasksForColumn(nextGrouped[column], column);
stableGrouped[column] = areTaskArraysEqual(previousGrouped[column], sortedTasks)
? previousGrouped[column]
: sortedTasks;

View File

@@ -331,6 +331,126 @@ describe("Board", () => {
expect(todoTasks[2].id).toBe("FN-003");
});
describe("sortTasksForColumn merging pinning", () => {
it("pins merging tasks to top of in-review even when newer non-merging tasks exist", () => {
const tasks: Task[] = [
createTask({
id: "FN-010",
column: "in-review",
status: "merging",
columnMovedAt: "2024-01-01T10:00:00.000Z",
}),
createTask({
id: "FN-011",
column: "in-review",
status: "review-ready",
columnMovedAt: "2024-01-01T12:00:00.000Z",
}),
];
renderBoard({ tasks });
const inReviewTasks = JSON.parse(screen.getByTestId("column-in-review").getAttribute("data-tasks") || "[]") as Task[];
expect(inReviewTasks.map((task) => task.id)).toEqual(["FN-010", "FN-011"]);
});
it("pins merging-pr tasks to top of in-review even when newer non-merging tasks exist", () => {
const tasks: Task[] = [
createTask({
id: "FN-020",
column: "in-review",
status: "merging-pr",
columnMovedAt: "2024-01-01T10:00:00.000Z",
}),
createTask({
id: "FN-021",
column: "in-review",
status: "review-ready",
columnMovedAt: "2024-01-01T13:00:00.000Z",
}),
];
renderBoard({ tasks });
const inReviewTasks = JSON.parse(screen.getByTestId("column-in-review").getAttribute("data-tasks") || "[]") as Task[];
expect(inReviewTasks.map((task) => task.id)).toEqual(["FN-020", "FN-021"]);
});
it("sorts multiple merging tasks by columnMovedAt descending within the pinned group", () => {
const tasks: Task[] = [
createTask({
id: "FN-030",
column: "in-review",
status: "merging",
columnMovedAt: "2024-01-01T09:00:00.000Z",
}),
createTask({
id: "FN-031",
column: "in-review",
status: "merging-pr",
columnMovedAt: "2024-01-01T11:00:00.000Z",
}),
createTask({
id: "FN-032",
column: "in-review",
status: "review-ready",
columnMovedAt: "2024-01-01T12:00:00.000Z",
}),
];
renderBoard({ tasks });
const inReviewTasks = JSON.parse(screen.getByTestId("column-in-review").getAttribute("data-tasks") || "[]") as Task[];
expect(inReviewTasks.map((task) => task.id)).toEqual(["FN-031", "FN-030", "FN-032"]);
});
it("does not change sort order for non-in-review columns", () => {
const tasks: Task[] = [
createTask({
id: "FN-040",
column: "todo",
status: "merging",
columnMovedAt: "2024-01-01T10:00:00.000Z",
}),
createTask({
id: "FN-041",
column: "todo",
status: "ready",
columnMovedAt: "2024-01-01T12:00:00.000Z",
}),
];
renderBoard({ tasks });
const todoTasks = JSON.parse(screen.getByTestId("column-todo").getAttribute("data-tasks") || "[]") as Task[];
expect(todoTasks.map((task) => task.id)).toEqual(["FN-041", "FN-040"]);
});
it("keeps tasks without status sorting normally in in-review", () => {
const statuslessTask = createTask({
id: "FN-050",
column: "in-review",
columnMovedAt: "2024-01-01T11:00:00.000Z",
});
delete statuslessTask.status;
const tasks: Task[] = [
statuslessTask,
createTask({
id: "FN-051",
column: "in-review",
status: "review-ready",
columnMovedAt: "2024-01-01T12:00:00.000Z",
}),
];
renderBoard({ tasks });
const inReviewTasks = JSON.parse(screen.getByTestId("column-in-review").getAttribute("data-tasks") || "[]") as Task[];
expect(inReviewTasks.map((task) => task.id)).toEqual(["FN-051", "FN-050"]);
});
});
it("renders server-filtered tasks matching across multiple fields simultaneously", () => {
const tasks: Task[] = [
createTask({ id: "SEARCH-123", title: "Searchable title", description: "Normal description", column: "todo" }),