feat(FN-3231): preserve merge-active state on verification bounce and board
This merge lands FN-3231 across two steps: it preserves a merge-active fix when verification bounces occur (step 1) and ensures the fix is retained during board routing transitions (step 2). Changes span the dashboard Board routing logic and the engine executor, with corresponding test coverage adde Fusion-Task-Id: FN-3231
This commit is contained in:
@@ -403,6 +403,28 @@ describe("Board", () => {
|
||||
expect(inReviewTasks.map((task) => task.id)).toEqual(["FN-020", "FN-021"]);
|
||||
});
|
||||
|
||||
it("pins merging-fix tasks to top of in-review even when newer non-merging tasks exist", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({
|
||||
id: "FN-060",
|
||||
column: "in-review",
|
||||
status: "merging-fix",
|
||||
columnMovedAt: "2024-01-01T10:00:00.000Z",
|
||||
}),
|
||||
createTask({
|
||||
id: "FN-061",
|
||||
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-060", "FN-061"]);
|
||||
});
|
||||
|
||||
it("sorts multiple merging tasks by priority then task ID within the pinned group", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({
|
||||
|
||||
@@ -620,12 +620,15 @@ describe("ListView", () => {
|
||||
expect(row?.className).toContain("paused");
|
||||
});
|
||||
|
||||
it("renders agent-active tasks with glow styling", () => {
|
||||
it.each([
|
||||
{ status: "executing", column: "in-progress" as const, label: "executing" },
|
||||
{ status: "merging-fix", column: "in-review" as const, label: "Merging fixes…" },
|
||||
])("renders agent-active tasks with glow styling for $status", ({ status, column, label }) => {
|
||||
const tasks = [
|
||||
createMockTask({
|
||||
id: "FN-001",
|
||||
status: "executing",
|
||||
column: "in-progress",
|
||||
status,
|
||||
column,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -633,6 +636,7 @@ describe("ListView", () => {
|
||||
|
||||
const row = screen.getByText("FN-001").closest("tr");
|
||||
expect(row?.className).toContain("agent-active");
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render agent-active when globalPaused is true", () => {
|
||||
@@ -2845,15 +2849,18 @@ describe("ListView - Bulk Selection", () => {
|
||||
expect((screen.getByLabelText("Select FN-002") as HTMLInputElement).checked).toBe(true);
|
||||
});
|
||||
|
||||
it("applies agent-active class to mobile cards when task is in-progress and not paused/failed", () => {
|
||||
it.each([
|
||||
{ status: "executing", column: "in-progress" as const },
|
||||
{ status: "merging-fix", column: "in-review" as const },
|
||||
])("applies agent-active class to mobile cards for active states (%s)", ({ status, column }) => {
|
||||
mockMobileViewport();
|
||||
|
||||
const { container } = renderListView({
|
||||
tasks: [
|
||||
createMockTask({
|
||||
id: "FN-001",
|
||||
status: "executing",
|
||||
column: "in-progress",
|
||||
status,
|
||||
column,
|
||||
}),
|
||||
],
|
||||
globalPaused: false,
|
||||
|
||||
@@ -224,8 +224,8 @@ describe("RoutingTab", () => {
|
||||
expect(screen.getByText("Node override cannot be changed while the task is active.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables node selector for active task statuses", async () => {
|
||||
render(<RoutingTab task={makeTask({ column: "todo", status: "executing" })} settings={makeSettings()} addToast={addToast} />);
|
||||
it.each(["executing", "merging-fix"] as const)("disables node selector for active task status %s", async (status) => {
|
||||
render(<RoutingTab task={makeTask({ column: "todo", status })} settings={makeSettings()} addToast={addToast} />);
|
||||
|
||||
const selector = await screen.findByLabelText("Select execution node");
|
||||
expect(selector).toBeDisabled();
|
||||
|
||||
@@ -70,6 +70,20 @@ describe("TaskCard", () => {
|
||||
expect(screen.getByText("executing")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders merge-remediation status as merge-active for in-review tasks", () => {
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
task={makeTask({ column: "in-review", status: "merging-fix" })}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Merging fixes…")).toBeDefined();
|
||||
const badge = container.querySelector(".card-status-badge");
|
||||
expect(badge?.className).toContain("pulsing");
|
||||
});
|
||||
|
||||
it("renders the status badge after the card ID in DOM order", () => {
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
@@ -856,7 +870,7 @@ describe("TaskCard", () => {
|
||||
expect(container.querySelector(".card-time-indicator")?.getAttribute("title")).toBe("Execution time 35m");
|
||||
});
|
||||
|
||||
it("shows live merge elapsed in timer chip while task.status is merging", () => {
|
||||
it.each(["merging", "merging-fix"] as const)("shows live merge elapsed in timer chip while task.status is %s", (status) => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-04-25T13:45:00.000Z"));
|
||||
|
||||
@@ -865,7 +879,7 @@ describe("TaskCard", () => {
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "in-review",
|
||||
status: "merging",
|
||||
status,
|
||||
executionStartedAt: "2026-04-25T13:00:00.000Z",
|
||||
updatedAt: "2026-04-25T13:44:30.000Z",
|
||||
workflowStepResults: [
|
||||
|
||||
Reference in New Issue
Block a user