154 lines
6.3 KiB
TypeScript
154 lines
6.3 KiB
TypeScript
import { test, expect } from "../../fixtures/test-base";
|
|
import {
|
|
allTaskActionOutcomes,
|
|
seedActionThreads,
|
|
seedLinkedIssue,
|
|
ThreadActionsPage,
|
|
withTaskActionSettings,
|
|
} from "./threads-task-actions-helpers";
|
|
import { swipeDeckLeft } from "./mobile-threads-swipe-helpers";
|
|
import {
|
|
failedActionOutcomes,
|
|
filteredArchiveOutcome,
|
|
lateArchiveOutcome,
|
|
} from "./threads-task-actions-edge-helpers";
|
|
import { pendingArchiveRecovery, pendingLastArchive } from "./threads-pending-archive-helpers";
|
|
|
|
for (const [name, outcome] of [
|
|
["pending archive recovers without taking focus", pendingArchiveRecovery],
|
|
["pending last archive stays empty through settlement", pendingLastArchive],
|
|
] as const) {
|
|
test(name, async ({ testPage, apiClient, seedData }, testInfo) => {
|
|
test.setTimeout(120_000);
|
|
await testPage.setViewportSize({ width: 360, height: 780 });
|
|
await withTaskActionSettings(apiClient, () =>
|
|
outcome(testPage, apiClient, seedData, true, testInfo),
|
|
);
|
|
});
|
|
}
|
|
|
|
for (const [name, outcome] of [
|
|
["failures and retry", failedActionOutcomes],
|
|
["late archive retains a newer menu", lateArchiveOutcome],
|
|
["filtered opener retains archive identity", filteredArchiveOutcome],
|
|
] as const) {
|
|
test(name, async ({ testPage, apiClient, seedData }) => {
|
|
test.setTimeout(120_000);
|
|
await testPage.setViewportSize({ width: 360, height: 780 });
|
|
await withTaskActionSettings(apiClient, () => outcome(testPage, apiClient, seedData, true));
|
|
});
|
|
}
|
|
|
|
test("completes all six task actions by touch and cancels destructive choices", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
test.setTimeout(240_000);
|
|
await testPage.setViewportSize({ width: 360, height: 780 });
|
|
await seedLinkedIssue(apiClient);
|
|
try {
|
|
await allTaskActionOutcomes(testPage, apiClient, seedData, true);
|
|
} finally {
|
|
await apiClient.saveUserSettings({ confirm_task_archive: true });
|
|
}
|
|
});
|
|
|
|
// @covers AC-TASKS-THREADS-ACTIONS-004.2 through AC-TASKS-THREADS-ACTIONS-004.7
|
|
test("contains nested choices with long labels and preserves native swiping after dismissal", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}, testInfo) => {
|
|
test.setTimeout(240_000);
|
|
const { a, destination } = await seedActionThreads(apiClient, seedData);
|
|
const destinationName = "Workflow".repeat(12);
|
|
await apiClient.updateWorkflow(destination.id, { name: destinationName });
|
|
const longName = "A deliberately long workflow step " + "unbroken".repeat(24);
|
|
for (let index = 0; index < 18; index++)
|
|
await apiClient.createWorkflowStep(destination.id, `${index} ${longName}`, index + 1);
|
|
await apiClient.updateTaskTitle(a.id, "Task".repeat(15));
|
|
await testPage.setViewportSize({ width: 360, height: 780 });
|
|
await testPage.goto(`/threads?workspace=${seedData.workspaceId}`);
|
|
const ui = new ThreadActionsPage(testPage, true);
|
|
const firstId = await testPage
|
|
.getByTestId("threads-board")
|
|
.locator("[data-thread-column-id]")
|
|
.first()
|
|
.getAttribute("data-thread-column-id");
|
|
if (!firstId) throw new Error("No first thread");
|
|
await expect(ui.trigger(firstId)).toBeVisible();
|
|
await ui.expectHeaderActionAlignment(firstId);
|
|
await testPage.screenshot({ path: testInfo.outputPath("phone-header-360.png") });
|
|
await ui.open(firstId);
|
|
const drawer = testPage.getByTestId("task-management-drawer");
|
|
await ui.contained(drawer);
|
|
await expect(ui.choice("Close")).toHaveCSS("cursor", "pointer");
|
|
await testPage.screenshot({ path: testInfo.outputPath("phone-root-360.png") });
|
|
await ui.nested("Send to workflow");
|
|
await expect(ui.choice("Back")).toHaveCSS("cursor", "pointer");
|
|
await ui.nested(destinationName);
|
|
await ui.contained(drawer);
|
|
await expect(testPage.getByRole("dialog")).toHaveCount(1);
|
|
const scroll = testPage.getByTestId("task-management-scroll");
|
|
expect(await scroll.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
await drawer.evaluate(
|
|
(element) =>
|
|
[element, ...element.querySelectorAll("*")].filter(
|
|
(node) =>
|
|
/auto|scroll/.test(getComputedStyle(node).overflowY) &&
|
|
node.scrollHeight > node.clientHeight,
|
|
).length,
|
|
),
|
|
).toBe(1);
|
|
await ui.choice(`17 ${longName}`).scrollIntoViewIfNeeded();
|
|
await expect(ui.choice(`17 ${longName}`)).toBeVisible();
|
|
await testPage.screenshot({ path: testInfo.outputPath("phone-deep-360.png") });
|
|
await ui.press(ui.choice("Back"));
|
|
await expect(ui.choice(destinationName)).toBeFocused();
|
|
await testPage.keyboard.press("Escape");
|
|
await expect(ui.choice("Send to workflow")).toBeFocused();
|
|
await ui.pick("Delete");
|
|
await ui.contained(testPage.getByRole("alertdialog"));
|
|
await testPage.screenshot({ path: testInfo.outputPath("phone-delete-360.png") });
|
|
await ui.press(testPage.getByRole("button", { name: "Cancel", exact: true }));
|
|
await expect(ui.trigger(firstId)).toBeFocused();
|
|
for (const width of [320, 700, 820]) {
|
|
await testPage.setViewportSize({ width, height: 640 });
|
|
await ui.trigger(firstId).scrollIntoViewIfNeeded();
|
|
await ui.expectHeaderActionAlignment(firstId, width < 640);
|
|
const hitTarget = await ui.trigger(firstId).evaluate((button) => {
|
|
const rect = button.getBoundingClientRect();
|
|
return {
|
|
width: rect.width,
|
|
height: rect.height,
|
|
hit: button.contains(
|
|
document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2),
|
|
),
|
|
};
|
|
});
|
|
expect(hitTarget).toMatchObject({ width: 44, height: 44, hit: true });
|
|
await ui.open(firstId);
|
|
await ui.contained(drawer);
|
|
const rows = await drawer
|
|
.getByRole("button")
|
|
.evaluateAll((buttons) => buttons.map((button) => button.getBoundingClientRect().height));
|
|
expect(rows.every((height) => height >= 44)).toBe(true);
|
|
if (width === 320)
|
|
await testPage.screenshot({ path: testInfo.outputPath("phone-root-320.png") });
|
|
await ui.pick("Close");
|
|
}
|
|
await ui.open(firstId);
|
|
await testPage.touchscreen.tap(4, 4);
|
|
await expect(drawer).toHaveCount(0);
|
|
await expect(ui.trigger(firstId)).toBeFocused();
|
|
await testPage.setViewportSize({ width: 360, height: 780 });
|
|
await swipeDeckLeft(testPage, async () => {
|
|
await expect(testPage.getByTestId("thread-swipe-cue")).toHaveText("2/2");
|
|
});
|
|
await expect(testPage.getByTestId("thread-swipe-cue")).toHaveText("2/2");
|
|
});
|