Files
kandev/apps/web/e2e/tests/task/sidebar-subtask-state-sort.spec.ts
T

81 lines
3.6 KiB
TypeScript

/**
* E2E test for effective-state sort bubbling in the left sidebar.
*
* When the sidebar is sorted by state (the default), a parent task should
* adopt the highest-priority state bucket among itself and its direct
* subtasks. So a backlog parent with an in-progress subtask must bubble up
* above an unrelated backlog peer.
*
* This is the end-to-end counterpart to the unit coverage in
* `lib/sidebar/apply-view.test.ts`: it proves the live store threads the
* active `state` sort through `applyView` and the sidebar renders the
* bubbled order in the DOM.
*
* Regression value: without the fix both parent and peer share the backlog
* bucket, where the tiebreak is `createdAt` desc — so the newer peer (created
* last) would render ABOVE the older parent. The assertion below only holds
* once the subtask bubbles the parent into the in_progress bucket.
*/
import { test, expect } from "../../fixtures/test-base";
import { SessionPage } from "../../pages/session-page";
test.describe("Sidebar subtasks — effective state sort", () => {
test("a backlog parent with an in-progress subtask bubbles above a backlog peer", async ({
testPage,
apiClient,
seedData,
}) => {
// Parent task — stays in a backlog-bucket state.
const parent = await apiClient.createTask(seedData.workspaceId, "Bubble Parent Task", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
});
// Child subtask whose task state we flip to IN_PROGRESS. `classifyTask`
// buckets a task with no session by its persisted state, so this lands in
// the in_progress bucket without needing to drive an agent.
const child = await apiClient.createTask(seedData.workspaceId, "Bubble Child Task", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
parent_id: parent.id,
repository_ids: [seedData.repositoryId],
});
await apiClient.updateTaskState(child.id, "IN_PROGRESS");
// A second root task created LAST — stays in the backlog bucket. Without
// bubbling it would sort above the older parent (backlog tiebreak is
// newest-createdAt-first).
const peer = await apiClient.createTask(seedData.workspaceId, "Bubble Backlog Peer", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
});
await testPage.goto(`/t/${parent.id}`);
const session = new SessionPage(testPage);
await session.waitForLoad();
await expect(session.sidebar).toBeVisible({ timeout: 10_000 });
// Wait for both root rows to render before reading order.
const parentBlock = session.sidebar.locator(
`[data-testid='sortable-task-block'][data-task-id='${parent.id}']`,
);
const peerBlock = session.sidebar.locator(
`[data-testid='sortable-task-block'][data-task-id='${peer.id}']`,
);
await expect(parentBlock).toBeVisible({ timeout: 10_000 });
await expect(peerBlock).toBeVisible({ timeout: 10_000 });
// Read root-task order from the DOM and assert the parent precedes the peer.
// Comparing indices (not absolute positions) keeps this robust against any
// other tasks present in the sidebar.
const rootBlocks = session.sidebar.locator("[data-testid='sortable-task-block']");
const orderedIds = await rootBlocks.evaluateAll((els) =>
els.map((el) => el.getAttribute("data-task-id")),
);
expect(orderedIds.indexOf(parent.id)).toBeGreaterThanOrEqual(0);
expect(orderedIds.indexOf(parent.id)).toBeLessThan(orderedIds.indexOf(peer.id));
});
});