fix: suppress title previews during Kanban multi-select (#3919)
This commit is contained in:
@@ -275,7 +275,7 @@ export function KanbanCardShell({
|
||||
<KanbanCardBody
|
||||
task={task}
|
||||
repositoryChips={repositoryChips ?? []}
|
||||
enableTitleHover
|
||||
enableTitleHover={!isMultiSelectMode}
|
||||
actions={
|
||||
<KanbanCardActionSlot
|
||||
isMultiSelectMode={isMultiSelectMode}
|
||||
|
||||
@@ -208,6 +208,21 @@ describe("KanbanCard — title-preview disclosure widening (AC-UI-PIPELINE-ROW-0
|
||||
});
|
||||
});
|
||||
|
||||
describe("KanbanCard — title preview in multi-select mode", () => {
|
||||
// @covers AC-TASKS-RICH-TASK-TITLE-PREVIEWS-001.2
|
||||
it.each([
|
||||
["unselected", false],
|
||||
["selected", true],
|
||||
] as const)("does not mount a title preview for a %s card", (_label, isSelected) => {
|
||||
renderCard(baseTask({ description: "Some description content" }), [], {
|
||||
isMultiSelectMode: true,
|
||||
isSelected,
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId("task-title-preview-trigger")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// @covers AC-TASKS-MOBILE-KANBAN-SCROLL-001.1, AC-TASKS-MOBILE-KANBAN-SCROLL-001.4
|
||||
it.each(["Enter", " "])(
|
||||
"phone cards open with %j without exposing or invoking drag pickup",
|
||||
|
||||
@@ -84,9 +84,11 @@ function RowMenuTrigger({
|
||||
function RowInfoColumn({
|
||||
task,
|
||||
repositoryChips,
|
||||
isMultiSelectMode,
|
||||
}: {
|
||||
task: Task;
|
||||
repositoryChips: ReturnType<typeof resolveTaskRepositoryChips>;
|
||||
isMultiSelectMode?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const sessionCount = task.sessionCount ?? 0;
|
||||
@@ -94,7 +96,7 @@ function RowInfoColumn({
|
||||
return (
|
||||
<div className="w-[200px] min-w-0 shrink-0" data-testid="pipeline-row-info">
|
||||
<div data-testid="pipeline-row-title">
|
||||
<CardTitle task={task} enableTitleHover />
|
||||
<CardTitle task={task} enableTitleHover={!isMultiSelectMode} />
|
||||
</div>
|
||||
{/* Height is reserved whether or not the task has a repository, so a
|
||||
row with no repository is the same height as one with, and the
|
||||
@@ -259,7 +261,11 @@ export function PipelineRow({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<RowInfoColumn task={task} repositoryChips={repositoryChips} />
|
||||
<RowInfoColumn
|
||||
task={task}
|
||||
repositoryChips={repositoryChips}
|
||||
isMultiSelectMode={isMultiSelectMode}
|
||||
/>
|
||||
<div
|
||||
ref={overflowStage.outerRef}
|
||||
data-testid="pipeline-row-overflow-region"
|
||||
|
||||
@@ -135,13 +135,13 @@ function renderKanbanCard() {
|
||||
);
|
||||
}
|
||||
|
||||
function renderPipelineRow() {
|
||||
function renderPipelineRow(task: Task = TASK, isMultiSelectMode = false) {
|
||||
return render(
|
||||
<ToastProvider>
|
||||
<StateProvider initialState={SHARED_INITIAL_STATE}>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Graph2TaskPipeline
|
||||
task={TASK}
|
||||
task={task}
|
||||
steps={STEPS}
|
||||
moveTargetSteps={STEPS}
|
||||
workspaceId={WORKSPACE_ID}
|
||||
@@ -151,6 +151,7 @@ function renderPipelineRow() {
|
||||
onPreviewTask={() => undefined}
|
||||
onOpenTask={() => undefined}
|
||||
onDeleteTask={() => undefined}
|
||||
isMultiSelectMode={isMultiSelectMode}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</StateProvider>
|
||||
@@ -158,6 +159,22 @@ function renderPipelineRow() {
|
||||
);
|
||||
}
|
||||
|
||||
describe("Pipeline title preview in multi-select mode", () => {
|
||||
// @covers AC-TASKS-RICH-TASK-TITLE-PREVIEWS-001.2
|
||||
it.each([
|
||||
["outside", false, true],
|
||||
["inside", true, false],
|
||||
] as const)("is %s multi-select mode", (_mode, isMultiSelectMode, previewEnabled) => {
|
||||
renderPipelineRow({ ...TASK, parentTaskId: "parent-1" }, isMultiSelectMode);
|
||||
|
||||
if (previewEnabled) {
|
||||
expect(screen.getByTestId("task-title-preview-trigger")).not.toBeNull();
|
||||
} else {
|
||||
expect(screen.queryByTestId("task-title-preview-trigger")).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function openMenuAndSnapshot() {
|
||||
const trigger = screen.getByRole("button", { name: t("kanban:moreOptions") });
|
||||
fireEvent.pointerDown(trigger, { button: 0, pointerId: 1 });
|
||||
|
||||
@@ -141,4 +141,61 @@ test.describe("Task title hover card on the Kanban card", () => {
|
||||
await title.click();
|
||||
await expect(testPage).toHaveURL(new RegExp(`/t/${parent.id}`));
|
||||
});
|
||||
|
||||
// @covers AC-TASKS-RICH-TASK-TITLE-PREVIEWS-001.2, AC-TASKS-RICH-TASK-TITLE-PREVIEWS-001.3, AC-TASKS-RICH-TASK-TITLE-PREVIEWS-001.4
|
||||
test("multi-select closes and suppresses title previews, then restores them when it ends", async ({
|
||||
testPage,
|
||||
apiClient,
|
||||
seedData,
|
||||
}) => {
|
||||
test.setTimeout(120_000);
|
||||
const { parent } = await seedParentWithSubtasks(
|
||||
apiClient,
|
||||
seedData,
|
||||
"Preview selection parent",
|
||||
);
|
||||
const sibling = await apiClient.createTask(seedData.workspaceId, "Preview selection sibling", {
|
||||
description: "Second preview eligible task",
|
||||
workflow_id: seedData.workflowId,
|
||||
workflow_step_id: seedData.startStepId,
|
||||
repository_ids: [seedData.repositoryId],
|
||||
});
|
||||
|
||||
const kanban = new KanbanPage(testPage);
|
||||
await kanban.goto();
|
||||
const parentCard = kanban.taskCard(parent.id);
|
||||
const siblingCard = kanban.taskCard(sibling.id);
|
||||
await expect(parentCard).toBeVisible({ timeout: 45_000 });
|
||||
await expect(siblingCard).toBeVisible({ timeout: 45_000 });
|
||||
|
||||
const parentTitle = parentCard.getByTestId("task-card-title");
|
||||
await parentTitle.hover();
|
||||
const hoverCard = testPage.getByTestId("task-title-hover-card");
|
||||
await expect(hoverCard).toBeVisible();
|
||||
|
||||
// Modifier-click enters selection mode while the pointer remains on the
|
||||
// open title preview, so this proves the mode transition itself closes it.
|
||||
await parentTitle.click({ modifiers: ["ControlOrMeta"] });
|
||||
await expect(kanban.multiSelectToolbar).toContainText("1 selected");
|
||||
await expect(hoverCard).toHaveCount(0);
|
||||
|
||||
const siblingTitle = siblingCard.getByTestId("task-card-title");
|
||||
await siblingTitle.hover();
|
||||
await dwell(
|
||||
testPage,
|
||||
300,
|
||||
"negative-assertion",
|
||||
"multi-select must suppress a preview on an unselected title",
|
||||
);
|
||||
await expect(hoverCard).toHaveCount(0);
|
||||
await siblingTitle.click();
|
||||
await expect(kanban.multiSelectToolbar).toContainText("2 selected");
|
||||
await expect(kanban.board).toBeVisible();
|
||||
await expect(testPage).not.toHaveURL(new RegExp(`/t/${sibling.id}`));
|
||||
|
||||
await kanban.multiSelectToggle.first().click();
|
||||
await expect(kanban.multiSelectToolbar).not.toBeVisible();
|
||||
await parentTitle.hover();
|
||||
await expect(hoverCard).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user