Files
kandev/apps/web/e2e/tests/github/github-scope-bar.spec.ts
T
João Salavisa 5ee5161166 feat(ui): confirm saved view deletion (#3461)
Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-09-07 16:14:58 +01:00

361 lines
14 KiB
TypeScript

import type { Page } from "@playwright/test";
import { test, expect } from "../../fixtures/test-base";
const DEFAULT_REPO = "defaultorg/defaultrepo";
const DEFAULT_REPO_ISSUE = "Issue in saved default repo";
const OTHER_REPO_ISSUE = "Issue outside saved default repo";
const SAVED_QUERY_FILTER = "assignee:@me is:open saved-default-repo-e2e";
const DEFAULTS_TIMESTAMP = "2026-08-07T00:00:00Z";
function issueRow(testPage: Page, title: string) {
return testPage.getByTestId("issue-row").filter({ hasText: title });
}
// The /github dashboard used to render its own w-60 presets rail next to the
// global AppSidebar (a redundant "double sidebar"). On desktop that rail is now
// a horizontal scope bar; this guards that it drives presets/kind and that the
// old inline rail is gone. Mobile keeps the sheet (see mobile-github-sidebar).
test.describe("Desktop /github scope bar", () => {
test.describe("saved default views", () => {
test.afterEach(async ({ testPage, seedData }) => {
const cleanupResponse = await testPage.request.put("/api/v1/github/workspace-settings", {
data: { workspace_id: seedData.workspaceId, saved_presets: [] },
});
expect(cleanupResponse.ok(), "afterEach saved-presets cleanup").toBe(true);
});
test("saved defaults apply independently on entry and kind switches", async ({
testPage,
apiClient,
seedData,
}) => {
const prDefaultLabel = "Default Kandev PRs";
const issueDefaultLabel = "Future Kandev issues";
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("test-user");
await apiClient.mockGitHubAddPRs([
{
number: 7,
title: "PR in default view",
state: "open",
head_branch: "feat/default-view",
base_branch: "main",
author_login: "test-user",
repo_owner: "defaultorg",
repo_name: "defaultrepo",
},
]);
await apiClient.mockGitHubAddIssues([
{
number: 8,
title: DEFAULT_REPO_ISSUE,
state: "open",
author_login: "test-user",
repo_owner: "defaultorg",
repo_name: "defaultrepo",
assignees: ["test-user"],
},
{
number: 9,
title: OTHER_REPO_ISSUE,
state: "open",
author_login: "test-user",
repo_owner: "otherorg",
repo_name: "otherrepo",
assignees: ["test-user"],
},
]);
const seedResponse = await testPage.request.put("/api/v1/github/workspace-settings", {
data: {
workspace_id: seedData.workspaceId,
saved_presets: [
{
id: "saved-pr-default",
kind: "pr",
label: prDefaultLabel,
customQuery: "author:@me is:open",
repoFilter: DEFAULT_REPO,
createdAt: DEFAULTS_TIMESTAMP,
isDefault: true,
},
{
id: "saved-issue-future",
kind: "issue",
label: issueDefaultLabel,
customQuery: "assignee:@me is:open",
repoFilter: DEFAULT_REPO,
createdAt: DEFAULTS_TIMESTAMP,
isDefault: false,
},
],
},
});
expect(seedResponse.ok()).toBe(true);
await testPage.goto("/github");
const scopeBar = testPage.getByTestId("github-presets-scope-bar");
const title = testPage.getByTestId("github-list-toolbar-title");
const savedMenu = testPage.getByTestId("github-saved-queries-menu");
const repoFilter = testPage.getByTestId("github-repo-filter-trigger");
await expect(title).toContainText(prDefaultLabel);
await expect(repoFilter).toContainText(DEFAULT_REPO);
await scopeBar.getByRole("button", { name: "Issues", exact: true }).click();
await expect(title).toContainText("Assigned");
await expect(repoFilter).toContainText("All repos");
await savedMenu.focus();
await savedMenu.press("Enter");
const issueSavedQuery = testPage.getByRole("menuitem", {
name: issueDefaultLabel,
exact: true,
});
const setIssueDefault = testPage.getByRole("menuitemcheckbox", {
name: `Set ${issueDefaultLabel} as default view`,
});
await expect(issueSavedQuery).toBeFocused();
await testPage.keyboard.press("ArrowDown");
await expect(setIssueDefault).toBeFocused();
const deleteIssueSavedQuery = testPage.getByRole("menuitem", {
name: `Delete ${issueDefaultLabel} saved query`,
});
await testPage.keyboard.press("ArrowDown");
await expect(deleteIssueSavedQuery).toBeFocused();
await testPage.keyboard.press("ArrowUp");
await expect(setIssueDefault).toBeFocused();
const setResponse = testPage.waitForResponse(
(response) =>
response.url().includes("/api/v1/github/workspace-settings") &&
response.request().method() === "PUT" &&
response.status() === 200,
);
await testPage.keyboard.press("Enter");
await setResponse;
const setMarker = testPage.getByRole("menuitemcheckbox", {
name: `Clear ${issueDefaultLabel} as default view`,
});
await expect(setMarker).toBeVisible();
await expect(setMarker).toHaveAttribute("aria-checked", "true");
await expect(title).toContainText("Assigned");
await expect(repoFilter).toContainText("All repos");
await testPage.reload();
await expect(title).toContainText(prDefaultLabel);
await scopeBar.getByRole("button", { name: "Issues", exact: true }).click();
await expect(title).toContainText(issueDefaultLabel);
await expect(repoFilter).toContainText(DEFAULT_REPO);
await expect(issueRow(testPage, DEFAULT_REPO_ISSUE)).toBeVisible();
await expect(issueRow(testPage, OTHER_REPO_ISSUE)).toHaveCount(0);
await savedMenu.click();
const clearIssueDefault = testPage.getByRole("menuitemcheckbox", {
name: `Clear ${issueDefaultLabel} as default view`,
});
const clearResponse = testPage.waitForResponse(
(response) =>
response.url().includes("/api/v1/github/workspace-settings") &&
response.request().method() === "PUT" &&
response.status() === 200,
);
await clearIssueDefault.click();
await clearResponse;
await expect(title).toContainText(issueDefaultLabel);
await testPage.reload();
await expect(title).toContainText(prDefaultLabel);
await scopeBar.getByRole("button", { name: "Issues", exact: true }).click();
await expect(title).toContainText("Assigned");
await expect(repoFilter).toContainText("All repos");
await savedMenu.click();
await deleteIssueSavedQuery.click();
const deleteConfirmation = testPage.getByTestId("saved-task-view-delete-confirmation");
await expect(deleteConfirmation).toHaveAccessibleName(`Delete ${issueDefaultLabel}?`);
await expect(testPage.getByRole("menu")).toBeVisible();
await deleteConfirmation.getByRole("button", { name: "Cancel" }).click();
await expect(issueSavedQuery).toBeVisible();
await expect(testPage.getByRole("menu")).toBeVisible();
await deleteIssueSavedQuery.click();
const deleteResponse = testPage.waitForResponse(
(response) =>
response.ok() &&
response.request().method() === "PUT" &&
response.url().includes("/api/v1/github/workspace-settings"),
);
await deleteConfirmation.getByRole("button", { name: `Delete ${issueDefaultLabel}` }).click();
await deleteResponse;
await expect(issueSavedQuery).toHaveCount(0);
});
});
test("scope bar drives presets and kind, with no second sidebar", async ({
testPage,
apiClient,
}) => {
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("test-user");
await apiClient.mockGitHubAddPRs([
{
number: 11,
title: "Scope bar PR",
state: "open",
head_branch: "feat/scope",
base_branch: "main",
author_login: "test-user",
repo_owner: "testorg",
repo_name: "testrepo",
},
]);
await testPage.goto("/github");
const scopeBar = testPage.getByTestId("github-presets-scope-bar");
const title = testPage.getByTestId("github-list-toolbar-title");
// The horizontal scope bar is present; the old inline rail is gone.
await expect(scopeBar).toBeVisible();
await expect(testPage.getByTestId("github-presets-sidebar-inline")).toHaveCount(0);
// Default PR preset is active.
await expect(title).toContainText("Review requested");
// Clicking a preset pill updates the active query.
await scopeBar.getByRole("button", { name: "Mentions" }).click();
await expect(title).toContainText("Mentions");
// Switching kind to Issues falls back to the first issue preset.
await scopeBar.getByRole("button", { name: "Issues" }).click();
await expect(title).toContainText("Assigned");
});
test("repo filter menu can be searched", async ({ testPage, apiClient }) => {
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("test-user");
await apiClient.mockGitHubAddPRs([
{
number: 21,
title: "Searchable repo filter PR",
state: "open",
head_branch: "feat/search",
base_branch: "main",
author_login: "test-user",
repo_owner: "testorg",
repo_name: "testrepo",
},
{
number: 22,
title: "Searchable repo filter second PR",
state: "open",
head_branch: "feat/other",
base_branch: "main",
author_login: "test-user",
repo_owner: "anotherorg",
repo_name: "secondrepo",
},
]);
await testPage.goto("/github");
await testPage.getByTestId("github-repo-filter-trigger").click();
const search = testPage.getByTestId("github-repo-filter-dropdown").getByRole("combobox");
await expect(search).toBeVisible();
await search.fill("testorg");
await testPage.getByRole("option", { name: "testorg/testrepo" }).click();
await expect(testPage.getByTestId("github-repo-filter-trigger")).toContainText(
"testorg/testrepo",
);
});
test("saved query defaults to its chosen repository and persists", async ({
testPage,
apiClient,
}) => {
const savedQuery = "Default repo issues";
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("test-user");
await apiClient.mockGitHubAddIssues([
{
number: 31,
title: DEFAULT_REPO_ISSUE,
state: "open",
author_login: "test-user",
repo_owner: "defaultorg",
repo_name: "defaultrepo",
assignees: ["test-user"],
},
{
number: 32,
title: OTHER_REPO_ISSUE,
state: "open",
author_login: "test-user",
repo_owner: "otherorg",
repo_name: "otherrepo",
assignees: ["test-user"],
},
]);
await testPage.goto("/github");
const scopeBar = testPage.getByTestId("github-presets-scope-bar");
const savedMenu = testPage.getByTestId("github-saved-queries-menu");
const repoFilter = testPage.getByTestId("github-repo-filter-trigger");
await expect(scopeBar).toBeVisible();
await scopeBar.getByRole("button", { name: "Issues", exact: true }).click();
const queryInput = testPage.getByPlaceholder(/Custom query/);
await queryInput.fill(SAVED_QUERY_FILTER);
await queryInput.press("Enter");
await expect(testPage.getByTestId("issue-row")).toHaveCount(2, { timeout: 15_000 });
await savedMenu.click();
await testPage.getByRole("menuitem", { name: "Save current query" }).click();
const dialog = testPage.getByRole("dialog", { name: "Save query" });
await dialog.getByLabel("Name").fill(savedQuery);
const saveRepoTrigger = dialog.getByTestId("github-save-query-repo-trigger");
await expect(saveRepoTrigger).toBeVisible();
await expect
.poll(async () => (await saveRepoTrigger.boundingBox())?.height ?? 0)
.toBeCloseTo(36, 0);
await saveRepoTrigger.click();
const saveRepoDropdown = testPage.getByTestId("github-save-query-repo-dropdown");
await expect(saveRepoDropdown).toBeVisible();
await saveRepoDropdown.getByRole("option", { name: DEFAULT_REPO, exact: true }).click();
const saveResponse = testPage.waitForResponse(
(response) =>
response.url().includes("/api/v1/github/workspace-settings") &&
response.request().method() === "PUT" &&
response.status() === 200,
);
await dialog.getByRole("button", { name: "Save", exact: true }).click();
await saveResponse;
await expect(repoFilter).toContainText(DEFAULT_REPO);
await expect(issueRow(testPage, DEFAULT_REPO_ISSUE)).toBeVisible();
await expect(issueRow(testPage, OTHER_REPO_ISSUE)).toHaveCount(0);
await repoFilter.click();
await testPage
.getByTestId("github-repo-filter-dropdown")
.getByRole("option", { name: "All repos", exact: true })
.click();
await expect(testPage.getByTestId("issue-row")).toHaveCount(2);
await savedMenu.click();
await testPage.getByRole("menuitem").filter({ hasText: savedQuery }).click();
await expect(repoFilter).toContainText(DEFAULT_REPO);
await expect(issueRow(testPage, DEFAULT_REPO_ISSUE)).toBeVisible();
await expect(issueRow(testPage, OTHER_REPO_ISSUE)).toHaveCount(0);
await testPage.reload();
await expect(scopeBar).toBeVisible();
await scopeBar.getByRole("button", { name: "Issues", exact: true }).click();
await savedMenu.click();
await testPage.getByRole("menuitem").filter({ hasText: savedQuery }).click();
await expect(repoFilter).toContainText(DEFAULT_REPO);
await expect(issueRow(testPage, DEFAULT_REPO_ISSUE)).toBeVisible();
await expect(issueRow(testPage, OTHER_REPO_ISSUE)).toHaveCount(0);
});
});