diff --git a/packages/dashboard-frontend/src/pages/Loader/Workspace/__mocks__/index.tsx b/packages/dashboard-frontend/src/pages/Loader/Workspace/__mocks__/index.tsx index 26159fdf39..310627558b 100644 --- a/packages/dashboard-frontend/src/pages/Loader/Workspace/__mocks__/index.tsx +++ b/packages/dashboard-frontend/src/pages/Loader/Workspace/__mocks__/index.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { Props, State } from '..'; -export class WorkspaceLoaderPage extends React.PureComponent { +export default class WorkspaceLoaderPage extends React.PureComponent { render(): React.ReactNode { const { alertItem, currentStepId, steps, onRestart } = this.props; const wizardSteps = steps.map(step => ( diff --git a/packages/dashboard-frontend/src/pages/Loader/Workspace/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/pages/Loader/Workspace/__tests__/index.spec.tsx index cd45f0f1b8..cfe39fccf2 100644 --- a/packages/dashboard-frontend/src/pages/Loader/Workspace/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/pages/Loader/Workspace/__tests__/index.spec.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AlertVariant } from '@patternfly/react-core'; import WorkspaceLoaderPage from '..'; @@ -25,6 +25,10 @@ import getComponentRenderer from '../../../../services/__mocks__/getComponentRen import { Provider } from 'react-redux'; import { Store } from 'redux'; import { FakeStoreBuilder } from '../../../../store/__mocks__/storeBuilder'; +import { DevWorkspaceBuilder } from '../../../../store/__mocks__/devWorkspaceBuilder'; +import { Workspace, WorkspaceAdapter } from '../../../../services/workspace-adapter'; +import { RunningWorkspacesExceededError } from '../../../../store/Workspaces/devWorkspaces'; +import { DevWorkspace } from '../../../../services/devfileApi/devWorkspace'; jest.mock('react-tooltip', () => { return function DummyTooltip(): React.ReactElement { @@ -99,6 +103,160 @@ describe('Workspace loader page', () => { expect(activeTab?.textContent).toEqual(LoaderTab.Logs.toString()); }); + + describe('workspaces runningLimit has been reached', () => { + let startedWorkspace1: DevWorkspace; + let startedWorkspace2: DevWorkspace; + let stoppedWorkspace: DevWorkspace; + let currentWorkspace: DevWorkspace; + + beforeEach(() => { + const namespace = 'user-che'; + + startedWorkspace1 = new DevWorkspaceBuilder() + .withName('bash') + .withNamespace(namespace) + .withMetadata({ uid: 'uid-bash' }) + .build(); + startedWorkspace1.spec.started = true; + + startedWorkspace2 = new DevWorkspaceBuilder() + .withName('python-hello-world') + .withNamespace(namespace) + .withMetadata({ uid: 'uid-python-hello-world' }) + .build(); + startedWorkspace2.spec.started = true; + + stoppedWorkspace = new DevWorkspaceBuilder() + .withName('nodejs-web-app') + .withNamespace(namespace) + .withMetadata({ uid: 'uid-nodejs-web-app' }) + .build(); + + currentWorkspace = new DevWorkspaceBuilder() + .withName('golang-example') + .withNamespace(namespace) + .withMetadata({ uid: 'uid-golang-example' }) + .build(); + }); + + it('should show options if workspace running limit has been reached, and there is only one running workspace', () => { + const alertItem: AlertItem = { + key: 'alert-id', + title: + 'Failed to start the workspace golang-example, reason: You are not allowed to start more workspaces.', + variant: AlertVariant.danger, + error: new RunningWorkspacesExceededError('You are not allowed to start more workspaces.'), + }; + + const store = new FakeStoreBuilder() + .withDevWorkspaces({ + workspaces: [startedWorkspace1, stoppedWorkspace, currentWorkspace], + }) + .build(); + + renderComponent( + { steps: steps.values, alertItem, workspace: new WorkspaceAdapter(currentWorkspace) }, + store, + ); + + const alert = screen.getByTestId('action-links'); + const buttons = within(alert).getAllByRole('button'); + expect(buttons.length).toEqual(2); + expect(buttons[0].textContent).toEqual('Switch to running workspace (bash)'); + expect(buttons[1].textContent).toEqual( + 'Close running workspace (bash) and restart golang-example', + ); + }); + + it('should switch to running workspace when there is only one running workspace and button clicked', async () => { + createWindowMock({ + href: 'https://che-host/dashboard/#/ide/user-che/golang-example', + origin: 'https://che-host', + }); + window.open = jest.fn(); + window.location.href = 'asdfsadf'; + + const alertItem: AlertItem = { + key: 'alert-id', + title: + 'Failed to start the workspace golang-example, reason: You are not allowed to start more workspaces.', + variant: AlertVariant.danger, + error: new RunningWorkspacesExceededError('You are not allowed to start more workspaces.'), + }; + + const store = new FakeStoreBuilder() + .withDevWorkspaces({ + workspaces: [startedWorkspace1, stoppedWorkspace, currentWorkspace], + }) + .build(); + + renderComponent( + { steps: steps.values, alertItem, workspace: new WorkspaceAdapter(currentWorkspace) }, + store, + ); + + const alert = screen.getByTestId('action-links'); + const buttons = within(alert).getAllByRole('button'); + expect(buttons.length).toEqual(2); + expect(buttons[0].textContent).toEqual('Switch to running workspace (bash)'); + + userEvent.click(buttons[0]); + await waitFor(() => + expect(window.open).toHaveBeenCalledWith( + 'https://che-host/dashboard/#/ide/user-che/bash', + 'uid-bash', + ), + ); + }); + + it('should show options if workspace running limit has been reached, and there more than one running workspaces', async () => { + createWindowMock({ origin: 'https://che-host' }); + + const alertItem: AlertItem = { + key: 'alert-id', + title: + 'Failed to start the workspace golang-example, reason: You are not allowed to start more workspaces.', + variant: AlertVariant.danger, + error: new RunningWorkspacesExceededError('You are not allowed to start more workspaces.'), + }; + + const store = new FakeStoreBuilder() + .withDevWorkspaces({ + workspaces: [startedWorkspace1, startedWorkspace2, stoppedWorkspace, currentWorkspace], + }) + .build(); + + const spyWindowLocation = jest.spyOn(window.location, 'href', 'set'); + + renderComponent( + { steps: steps.values, alertItem, workspace: new WorkspaceAdapter(currentWorkspace) }, + store, + ); + + const button = screen.getByRole('button', { name: 'Return to dashboard' }); + userEvent.click(button); + await waitFor(() => + expect(spyWindowLocation).toHaveBeenCalledWith('https://che-host/dashboard/'), + ); + }); + }); + + function createWindowMock(location: { href?: string; origin?: string }) { + delete (window as any).location; + (window.location as any) = { + origin: location.origin, + }; + Object.defineProperty(window.location, 'href', { + set: () => { + // no-op + }, + get: () => { + return location.href; + }, + configurable: true, + }); + } }); function getComponent( @@ -106,6 +264,7 @@ function getComponent( steps: LoaderStep[]; initialTab?: keyof typeof LoaderTab; alertItem?: AlertItem; + workspace?: Workspace; }, store?: Store, ): React.ReactElement { @@ -121,7 +280,7 @@ function getComponent( onRestart={mockOnWorkspaceRestart} steps={props.steps} tabParam={'Progress'} - workspace={undefined} + workspace={props.workspace} /> );