diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.stories.tsx index a3f6a26866..a7d247c3d7 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.stories.tsx @@ -15,15 +15,24 @@ import { templateVersionsQueryKey, } from "#/api/queries/templates"; import { workspacesKey } from "#/api/queries/workspaces"; -import type { Workspace } from "#/api/typesGenerated"; +import type { Workspace, WorkspaceAppHealth } from "#/api/typesGenerated"; import { workspaceChecks } from "#/modules/workspaces/permissions"; import { MockDefaultOrganization, + MockDormantOutdatedWorkspace, + MockDormantWorkspace, + MockOutdatedStoppedWorkspaceAlwaysUpdate, + MockOutdatedWorkspace, + MockRunningOutdatedWorkspace, MockStoppedWorkspace, MockTemplate, MockTemplateVersion, MockUserOwner, MockWorkspace, + MockWorkspaceAgent, + MockWorkspaceApp, + MockWorkspaceBuild, + MockWorkspacesResponse, } from "#/testHelpers/entities"; import { withAuthProvider, @@ -216,3 +225,379 @@ export const PaginationChangesQueryKey: Story = { }); }, }; + +export const Empty: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ workspaces: [], count: 0 }); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByRole("heading", { name: /Create a workspace/ }); + }, +}; + +export const RendersWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue(MockWorkspacesResponse); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByText(`${MockWorkspace.name}1`); + const templateDisplayNames = await canvas.findAllByText( + MockWorkspace.template_display_name, + ); + expect(templateDisplayNames).toHaveLength(MockWorkspacesResponse.count); + }, +}; + +const deletableWorkspaces: Workspace[] = [ + { ...MockWorkspace, id: "1" }, + { ...MockWorkspace, id: "2" }, + { ...MockWorkspace, id: "3" }, +]; + +export const DeletesOnlySelectedWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: deletableWorkspaces, + count: deletableWorkspaces.length, + }); + spyOn(API, "deleteWorkspace").mockResolvedValue(MockWorkspaceBuild); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /delete/i })); + + const confirmButton = await body.findByTestId("confirm-button"); + await user.click(confirmButton); + await user.click(confirmButton); + await user.click(confirmButton); + + await waitFor(() => expect(API.deleteWorkspace).toHaveBeenCalledTimes(2)); + expect(API.deleteWorkspace).toHaveBeenCalledWith("1"); + expect(API.deleteWorkspace).toHaveBeenCalledWith("2"); + }, +}; + +const skipUpToDateWorkspaces: Workspace[] = [ + { ...MockWorkspace, id: "1" }, + { ...MockDormantWorkspace, id: "2" }, + { ...MockOutdatedWorkspace, id: "3" }, + { + ...MockOutdatedWorkspace, + id: "4", + latest_build: { ...MockOutdatedWorkspace.latest_build, status: "running" }, + }, +]; + +export const BatchUpdateSkipsUpToDateWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: skipUpToDateWorkspaces, + count: skipUpToDateWorkspaces.length, + }); + spyOn(API, "updateWorkspace").mockResolvedValue(MockWorkspaceBuild); + spyOn(API, "getTemplateVersion").mockResolvedValue(MockTemplateVersion); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2", "3", "4"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /Update/ })); + + const modal = await body.findByRole("dialog", { name: /Review Updates/i }); + await user.click( + within(modal).getByRole("checkbox", { + name: /I acknowledge these risks\./, + }), + ); + await user.click(within(modal).getByRole("button", { name: /Update/ })); + + await waitFor(() => expect(API.updateWorkspace).toHaveBeenCalledTimes(2)); + expect(API.updateWorkspace).toHaveBeenCalledWith( + skipUpToDateWorkspaces[2], + [], + false, + ); + expect(API.updateWorkspace).toHaveBeenCalledWith( + skipUpToDateWorkspaces[3], + [], + false, + ); + }, +}; + +const updateRunningWorkspaces: Workspace[] = [ + { ...MockRunningOutdatedWorkspace, id: "1" }, + { ...MockOutdatedWorkspace, id: "2" }, + { ...MockOutdatedWorkspace, id: "3" }, +]; + +export const BatchUpdateRunningWorkspace: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: updateRunningWorkspaces, + count: updateRunningWorkspaces.length, + }); + spyOn(API, "updateWorkspace").mockResolvedValue(MockWorkspaceBuild); + spyOn(API, "getTemplateVersion").mockResolvedValue(MockTemplateVersion); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2", "3"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /Update/ })); + + const modal = await body.findByRole("dialog", { name: /Review Updates/i }); + await user.click( + within(modal).getByRole("checkbox", { + name: /I acknowledge these risks\./, + }), + ); + await user.click(within(modal).getByRole("button", { name: /Update/ })); + + await waitFor(() => expect(API.updateWorkspace).toHaveBeenCalledTimes(3)); + expect(API.updateWorkspace).toHaveBeenCalledWith( + updateRunningWorkspaces[0], + [], + false, + ); + expect(API.updateWorkspace).toHaveBeenCalledWith( + updateRunningWorkspaces[1], + [], + false, + ); + expect(API.updateWorkspace).toHaveBeenCalledWith( + updateRunningWorkspaces[2], + [], + false, + ); + }, +}; + +const ignoreDormantWorkspaces: Workspace[] = [ + { ...MockDormantOutdatedWorkspace, id: "1" }, + { ...MockOutdatedWorkspace, id: "2" }, + { ...MockOutdatedWorkspace, id: "3" }, +]; + +export const BatchUpdateIgnoresDormantWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: ignoreDormantWorkspaces, + count: ignoreDormantWorkspaces.length, + }); + spyOn(API, "updateWorkspace").mockResolvedValue(MockWorkspaceBuild); + spyOn(API, "getTemplateVersion").mockResolvedValue(MockTemplateVersion); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2", "3"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /Update/ })); + + const modal = await body.findByRole("dialog", { name: /Review Updates/i }); + await user.click(within(modal).getByRole("button", { name: /Update/ })); + + await waitFor(() => expect(API.updateWorkspace).toHaveBeenCalledTimes(2)); + expect(API.updateWorkspace).toHaveBeenCalledWith( + ignoreDormantWorkspaces[1], + [], + false, + ); + expect(API.updateWorkspace).toHaveBeenCalledWith( + ignoreDormantWorkspaces[2], + [], + false, + ); + }, +}; + +const runningWorkspaces: Workspace[] = [ + { ...MockWorkspace, id: "1" }, + { ...MockWorkspace, id: "2" }, + { ...MockWorkspace, id: "3" }, +]; + +export const StopsOnlySelectedWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: runningWorkspaces, + count: runningWorkspaces.length, + }); + spyOn(API, "stopWorkspace").mockResolvedValue(MockWorkspaceBuild); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /stop/i })); + + await waitFor(() => expect(API.stopWorkspace).toHaveBeenCalledTimes(2)); + expect(API.stopWorkspace).toHaveBeenCalledWith("1"); + expect(API.stopWorkspace).toHaveBeenCalledWith("2"); + }, +}; + +const stoppedWorkspaces: Workspace[] = [ + { ...MockStoppedWorkspace, id: "1" }, + { ...MockStoppedWorkspace, id: "2" }, + { ...MockStoppedWorkspace, id: "3" }, +]; + +export const StartsOnlySelectedWorkspaces: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: stoppedWorkspaces, + count: stoppedWorkspaces.length, + }); + spyOn(API, "startWorkspace").mockResolvedValue(MockWorkspaceBuild); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const body = within(document.body); + const user = userEvent.setup(); + + await selectWorkspaces(canvas, user, ["1", "2"]); + await openBulkActions(canvas, user); + await user.click(await body.findByRole("menuitem", { name: /start/i })); + + await waitFor(() => expect(API.startWorkspace).toHaveBeenCalledTimes(2)); + expect(API.startWorkspace).toHaveBeenCalledWith( + "1", + MockStoppedWorkspace.latest_build.template_version_id, + ); + expect(API.startWorkspace).toHaveBeenCalledWith( + "2", + MockStoppedWorkspace.latest_build.template_version_id, + ); + }, +}; + +const appHealthStatuses: [WorkspaceAppHealth, boolean][] = [ + ["healthy", true], + ["disabled", true], + ["unhealthy", false], + ["initializing", false], +]; + +const appsWorkspace: Workspace = { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + status: "running", + resources: [ + { + ...MockWorkspace.latest_build.resources[0], + agents: [ + { + ...MockWorkspaceAgent, + display_apps: [], + apps: [ + ...appHealthStatuses.map(([health]) => ({ + ...MockWorkspaceApp, + id: `${health}-app`, + slug: `${health}-app`, + display_name: `${health} App`, + health, + hidden: false, + })), + { + ...MockWorkspaceApp, + id: "hidden-app", + slug: "hidden-app", + display_name: "Hidden App", + health: "healthy", + hidden: true, + }, + ], + }, + ], + }, + ], + }, +}; + +export const FiltersWorkspaceAppsByHealthAndVisibility: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: [appsWorkspace], + count: 1, + }); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByRole("link", { + name: (name) => name.toLowerCase().includes("healthy app"), + }); + + for (const [health, shouldBeVisible] of appHealthStatuses) { + const link = canvas.queryByRole("link", { + name: (name) => name.toLowerCase().includes(`${health} app`), + }); + if (shouldBeVisible) { + expect(link).toBeInTheDocument(); + } else { + expect(link).not.toBeInTheDocument(); + } + } + + expect( + canvas.queryByRole("link", { + name: (name) => name.toLowerCase().includes("hidden app"), + }), + ).not.toBeInTheDocument(); + }, +}; + +export const OutdatedStoppedAlwaysUpdateHidesStartButton: Story = { + beforeEach: () => { + spyOn(API, "getWorkspaces").mockResolvedValue({ + workspaces: [MockOutdatedStoppedWorkspaceAlwaysUpdate], + count: 1, + }); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByRole("button", { name: "Update and start workspace" }); + expect( + canvas.queryByRole("button", { name: "Start workspace" }), + ).not.toBeInTheDocument(); + }, +}; + +async function selectWorkspaces( + canvas: ReturnType, + user: ReturnType, + ids: string[], +) { + for (const id of ids) { + await user.click(await canvas.findByTestId(`checkbox-${id}`)); + } +} + +async function openBulkActions( + canvas: ReturnType, + user: ReturnType, +) { + await user.click( + await canvas.findByRole("button", { name: /bulk actions/i }), + ); +} diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx deleted file mode 100644 index e10d88521b..0000000000 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ /dev/null @@ -1,415 +0,0 @@ -import { screen, waitFor, within } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { HttpResponse, http } from "msw"; -import { API } from "#/api/api"; -import type { - Workspace, - WorkspaceApp, - WorkspaceAppHealth, - WorkspacesResponse, -} from "#/api/typesGenerated"; -import { - MockDormantOutdatedWorkspace, - MockDormantWorkspace, - MockOutdatedStoppedWorkspaceAlwaysUpdate, - MockOutdatedWorkspace, - MockRunningOutdatedWorkspace, - MockStoppedWorkspace, - MockWorkspace, - MockWorkspaceAgent, - MockWorkspaceApp, - MockWorkspacesResponse, -} from "#/testHelpers/entities"; -import { - renderWithAuth, - waitForLoaderToBeRemoved, -} from "#/testHelpers/renderHelpers"; -import { server } from "#/testHelpers/server"; -import * as CreateDayString from "#/utils/createDayString"; -import WorkspacesPage from "./WorkspacesPage"; - -describe("WorkspacesPage", () => { - beforeEach(() => { - // Mocking the dayjs module within the createDayString file - const mock = vi.spyOn(CreateDayString, "createDayString"); - mock.mockImplementation(() => "a minute ago"); - }); - - it("renders an empty workspaces page", async () => { - server.use( - http.get("/api/v2/workspaces", async () => { - return HttpResponse.json({ - workspaces: [], - count: 0, - }); - }), - ); - - renderWithAuth(); - await screen.findByRole("heading", { name: /Create a workspace/ }); - }); - - it("renders a filled workspaces page", async () => { - renderWithAuth(); - await screen.findByText(`${MockWorkspace.name}1`); - const templateDisplayNames = await screen.findAllByText( - `${MockWorkspace.template_display_name}`, - ); - expect(templateDisplayNames).toHaveLength(MockWorkspacesResponse.count); - }); - - it("deletes only the selected workspaces", async () => { - const workspaces = [ - { ...MockWorkspace, id: "1" }, - { ...MockWorkspace, id: "2" }, - { ...MockWorkspace, id: "3" }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - const deleteWorkspace = vi.spyOn(API, "deleteWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - await user.click(getWorkspaceCheckbox(workspaces[0])); - await user.click(getWorkspaceCheckbox(workspaces[1])); - - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const deleteButton = await screen.findByText(/delete/i); - await user.click(deleteButton); - - // The button changes its text, and advances the content of the modal, - // but it is technically the same button being clicked 3 times. - const confirmButton = await screen.findByTestId("confirm-button"); - await user.click(confirmButton); - await user.click(confirmButton); - await user.click(confirmButton); - - await waitFor(() => { - expect(deleteWorkspace).toHaveBeenCalledTimes(2); - }); - expect(deleteWorkspace).toHaveBeenCalledWith(workspaces[0].id); - expect(deleteWorkspace).toHaveBeenCalledWith(workspaces[1].id); - }); - - describe("batch updates", () => { - it("skips up-to-date workspaces after confirming update", async () => { - const workspaces: readonly Workspace[] = [ - // Not outdated but running; should have no warning - { ...MockWorkspace, id: "1" }, - // Dormant; no warning - { ...MockDormantWorkspace, id: "2" }, - // Out of date but not running; no warning - { ...MockOutdatedWorkspace, id: "3" }, - // Out of date but running; should issue warning - { - ...MockOutdatedWorkspace, - id: "4", - latest_build: { - ...MockOutdatedWorkspace.latest_build, - status: "running", - }, - }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - - const updateWorkspace = vi.spyOn(API, "updateWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - for (const workspace of workspaces) { - await user.click(getWorkspaceCheckbox(workspace)); - } - - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const dropdownItem = await screen.findByRole("menuitem", { - name: /Update/, - }); - await user.click(dropdownItem); - - const modal = await screen.findByRole("dialog", { - name: /Review Updates/i, - }); - const confirmCheckbox = within(modal).getByRole("checkbox", { - name: /I acknowledge these risks\./, - }); - await user.click(confirmCheckbox); - const updateModalButton = within(modal).getByRole("button", { - name: /Update/, - }); - await user.click(updateModalButton); - - // `workspaces[0]` was up-to-date, and running - // `workspaces[1]` was dormant - await waitFor(() => expect(updateWorkspace).toHaveBeenCalledTimes(2)); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); - }); - - it("lets user update a running workspace (after user goes through warning)", async () => { - const workspaces: readonly Workspace[] = [ - { ...MockRunningOutdatedWorkspace, id: "1" }, - { ...MockOutdatedWorkspace, id: "2" }, - { ...MockOutdatedWorkspace, id: "3" }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - - const updateWorkspace = vi.spyOn(API, "updateWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - for (const workspace of workspaces) { - await user.click(getWorkspaceCheckbox(workspace)); - } - - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const dropdownItem = await screen.findByRole("menuitem", { - name: /Update/, - }); - await user.click(dropdownItem); - - const modal = await screen.findByRole("dialog", { - name: /Review Updates/i, - }); - const confirmCheckbox = within(modal).getByRole("checkbox", { - name: /I acknowledge these risks\./, - }); - await user.click(confirmCheckbox); - const updateModalButton = within(modal).getByRole("button", { - name: /Update/, - }); - await user.click(updateModalButton); - - await waitFor(() => expect(updateWorkspace).toHaveBeenCalledTimes(3)); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); - }); - - it("warns about and ignores dormant workspaces", async () => { - const workspaces = [ - { ...MockDormantOutdatedWorkspace, id: "1" }, - { ...MockOutdatedWorkspace, id: "2" }, - { ...MockOutdatedWorkspace, id: "3" }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - const updateWorkspace = vi.spyOn(API, "updateWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - for (const workspace of workspaces) { - await user.click(getWorkspaceCheckbox(workspace)); - } - - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const dropdownItem = await screen.findByRole("menuitem", { - name: /Update/, - }); - await user.click(dropdownItem); - - const modal = await screen.findByRole("dialog", { - name: /Review Updates/i, - }); - const updateModalButton = within(modal).getByRole("button", { - name: /Update/, - }); - await user.click(updateModalButton); - - // `workspaces[0]` was dormant - await waitFor(() => expect(updateWorkspace).toHaveBeenCalledTimes(2)); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); - }); - }); - - it("stops only the running and selected workspaces", async () => { - const workspaces = [ - { ...MockWorkspace, id: "1" }, - { ...MockWorkspace, id: "2" }, - { ...MockWorkspace, id: "3" }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - const stopWorkspace = vi.spyOn(API, "stopWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - await user.click(getWorkspaceCheckbox(workspaces[0])); - await user.click(getWorkspaceCheckbox(workspaces[1])); - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const stopButton = await screen.findByRole("menuitem", { name: /stop/i }); - await user.click(stopButton); - - await waitFor(() => { - expect(stopWorkspace).toHaveBeenCalledTimes(2); - }); - expect(stopWorkspace).toHaveBeenCalledWith(workspaces[0].id); - expect(stopWorkspace).toHaveBeenCalledWith(workspaces[1].id); - }); - - it("starts only the stopped and selected workspaces", async () => { - const workspaces = [ - { ...MockStoppedWorkspace, id: "1" }, - { ...MockStoppedWorkspace, id: "2" }, - { ...MockStoppedWorkspace, id: "3" }, - ]; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces, - count: workspaces.length, - }); - const startWorkspace = vi.spyOn(API, "startWorkspace"); - const user = userEvent.setup(); - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - await user.click(getWorkspaceCheckbox(workspaces[0])); - await user.click(getWorkspaceCheckbox(workspaces[1])); - await user.click(screen.getByRole("button", { name: /bulk actions/i })); - const startButton = await screen.findByRole("menuitem", { name: /start/i }); - await user.click(startButton); - - await waitFor(() => { - expect(startWorkspace).toHaveBeenCalledTimes(2); - }); - expect(startWorkspace).toHaveBeenCalledWith( - workspaces[0].id, - MockStoppedWorkspace.latest_build.template_version_id, - ); - expect(startWorkspace).toHaveBeenCalledWith( - workspaces[1].id, - MockStoppedWorkspace.latest_build.template_version_id, - ); - }); -}); - -const getWorkspaceCheckbox = (workspace: Workspace) => { - return screen.getByTestId(`checkbox-${workspace.id}`); -}; - -describe("WorkspaceApps filtering", () => { - it.each<[WorkspaceAppHealth, boolean]>([ - ["healthy", true], - ["disabled", true], - ["unhealthy", false], - ["initializing", false], - ])("apps with '%s' health status should be shown: %s", async (health, shouldBeVisible) => { - const app: WorkspaceApp = { - ...MockWorkspaceApp, - id: `${health}-app`, - display_name: `${health} App`, - health, - hidden: false, - }; - const workspace: Workspace = { - ...MockWorkspace, - latest_build: { - ...MockWorkspace.latest_build, - status: "running", - resources: [ - { - ...MockWorkspace.latest_build.resources[0], - agents: [ - { - ...MockWorkspaceAgent, - apps: [app], - }, - ], - }, - ], - }, - }; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces: [workspace], - count: 1, - }); - - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - const appLink = screen.queryByRole("link", { - name: (name) => - name.toLowerCase().includes(app.display_name!.toLowerCase()), - }); - if (shouldBeVisible) { - expect(appLink).toBeInTheDocument(); - } else { - expect(appLink).not.toBeInTheDocument(); - } - }); - - it("does not show hidden apps regardless of health status", async () => { - const hiddenApp: WorkspaceApp = { - ...MockWorkspaceApp, - id: "hidden-app", - display_name: "Hidden App", - health: "healthy", - hidden: true, - }; - const workspace: Workspace = { - ...MockWorkspace, - latest_build: { - ...MockWorkspace.latest_build, - status: "running", - resources: [ - { - ...MockWorkspace.latest_build.resources[0], - agents: [ - { - ...MockWorkspaceAgent, - apps: [hiddenApp], - }, - ], - }, - ], - }, - }; - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces: [workspace], - count: 1, - }); - - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - expect( - screen.queryByRole("link", { - name: (name) => - name.toLowerCase().includes(hiddenApp.display_name!.toLowerCase()), - }), - ).not.toBeInTheDocument(); - }); - - it("does not show start button for stopped outdated workspace with automatic_updates always", async () => { - vi.spyOn(API, "getWorkspaces").mockResolvedValue({ - workspaces: [MockOutdatedStoppedWorkspaceAlwaysUpdate], - count: 1, - }); - - renderWithAuth(); - await waitForLoaderToBeRemoved(); - - expect( - screen.queryByRole("button", { name: "Start workspace" }), - ).not.toBeInTheDocument(); - expect( - screen.getByRole("button", { name: "Update and start workspace" }), - ).toBeInTheDocument(); - }); -});