diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index c1ea0e061b..a6de9b227d 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -107,6 +107,7 @@ export default defineConfig({ "**/project-commit-detail.spec.ts", "**/project-inbox.spec.ts", "**/project-issue-comments.spec.ts", + "**/projects-empty-state.spec.ts", "**/project-pr-review.spec.ts", "**/persona-model-combobox-screenshots.spec.ts", "**/drafts-screenshots.spec.ts", diff --git a/desktop/src/features/projects/ui/ProjectsView.tsx b/desktop/src/features/projects/ui/ProjectsView.tsx index 78ba09934d..35029c89b5 100644 --- a/desktop/src/features/projects/ui/ProjectsView.tsx +++ b/desktop/src/features/projects/ui/ProjectsView.tsx @@ -592,9 +592,10 @@ export function ProjectsView() { ); } - if (projects.length === 0) { - return ; - } + // Zero projects still renders the full chrome — the create menu lives in the + // toolbar, so returning early here would leave no way to create the first + // project from the UI. + const hasProjects = projects.length > 0; const projectItems = visibleProjects.length === 0 ? ( @@ -850,7 +851,9 @@ export function ProjectsView() {
- {filter === "all" ? ( + {!hasProjects ? ( + + ) : filter === "all" ? ( { + window.localStorage.setItem( + featureOverridesStorageKey, + JSON.stringify({ projects: true }), + ); + window.localStorage.setItem( + "buzz.projects.hidden-cards.v1", + JSON.stringify(coordinates), + ); + }, + { + coordinates: ALL_MOCK_PROJECT_COORDINATES, + featureOverridesStorageKey: FEATURE_OVERRIDES_STORAGE_KEY, + }, + ); + await installMockBridge(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); + await page.getByTestId("open-projects-view").click(); +} + +test("projects header and create menu stay mounted with zero projects", async ({ + page, +}) => { + await openEmptyProjectsView(page); + + await expect( + page.getByRole("heading", { level: 1, name: "Projects" }), + ).toBeVisible(); + await expect(page.getByText("No projects yet")).toBeVisible(); + await expect( + page.getByRole("button", { name: "Repositories", exact: true }), + ).toBeVisible(); + + // The regression: the create menu was unreachable until a project already + // existed, so the first project could never be created from the UI. + await page.getByTestId("projects-create-menu").hover(); + await page.getByRole("menuitem", { name: "Project" }).click(); + await expect(page.getByTestId("create-project-dialog")).toBeVisible(); + await expect(page.getByTestId("create-project-name")).toBeVisible(); +}); + +test("empty state replaces only the content area on every filter", async ({ + page, +}) => { + await openEmptyProjectsView(page); + + for (const filter of ["Repositories", "Pull Requests", "Issues"]) { + await page.getByRole("button", { name: filter, exact: true }).click(); + await expect(page.getByText("No projects yet")).toBeVisible(); + await expect(page.getByTestId("projects-create-menu")).toBeVisible(); + } +});