e2e/issue-filter-advanced.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test, type Page } from "@playwright/test";
import { createTestApi, loginAsDefault } from "./helpers";
import type { TestApiClient } from "./fixtures";
// Menu search, the due-date dimension and advanced mode, end to end: menu or
// panel → store → table query → SQL. SHOTS_DIR, when set, collects screenshots.
async function visibleIssueTitles(page: Page, titles: string[]) {
const present: string[] = [];
for (const title of titles) {
if (await page.getByText(title, { exact: true }).first().isVisible()) {
present.push(title);
}
}
return present.sort();
}
async function shot(page: Page, name: string) {
const dir = process.env.SHOTS_DIR;
if (!dir) return;
// Let menus and tooltips finish their enter/exit transitions.
await page.waitForTimeout(400);
await page.screenshot({ path: `${dir}/${name}.png` });
}
async function closeMenus(page: Page) {
await page.keyboard.press("Escape");
await page.keyboard.press("Escape");
}
function localDate(offsetDays: number) {
const d = new Date();
d.setDate(d.getDate() + offsetDays);
const pad = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
test.describe("Issue filter: search, due date, advanced", () => {
let api: TestApiClient;
const suffix = Date.now().toString(36);
const urgentLate = `adv urgent late ${suffix}`;
const urgentUndated = `adv urgent undated ${suffix}`;
const lowLate = `adv low late ${suffix}`;
const lowUndated = `adv low undated ${suffix}`;
const mediumLate = `adv medium late ${suffix}`;
const all = [urgentLate, urgentUndated, lowLate, lowUndated, mediumLate];
test.beforeEach(async ({ page }) => {
api = await createTestApi();
const yesterday = localDate(-1);
await api.createIssue(urgentLate, { priority: "urgent", due_date: yesterday });
await api.createIssue(urgentUndated, { priority: "urgent" });
await api.createIssue(lowLate, { priority: "low", due_date: yesterday });
await api.createIssue(lowUndated, { priority: "low" });
await api.createIssue(mediumLate, { priority: "medium", due_date: yesterday });
await loginAsDefault(page);
});
test.afterEach(async () => {
await api.cleanup();
});
test("search adds a value as a condition; due date filters by bucket", async ({ page }) => {
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([...all].sort());
await page.getByRole("button", { name: "Filter", exact: true }).click();
await page.getByRole("textbox", { name: "Filter by field or value…" }).fill("urg");
const urgentRow = page.getByRole("menuitemcheckbox", { name: /Priority\s*is\s*Urgent/ });
await expect(urgentRow).toBeVisible();
await shot(page, "01-search");
await urgentRow.click();
await closeMenus(page);
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([urgentLate, urgentUndated].sort());
await page.getByRole("button", { name: "1 filter" }).click();
await page.getByRole("menuitem", { name: /^Due date/ }).click();
await page.getByRole("menuitemcheckbox", { name: "Overdue" }).click();
await shot(page, "02-due-date");
await closeMenus(page);
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([urgentLate]);
});
test("advanced mode ORs inside a group and switches back only when simple can say it", async ({ page }) => {
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([...all].sort());
// Start from a simple filter: due date is overdue.
await page.getByRole("button", { name: "Filter", exact: true }).click();
await page.getByRole("menuitem", { name: /^Due date/ }).click();
await page.getByRole("menuitemcheckbox", { name: "Overdue" }).click();
await closeMenus(page);
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([lowLate, mediumLate, urgentLate].sort());
// Switching carries it over as the first rule.
await page.getByRole("button", { name: "1 filter" }).click();
await page.getByRole("menuitem", { name: /Switch to advanced filter/ }).click();
const panel = page.getByTestId("advanced-filter-panel");
await expect(panel).toBeVisible();
await expect(panel.getByTestId("advanced-filter-row")).toHaveCount(1);
await expect(panel.getByRole("button", { name: "Switch to simple filter" })).toBeEnabled();
// AND (priority is urgent OR priority is low), as a group.
await panel.getByRole("button", { name: "Add condition group" }).click();
await page.getByRole("menuitem", { name: "Priority" }).click();
const group = panel.getByTestId("advanced-filter-group");
await expect(group).toBeVisible();
await group.getByRole("button", { name: "Values" }).first().click();
await page.getByRole("menuitemcheckbox", { name: "Urgent" }).click();
await page.keyboard.press("Escape");
await group.getByRole("button", { name: "Add condition" }).click();
await page.getByRole("menuitem", { name: "Priority" }).click();
await group.getByRole("button", { name: "Values" }).nth(1).click();
await page.getByRole("menuitemcheckbox", { name: "Low" }).click();
await page.keyboard.press("Escape");
await expect(panel).toBeVisible();
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([lowLate, urgentLate].sort());
const toSimple = panel.getByRole("button", { name: "Switch to simple filter" });
await expect(toSimple).toBeDisabled();
await shot(page, "03-advanced");
await toSimple.locator("..").hover();
const blockedReason = page.getByText(/Simple filters can't show groups/);
await expect(blockedReason).toBeVisible();
await panel.getByText("Advanced filter").hover();
await expect(blockedReason).toBeHidden();
// The trigger names the mode; the chip bar carries the tree as one chip.
await page.keyboard.press("Escape");
await expect(panel).toBeHidden();
await expect(page.getByRole("button", { name: "Advanced filter" })).toBeVisible();
await page.mouse.move(600, 400);
await shot(page, "04-advanced-closed");
// Delete the group: one overdue rule is a simple filter again.
await page.getByRole("button", { name: "Advanced filter" }).click();
await expect(panel).toBeVisible();
await group.getByRole("button", { name: "Condition actions" }).last().click();
await page.getByRole("menuitem", { name: "Delete group" }).click();
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([lowLate, mediumLate, urgentLate].sort());
await panel.getByRole("button", { name: "Switch to simple filter" }).click();
await expect(panel).toBeHidden();
await expect(page.getByRole("button", { name: "1 filter" })).toBeVisible();
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([lowLate, mediumLate, urgentLate].sort());
});
test("custom properties are conditions like any other field", async ({ page }) => {
const property = await api.createProperty(`Estimate ${suffix}`, "number");
await page.reload();
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([...all].sort());
await page.getByRole("button", { name: "Filter", exact: true }).click();
await page.getByRole("menuitem", { name: /Switch to advanced filter/ }).click();
const panel = page.getByTestId("advanced-filter-panel");
await panel.getByRole("button", { name: "Add condition", exact: true }).click();
await page.getByRole("menuitem", { name: "Priority" }).click();
await panel.getByRole("button", { name: "Values" }).first().click();
await page.getByRole("menuitemcheckbox", { name: "Urgent" }).click();
await page.keyboard.press("Escape");
await panel.getByRole("button", { name: "Add condition", exact: true }).click();
await expect(page.getByRole("menuitem", { name: property.name })).toBeVisible();
await shot(page, "05-advanced-field-picker");
await page.getByRole("menuitem", { name: property.name }).click();
await panel.getByRole("button", { name: "Values" }).nth(1).click();
await page.getByRole("menuitemcheckbox", { name: "No value" }).click();
await page.keyboard.press("Escape");
const rows = panel.getByTestId("advanced-filter-row");
await expect(rows).toHaveCount(2);
await expect(rows.nth(1)).toContainText(property.name);
await expect(panel.getByRole("button", { name: "Add property filter" })).toHaveCount(0);
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([urgentLate, urgentUndated].sort());
await shot(page, "06-advanced-properties");
await panel.getByRole("button", { name: "Clear all" }).click();
await expect(rows).toHaveCount(0);
await expect(panel.getByRole("button", { name: "Clear all" })).toBeDisabled();
await expect.poll(() => visibleIssueTitles(page, all)).toEqual([...all].sort());
});
});