81 lines
3.6 KiB
TypeScript
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));
|
|
});
|
|
});
|