diff --git a/desktop/src/features/projects/ui/ProjectCards.test.mjs b/desktop/src/features/projects/ui/ProjectCards.test.mjs new file mode 100644 index 00000000000..ec302837baa --- /dev/null +++ b/desktop/src/features/projects/ui/ProjectCards.test.mjs @@ -0,0 +1,49 @@ +import assert from "node:assert/strict"; +import { after, afterEach, before, test } from "node:test"; + +import { JSDOM } from "jsdom"; + +const dom = new JSDOM("", { + url: "http://localhost", +}); + +before(() => { + Object.assign(globalThis, { + document: dom.window.document, + HTMLElement: dom.window.HTMLElement, + IS_REACT_ACT_ENVIRONMENT: true, + window: dom.window, + }); + dom.window.matchMedia = () => ({ + matches: false, + addEventListener() {}, + removeEventListener() {}, + }); +}); + +afterEach(async () => { + const { cleanup } = await import("@testing-library/react"); + cleanup(); +}); + +after(() => dom.window.close()); + +test("empty projects state renders a create-project action that fires the provided callback", async () => { + const { createElement } = await import("react"); + const { fireEvent, render, screen } = await import("@testing-library/react"); + const { EmptyState } = await import("./ProjectCards.tsx"); + + let createClicked = false; + render( + createElement(EmptyState, { + onCreateProject: () => { + createClicked = true; + }, + }), + ); + + assert.ok(screen.getByText("No projects yet")); + const createButton = screen.getByRole("button", { name: "Create project" }); + fireEvent.click(createButton); + assert.equal(createClicked, true); +}); diff --git a/desktop/src/features/projects/ui/ProjectCards.tsx b/desktop/src/features/projects/ui/ProjectCards.tsx index 09535fdfc77..ba48b0297b8 100644 --- a/desktop/src/features/projects/ui/ProjectCards.tsx +++ b/desktop/src/features/projects/ui/ProjectCards.tsx @@ -327,7 +327,11 @@ function RepositoryUnavailableIndicator({ ); } -export function EmptyState() { +export function EmptyState({ + onCreateProject, +}: { + onCreateProject: () => void; +}) { return (
@@ -337,6 +341,9 @@ export function EmptyState() { Projects published to this relay will appear here.

+ ); } diff --git a/desktop/src/features/projects/ui/ProjectsView.tsx b/desktop/src/features/projects/ui/ProjectsView.tsx index 78ba09934de..6cbe18055cb 100644 --- a/desktop/src/features/projects/ui/ProjectsView.tsx +++ b/desktop/src/features/projects/ui/ProjectsView.tsx @@ -15,7 +15,10 @@ import { useProjectsWorkItemsQuery, } from "@/features/projects/hooks"; import { useRepositoryActivitySummariesQuery } from "@/features/projects/repositoryActivityHooks"; -import { useCreateProjectMutation } from "@/features/projects/useCreateProject"; +import { + type CreateProjectInput, + useCreateProjectMutation, +} from "@/features/projects/useCreateProject"; import { selectProjectRepository } from "@/features/projects/projectModels"; import { useProjectsRepoSnapshotsQuery } from "@/features/projects/useProjectsRepoSnapshots"; import { @@ -573,6 +576,23 @@ export function ProjectsView() { [deleteProjectMutation], ); + const handleCreateProject = React.useCallback( + async (input: CreateProjectInput) => { + const result = await createProjectMutation.mutateAsync(input); + if (result.compatibilityWarning) { + toast.warning("Created as a standalone project", { + description: result.compatibilityWarning, + }); + } else { + toast.success(`Project "${result.project.name}" created.`); + } + // Land on the complete project list after creation. + handleRepositoryScopeChange("all"); + handleFilterChange("projects"); + }, + [createProjectMutation, handleFilterChange, handleRepositoryScopeChange], + ); + if (projectsQuery.isLoading) { return ; } @@ -593,7 +613,17 @@ export function ProjectsView() { } if (projects.length === 0) { - return ; + return ( + <> + setCreateProjectOpen(true)} /> + + + ); } const projectItems = @@ -790,19 +820,7 @@ export function ProjectsView() {
{createMenu}
{ - const result = await createProjectMutation.mutateAsync(input); - if (result.compatibilityWarning) { - toast.warning("Created as a standalone project", { - description: result.compatibilityWarning, - }); - } else { - toast.success(`Project "${result.project.name}" created.`); - } - // Land on the complete project list after creation. - handleRepositoryScopeChange("all"); - handleFilterChange("projects"); - }} + onCreate={handleCreateProject} onOpenChange={setCreateProjectOpen} open={createProjectOpen} />