From d090004b8c192ae1c7a7768e64186942a9ea4112 Mon Sep 17 00:00:00 2001 From: Miguel Silva <108079391+msilva-broad@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:36:51 -0400 Subject: [PATCH 1/2] UIE-204 Narrow Ajax Usage pt4 (#5121) --- .../workspace/workflows/WorkflowView.test.js | 22 +--- src/workspaces/common/state/useAppPolling.ts | 6 +- .../useCloningWorkspaceNotifications.test.tsx | 53 +++----- .../state/useCloudEnvironmentPolling.test.ts | 14 +- .../state/useCloudEnvironmentPolling.ts | 4 +- .../common/state/useWorkspace.test.ts | 1 - .../common/state/useWorkspaceById.test.ts | 30 +---- .../common/state/useWorkspaceById.ts | 4 +- .../common/state/useWorkspaceDetails.ts | 4 +- .../common/state/useWorkspaceStatePolling.ts | 6 +- .../container/WorkspaceContainer.test.ts | 70 +++++----- src/workspaces/list/WorkspacesList.test.ts | 120 ++++++++---------- src/workspaces/list/WorkspacesList.ts | 4 +- 13 files changed, 133 insertions(+), 205 deletions(-) diff --git a/src/pages/workspaces/workspace/workflows/WorkflowView.test.js b/src/pages/workspaces/workspace/workflows/WorkflowView.test.js index 50ba33a0a6..8681a12f68 100644 --- a/src/pages/workspaces/workspace/workflows/WorkflowView.test.js +++ b/src/pages/workspaces/workspace/workflows/WorkflowView.test.js @@ -2,7 +2,9 @@ import { act, fireEvent, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { h } from 'react-hyperscript-helpers'; import { Ajax } from 'src/libs/ajax'; +import { Apps } from 'src/libs/ajax/leonardo/Apps'; import { leoDiskProvider } from 'src/libs/ajax/leonardo/providers/LeoDiskProvider'; +import { Runtimes } from 'src/libs/ajax/leonardo/Runtimes'; import { Methods } from 'src/libs/ajax/methods/Methods'; import { Workspaces } from 'src/libs/ajax/workspaces/Workspaces'; import { getLocalPref, setLocalPref } from 'src/libs/prefs'; @@ -16,6 +18,8 @@ jest.mock('src/libs/ajax'); jest.mock('src/libs/ajax/Dockstore'); jest.mock('src/libs/ajax/GoogleStorage'); +jest.mock('src/libs/ajax/leonardo/Apps'); +jest.mock('src/libs/ajax/leonardo/Runtimes'); jest.mock('src/libs/ajax/methods/Methods'); jest.mock('src/libs/ajax/Metrics'); jest.mock('src/libs/ajax/workspaces/Workspaces'); @@ -247,8 +251,6 @@ describe('Workflow View (GCP)', () => { const mockLaunchResponse = jest.fn(() => Promise.resolve({ submissionId: 'abc123', ...initializedGoogleWorkspace.workspaceId })); const mockDefaultAjax = () => { - asMockedFn(leoDiskProvider.list).mockImplementation(jest.fn()); - Methods.mockReturnValue({ list: jest.fn(() => Promise.resolve(methodList)), method: () => ({ @@ -284,19 +286,9 @@ describe('Workflow View (GCP)', () => { }), }), }); - Ajax.mockImplementation(() => ({ - Disks: { - disksV1: () => ({ - list: jest.fn(), - }), - }, - Runtimes: { - listV2: jest.fn(), - }, - Apps: { - list: jest.fn().mockReturnValue([]), - }, - })); + Apps.mockReturnValue({ list: jest.fn().mockReturnValue([]) }); + Runtimes.mockReturnValue({ listV2: jest.fn() }); + asMockedFn(leoDiskProvider.list).mockImplementation(jest.fn()); }; it('view workflow in workspace from mock import', async () => { diff --git a/src/workspaces/common/state/useAppPolling.ts b/src/workspaces/common/state/useAppPolling.ts index 4b9ffc94ef..adcdca7d3b 100644 --- a/src/workspaces/common/state/useAppPolling.ts +++ b/src/workspaces/common/state/useAppPolling.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { Ajax } from 'src/libs/ajax'; +import { Apps } from 'src/libs/ajax/leonardo/Apps'; import { ListAppItem } from 'src/libs/ajax/leonardo/models/app-models'; import { withErrorIgnoring, withErrorReporting } from 'src/libs/error'; import { InitializedWorkspaceWrapper as Workspace } from 'src/workspaces/common/state/useWorkspace'; @@ -30,14 +30,14 @@ export const useAppPolling = (name: string, namespace: string, workspace?: Works try { const newGoogleApps = workspace?.workspaceInitialized && isGoogleWorkspace(workspace) - ? await Ajax(signal).Apps.list(workspace.workspace.googleProject, { + ? await Apps(signal).list(workspace.workspace.googleProject, { role: 'creator', saturnWorkspaceName: workspace.workspace.name, }) : []; const newAzureApps = workspace?.workspaceInitialized && isAzureWorkspace(workspace) - ? await Ajax(signal).Apps.listAppsV2(workspace.workspace.workspaceId) + ? await Apps(signal).listAppsV2(workspace.workspace.workspaceId) : []; const combinedNewApps = [...newGoogleApps, ...newAzureApps]; diff --git a/src/workspaces/common/state/useCloningWorkspaceNotifications.test.tsx b/src/workspaces/common/state/useCloningWorkspaceNotifications.test.tsx index b35bf85b01..02f95bd078 100644 --- a/src/workspaces/common/state/useCloningWorkspaceNotifications.test.tsx +++ b/src/workspaces/common/state/useCloningWorkspaceNotifications.test.tsx @@ -2,10 +2,10 @@ import { DeepPartial } from '@terra-ui-packages/core-utils'; import { NotificationType } from '@terra-ui-packages/notifications'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { Ajax } from 'src/libs/ajax'; +import { WorkspaceContract, Workspaces, WorkspacesAjaxContract } from 'src/libs/ajax/workspaces/Workspaces'; import { clearNotification, notify } from 'src/libs/notifications'; import { cloningWorkspacesStore } from 'src/libs/state'; -import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; +import { asMockedFn, partial, renderWithAppContexts as render } from 'src/testing/test-utils'; import { defaultAzureWorkspace } from 'src/testing/workspace-fixtures'; import { notifyNewWorkspaceClone, @@ -14,15 +14,7 @@ import { import { WORKSPACE_UPDATE_POLLING_INTERVAL } from 'src/workspaces/common/state/useWorkspaceStatePolling'; import { WorkspaceInfo, WorkspaceState, WorkspaceWrapper } from 'src/workspaces/utils'; -type AjaxContract = ReturnType; -type AjaxWorkspacesContract = AjaxContract['Workspaces']; - -jest.mock('src/libs/ajax', (): typeof import('src/libs/ajax') => { - return { - ...jest.requireActual('src/libs/ajax'), - Ajax: jest.fn(), - }; -}); +jest.mock('src/libs/ajax/workspaces/Workspaces'); type NotificationExports = typeof import('src/libs/notifications'); jest.mock( @@ -95,15 +87,11 @@ describe('useCloningWorkspaceNotifications', () => { }, }; const mockDetailsFn = jest.fn().mockResolvedValue(update); - const mockAjax: DeepPartial = { - Workspaces: { - workspace: () => - ({ - details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue( + partial({ + workspace: () => partial({ details: mockDetailsFn }), + }) + ); // Act render(); @@ -136,12 +124,11 @@ describe('useCloningWorkspaceNotifications', () => { }, }; const mockDetailsFn = jest.fn().mockImplementation(() => Promise.resolve(update)); - const mockAjax: DeepPartial = { - Workspaces: { - workspace: () => ({ details: mockDetailsFn }), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue( + partial({ + workspace: () => partial({ details: mockDetailsFn }), + }) + ); jest.useFakeTimers(); // Act render(); @@ -170,15 +157,11 @@ describe('useCloningWorkspaceNotifications', () => { }, }; const mockDetailsFn = jest.fn().mockResolvedValue(update); - const mockAjax: DeepPartial = { - Workspaces: { - workspace: () => - ({ - details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue( + partial({ + workspace: () => partial({ details: mockDetailsFn }), + }) + ); jest.useFakeTimers(); // Act diff --git a/src/workspaces/common/state/useCloudEnvironmentPolling.test.ts b/src/workspaces/common/state/useCloudEnvironmentPolling.test.ts index c849f3714d..95f5e3285b 100644 --- a/src/workspaces/common/state/useCloudEnvironmentPolling.test.ts +++ b/src/workspaces/common/state/useCloudEnvironmentPolling.test.ts @@ -1,17 +1,15 @@ import { generateTestDiskWithGoogleWorkspace } from 'src/analysis/_testData/testData'; -import { Ajax } from 'src/libs/ajax'; import { leoDiskProvider, PersistentDisk } from 'src/libs/ajax/leonardo/providers/LeoDiskProvider'; -import { RuntimesAjaxContract } from 'src/libs/ajax/leonardo/Runtimes'; -import { asMockedFn, renderHookInAct } from 'src/testing/test-utils'; +import { Runtimes, RuntimesAjaxContract } from 'src/libs/ajax/leonardo/Runtimes'; +import { asMockedFn, partial, renderHookInAct } from 'src/testing/test-utils'; import { defaultGoogleWorkspace, defaultInitializedGoogleWorkspace } from 'src/testing/workspace-fixtures'; import { useCloudEnvironmentPolling } from './useCloudEnvironmentPolling'; -jest.mock('src/libs/ajax'); +jest.mock('src/libs/ajax/leonardo/Runtimes'); jest.mock('src/libs/ajax/leonardo/providers/LeoDiskProvider'); // This code will be needed when we mock and test the runtime methods -type AjaxContract = ReturnType; type RuntimesNeeds = Pick; interface RuntimeMockNeeds { topLevel: RuntimesNeeds; @@ -30,9 +28,9 @@ const mockAjaxNeeds = (): AjaxMockNeeds => { const partialRuntimes: RuntimesNeeds = { listV2: jest.fn(), }; - const mockRuntimes = partialRuntimes as RuntimesAjaxContract; + const mockRuntimes = partial(partialRuntimes); - asMockedFn(Ajax).mockReturnValue({ Runtimes: mockRuntimes } as AjaxContract); + asMockedFn(Runtimes).mockReturnValue(mockRuntimes); return { Runtimes: { @@ -69,7 +67,7 @@ describe('useCloudEnvironmentPolling', () => { // Assert // Runtimes and disk ajax calls - expect(Ajax).toBeCalledTimes(1); + expect(Runtimes).toBeCalledTimes(1); expect(leoDiskProvider.list).toBeCalledTimes(1); expect(result.current.persistentDisks).toEqual([persistentDisk]); expect(result.current.appDataDisks).toEqual([appDisk]); diff --git a/src/workspaces/common/state/useCloudEnvironmentPolling.ts b/src/workspaces/common/state/useCloudEnvironmentPolling.ts index 0c3a3ba6fd..99d12b946d 100644 --- a/src/workspaces/common/state/useCloudEnvironmentPolling.ts +++ b/src/workspaces/common/state/useCloudEnvironmentPolling.ts @@ -2,9 +2,9 @@ import _ from 'lodash/fp'; import { useEffect, useRef, useState } from 'react'; import { getDiskAppType } from 'src/analysis/utils/app-utils'; import { getConvertedRuntimeStatus, getCurrentRuntime } from 'src/analysis/utils/runtime-utils'; -import { Ajax } from 'src/libs/ajax'; import { ListRuntimeItem } from 'src/libs/ajax/leonardo/models/runtime-models'; import { leoDiskProvider, PersistentDisk } from 'src/libs/ajax/leonardo/providers/LeoDiskProvider'; +import { Runtimes } from 'src/libs/ajax/leonardo/Runtimes'; import { withErrorIgnoring, withErrorReporting } from 'src/libs/error'; import { InitializedWorkspaceWrapper as Workspace } from 'src/workspaces/common/state/useWorkspace'; @@ -58,7 +58,7 @@ export const useCloudEnvironmentPolling = ( }, { signal: controller.current.signal } ), - Ajax(controller.current.signal).Runtimes.listV2(cloudEnvFilters), + Runtimes(controller.current.signal).listV2(cloudEnvFilters), ]); setRuntimes(newRuntimes); diff --git a/src/workspaces/common/state/useWorkspace.test.ts b/src/workspaces/common/state/useWorkspace.test.ts index 05d11ab227..9b9d872da3 100644 --- a/src/workspaces/common/state/useWorkspace.test.ts +++ b/src/workspaces/common/state/useWorkspace.test.ts @@ -17,7 +17,6 @@ import { } from 'src/workspaces/common/state/useWorkspace'; jest.mock('src/libs/ajax/AzureStorage'); - jest.mock('src/libs/ajax/Metrics'); jest.mock('src/libs/ajax/workspaces/Workspaces'); diff --git a/src/workspaces/common/state/useWorkspaceById.test.ts b/src/workspaces/common/state/useWorkspaceById.test.ts index af6124b8dd..3976d53868 100644 --- a/src/workspaces/common/state/useWorkspaceById.test.ts +++ b/src/workspaces/common/state/useWorkspaceById.test.ts @@ -1,31 +1,16 @@ -import { DeepPartial } from '@terra-ui-packages/core-utils'; import { act } from '@testing-library/react'; -import { Ajax } from 'src/libs/ajax'; -import { asMockedFn, renderHookInAct } from 'src/testing/test-utils'; +import { Workspaces, WorkspacesAjaxContract } from 'src/libs/ajax/workspaces/Workspaces'; +import { asMockedFn, partial, renderHookInAct } from 'src/testing/test-utils'; import { useWorkspaceById } from './useWorkspaceById'; -type AjaxExports = typeof import('src/libs/ajax'); -jest.mock('src/libs/ajax', (): AjaxExports => { - const actual = jest.requireActual('src/libs/ajax'); - return { - ...actual, - Ajax: jest.fn(), - }; -}); - -type AjaxContract = ReturnType; +jest.mock('src/libs/ajax/workspaces/Workspaces'); describe('useWorkspaceById', () => { it('fetches a workspace by ID', async () => { // Arrange const getWorkspaceById = jest.fn().mockResolvedValue({}); - const mockAjax: DeepPartial = { - Workspaces: { - getById: getWorkspaceById, - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue(partial({ getById: getWorkspaceById })); // Act await renderHookInAct(() => useWorkspaceById('test-workspace')); @@ -37,12 +22,7 @@ describe('useWorkspaceById', () => { it('fetches workspace when ID changes', async () => { // Arrange const getWorkspaceById = jest.fn().mockResolvedValue({}); - const mockAjax: DeepPartial = { - Workspaces: { - getById: getWorkspaceById, - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue(partial({ getById: getWorkspaceById })); const { rerender } = await renderHookInAct(useWorkspaceById, { initialProps: 'workspace-1' }); diff --git a/src/workspaces/common/state/useWorkspaceById.ts b/src/workspaces/common/state/useWorkspaceById.ts index 90a39174d8..6c9f35be84 100644 --- a/src/workspaces/common/state/useWorkspaceById.ts +++ b/src/workspaces/common/state/useWorkspaceById.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Ajax } from 'src/libs/ajax'; +import { Workspaces } from 'src/libs/ajax/workspaces/Workspaces'; import { useCancellation } from 'src/libs/react-utils'; import { WorkspaceWrapper } from 'src/workspaces/utils'; @@ -27,7 +27,7 @@ export const useWorkspaceById = (workspaceId: string, fields?: string[]): UseWor const loadWorkspace = async () => { setState({ status: 'Loading', workspace: null }); try { - const workspace = await Ajax(signal).Workspaces.getById(workspaceId, fields); + const workspace = await Workspaces(signal).getById(workspaceId, fields); setState({ status: 'Ready', workspace }); } catch (error: unknown) { setState({ status: 'Error', workspace: null, error }); diff --git a/src/workspaces/common/state/useWorkspaceDetails.ts b/src/workspaces/common/state/useWorkspaceDetails.ts index dbeed76406..d1c87f31d0 100644 --- a/src/workspaces/common/state/useWorkspaceDetails.ts +++ b/src/workspaces/common/state/useWorkspaceDetails.ts @@ -1,6 +1,6 @@ import _ from 'lodash/fp'; import { useState } from 'react'; -import { Ajax } from 'src/libs/ajax'; +import { Workspaces } from 'src/libs/ajax/workspaces/Workspaces'; import { withErrorReporting } from 'src/libs/error'; import { useCancellation, useOnMount } from 'src/libs/react-utils'; import * as Utils from 'src/libs/utils'; @@ -18,7 +18,7 @@ export const useWorkspaceDetails = (workspaceName: { namespace: string; name: st withErrorReporting('Error loading workspace details'), Utils.withBusyState(setLoading) )(async () => { - const ws: Workspace = await Ajax(signal).Workspaces.workspace(namespace, name).details(fields); + const ws: Workspace = await Workspaces(signal).workspace(namespace, name).details(fields); setWorkspace(ws); }); diff --git a/src/workspaces/common/state/useWorkspaceStatePolling.ts b/src/workspaces/common/state/useWorkspaceStatePolling.ts index 870bbf6822..51fd9a36a3 100644 --- a/src/workspaces/common/state/useWorkspaceStatePolling.ts +++ b/src/workspaces/common/state/useWorkspaceStatePolling.ts @@ -1,7 +1,7 @@ import { LoadedState } from '@terra-ui-packages/core-utils'; import _ from 'lodash/fp'; import { useEffect, useRef } from 'react'; -import { Ajax } from 'src/libs/ajax'; +import { Workspaces } from 'src/libs/ajax/workspaces/Workspaces'; import { workspacesStore, workspaceStore } from 'src/libs/state'; import { pollWithCancellation } from 'src/libs/utils'; import { BaseWorkspaceInfo, WorkspaceInfo, WorkspaceState, WorkspaceWrapper as Workspace } from 'src/workspaces/utils'; @@ -47,8 +47,8 @@ const checkWorkspaceState = async ( ): Promise => { const startingState = workspace.state; try { - const wsResp: Workspace = await Ajax(signal) - .Workspaces.workspace(workspace.namespace, workspace.name) + const wsResp: Workspace = await Workspaces(signal) + .workspace(workspace.namespace, workspace.name) .details(['workspace.state', 'workspace.errorMessage']); const state = wsResp.workspace.state; diff --git a/src/workspaces/container/WorkspaceContainer.test.ts b/src/workspaces/container/WorkspaceContainer.test.ts index 285f97b1c8..a7fb1cd9d1 100644 --- a/src/workspaces/container/WorkspaceContainer.test.ts +++ b/src/workspaces/container/WorkspaceContainer.test.ts @@ -1,10 +1,9 @@ -import { DeepPartial } from '@terra-ui-packages/core-utils'; import { screen, waitFor, within } from '@testing-library/react'; import { h } from 'react-hyperscript-helpers'; -import { Ajax } from 'src/libs/ajax'; +import { WorkspaceContract, Workspaces, WorkspacesAjaxContract } from 'src/libs/ajax/workspaces/Workspaces'; import { goToPath } from 'src/libs/nav'; import { workspacesStore, workspaceStore } from 'src/libs/state'; -import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; +import { asMockedFn, partial, renderWithAppContexts as render } from 'src/testing/test-utils'; import { defaultAzureWorkspace, defaultGoogleWorkspace } from 'src/testing/workspace-fixtures'; import { InitializedWorkspaceWrapper } from 'src/workspaces/common/state/useWorkspace'; import { WORKSPACE_UPDATE_POLLING_INTERVAL } from 'src/workspaces/common/state/useWorkspaceStatePolling'; @@ -21,16 +20,9 @@ jest.mock( }) ); -type AjaxExports = typeof import('src/libs/ajax'); -type AjaxContract = ReturnType; -type AjaxWorkspacesContract = AjaxContract['Workspaces']; - -jest.mock('src/libs/ajax', (): AjaxExports => { - return { - ...jest.requireActual('src/libs/ajax'), - Ajax: jest.fn(), - }; -}); +jest.mock('src/libs/ajax/AzureStorage'); +jest.mock('src/libs/ajax/Metrics'); +jest.mock('src/libs/ajax/workspaces/Workspaces'); type StateExports = typeof import('src/libs/state'); jest.mock( @@ -128,15 +120,14 @@ describe('WorkspaceContainer', () => { const mockDetailsFn = jest.fn(); - const mockAjax: DeepPartial = { - Workspaces: { + asMockedFn(Workspaces).mockReturnValue( + partial({ workspace: () => - ({ + partial({ details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + }), + }) + ); const workspace: InitializedWorkspaceWrapper = { ...defaultAzureWorkspace, @@ -186,15 +177,14 @@ describe('WorkspaceContainer', () => { }; const mockDetailsFn = jest.fn().mockResolvedValue({ workspace: { state: 'Deleting' } }); - const mockAjax: DeepPartial = { - Workspaces: { + asMockedFn(Workspaces).mockReturnValue( + partial({ workspace: () => - ({ + partial({ details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + }), + }) + ); const props = { namespace: workspace.workspace.namespace, @@ -239,15 +229,14 @@ describe('WorkspaceContainer', () => { // Arrange const mockDetailsFn = jest.fn().mockRejectedValue(new Response(null, { status: 404 })); - const mockAjax: DeepPartial = { - Workspaces: { + asMockedFn(Workspaces).mockReturnValue( + partial({ workspace: () => - ({ + partial({ details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + }), + }) + ); const workspace: InitializedWorkspaceWrapper = { ...defaultAzureWorkspace, @@ -310,15 +299,14 @@ describe('WorkspaceContainer', () => { const errorMessage = 'this is an error message'; const mockDetailsFn = jest.fn().mockResolvedValue({ workspace: { state: 'DeleteFailed', errorMessage } }); - const mockAjax: DeepPartial = { - Workspaces: { + asMockedFn(Workspaces).mockReturnValue( + partial({ workspace: () => - ({ + partial({ details: mockDetailsFn, - } as Partial), - }, - }; - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + }), + }) + ); const workspace: InitializedWorkspaceWrapper = { ...defaultAzureWorkspace, diff --git a/src/workspaces/list/WorkspacesList.test.ts b/src/workspaces/list/WorkspacesList.test.ts index b9a40fb874..36062c933d 100644 --- a/src/workspaces/list/WorkspacesList.test.ts +++ b/src/workspaces/list/WorkspacesList.test.ts @@ -1,8 +1,10 @@ import { DeepPartial } from '@terra-ui-packages/core-utils'; import { act, waitFor } from '@testing-library/react'; import { h } from 'react-hyperscript-helpers'; -import { Ajax, AjaxContract } from 'src/libs/ajax'; -import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils'; +import { FirecloudBucket, FirecloudBucketAjaxContract } from 'src/libs/ajax/firecloud/FirecloudBucket'; +import { Metrics, MetricsContract } from 'src/libs/ajax/Metrics'; +import { WorkspaceContract, Workspaces, WorkspacesAjaxContract } from 'src/libs/ajax/workspaces/Workspaces'; +import { asMockedFn, partial, renderWithAppContexts as render } from 'src/testing/test-utils'; import { defaultAzureWorkspace, defaultGoogleWorkspace } from 'src/testing/workspace-fixtures'; import { useWorkspaces } from 'src/workspaces/common/state/useWorkspaces'; import { WORKSPACE_UPDATE_POLLING_INTERVAL } from 'src/workspaces/common/state/useWorkspaceStatePolling'; @@ -37,14 +39,9 @@ jest.mock('src/libs/notifications', (): NotificationExports => { }; }); -type AjaxExports = typeof import('src/libs/ajax'); - -jest.mock('src/libs/ajax', (): AjaxExports => { - return { - ...jest.requireActual('src/libs/ajax'), - Ajax: jest.fn(), - }; -}); +jest.mock('src/libs/ajax/firecloud/FirecloudBucket'); +jest.mock('src/libs/ajax/workspaces/Workspaces'); +jest.mock('src/libs/ajax/Metrics'); type WorkspaceFiltersExports = typeof import('src/workspaces/list/WorkspaceFilters'); jest.mock('src/workspaces/list/WorkspaceFilters', () => ({ @@ -72,19 +69,17 @@ describe('WorkspaceList', () => { status: 'Ready', }); const mockDetailsFn = jest.fn(); - const mockAjax: DeepPartial = { - Workspaces: { - workspace: () => ({ - details: mockDetailsFn, - }), - }, - FirecloudBucket: { - getFeaturedWorkspaces: () => [], - }, - Metrics: { captureEvent: jest.fn() } as Partial, - }; - - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue( + partial({ + workspace: () => partial({ details: mockDetailsFn }), + }) + ); + asMockedFn(FirecloudBucket).mockReturnValue( + partial({ + getFeaturedWorkspaces: async () => [], + }) + ); + asMockedFn(Metrics).mockReturnValue(partial({ captureEvent: jest.fn() })); jest.useFakeTimers(); @@ -119,24 +114,21 @@ describe('WorkspaceList', () => { loading: false, status: 'Ready', }); - const mockDetailsFn: ReturnType['details'] = jest - .fn() - .mockResolvedValue({ workspace: { state } } satisfies DeepPartial); - const mockWorkspacesFn = jest.fn().mockReturnValue({ - details: mockDetailsFn, - } satisfies DeepPartial); - - const mockAjax: DeepPartial = { - Workspaces: { - workspace: mockWorkspacesFn, - }, - FirecloudBucket: { - getFeaturedWorkspaces: () => [], - }, - Metrics: { captureEvent: jest.fn() } as Partial, - }; - - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + const mockDetailsFn: WorkspaceContract['details'] = jest.fn().mockResolvedValue({ + workspace: { state }, + } satisfies DeepPartial); + const mockWorkspacesFn: () => WorkspaceContract = jest.fn().mockReturnValue( + partial({ + details: mockDetailsFn, + }) + ); + asMockedFn(Workspaces).mockReturnValue(partial({ workspace: mockWorkspacesFn })); + asMockedFn(FirecloudBucket).mockReturnValue( + partial({ + getFeaturedWorkspaces: async () => [], + }) + ); + asMockedFn(Metrics).mockReturnValue(partial({ captureEvent: jest.fn() })); jest.useFakeTimers(); @@ -191,31 +183,27 @@ describe('WorkspaceList', () => { loading: false, status: 'Ready', }); - const mockDeletingDetailsFn: ReturnType['details'] = jest - .fn() - .mockResolvedValue({ workspace: { state: 'Deleting' } } satisfies DeepPartial); - const mockCloningDetailsFn: ReturnType['details'] = jest - .fn() - .mockResolvedValue({ workspace: { state: 'Cloning' } } satisfies DeepPartial); - - const mockWorkspacesFn = jest.fn().mockImplementation((_, name) => { + const mockDeletingDetailsFn: WorkspaceContract['details'] = jest.fn().mockResolvedValue({ + workspace: { state: 'Deleting' }, + } satisfies DeepPartial); + const mockCloningDetailsFn: WorkspaceContract['details'] = jest.fn().mockResolvedValue({ + workspace: { state: 'Cloning' }, + } satisfies DeepPartial); + + const mockWorkspaceFn: () => WorkspaceContract = jest.fn().mockImplementation((_, name) => { const detailsFn = name === defaultAzureWorkspace.workspace.name ? mockDeletingDetailsFn : mockCloningDetailsFn; - return { + return partial({ details: detailsFn, - } satisfies DeepPartial; + }); }); - const mockAjax: DeepPartial = { - Workspaces: { - workspace: mockWorkspacesFn, - }, - FirecloudBucket: { - getFeaturedWorkspaces: () => [], - }, - Metrics: { captureEvent: jest.fn() } as Partial, - }; - - asMockedFn(Ajax).mockImplementation(() => mockAjax as AjaxContract); + asMockedFn(Workspaces).mockReturnValue(partial({ workspace: mockWorkspaceFn })); + asMockedFn(FirecloudBucket).mockReturnValue( + partial({ + getFeaturedWorkspaces: async () => [], + }) + ); + asMockedFn(Metrics).mockReturnValue(partial({ captureEvent: jest.fn() })); jest.useFakeTimers(); @@ -236,22 +224,22 @@ describe('WorkspaceList', () => { await waitFor(() => expect(mockDeletingDetailsFn).toBeCalledTimes(2)); await waitFor(() => expect(mockCloningDetailsFn).toBeCalledTimes(2)); - expect(mockWorkspacesFn).toHaveBeenNthCalledWith( + expect(mockWorkspaceFn).toHaveBeenNthCalledWith( 1, defaultAzureWorkspace.workspace.namespace, defaultAzureWorkspace.workspace.name ); - expect(mockWorkspacesFn).toHaveBeenNthCalledWith( + expect(mockWorkspaceFn).toHaveBeenNthCalledWith( 2, defaultGoogleWorkspace.workspace.namespace, defaultGoogleWorkspace.workspace.name ); - expect(mockWorkspacesFn).toHaveBeenNthCalledWith( + expect(mockWorkspaceFn).toHaveBeenNthCalledWith( 3, defaultAzureWorkspace.workspace.namespace, defaultAzureWorkspace.workspace.name ); - expect(mockWorkspacesFn).toHaveBeenNthCalledWith( + expect(mockWorkspaceFn).toHaveBeenNthCalledWith( 4, defaultGoogleWorkspace.workspace.namespace, defaultGoogleWorkspace.workspace.name diff --git a/src/workspaces/list/WorkspacesList.ts b/src/workspaces/list/WorkspacesList.ts index e9295a10be..ba48917114 100644 --- a/src/workspaces/list/WorkspacesList.ts +++ b/src/workspaces/list/WorkspacesList.ts @@ -6,7 +6,7 @@ import { Link, topSpinnerOverlay, transparentSpinnerOverlay } from 'src/componen import FooterWrapper from 'src/components/FooterWrapper'; import { icon } from 'src/components/icons'; import { TopBar } from 'src/components/TopBar'; -import { Ajax } from 'src/libs/ajax'; +import { FirecloudBucket } from 'src/libs/ajax/firecloud/FirecloudBucket'; import { withErrorIgnoring } from 'src/libs/error'; import { updateSearch, useRoute } from 'src/libs/nav'; import { useOnMount } from 'src/libs/react-utils'; @@ -79,7 +79,7 @@ export const WorkspacesList = (): ReactNode => { useOnMount(() => { const loadFeatured = withErrorIgnoring(async () => { - setFeaturedList(await Ajax().FirecloudBucket.getFeaturedWorkspaces()); + setFeaturedList(await FirecloudBucket().getFeaturedWorkspaces()); }); loadFeatured(); }); From 3e1f96a4b7aaf6e71711f1942d068e36f98e0635 Mon Sep 17 00:00:00 2001 From: Christina Ahrens Roberts Date: Fri, 4 Oct 2024 09:48:54 -0400 Subject: [PATCH 2/2] [IA-5074] Improve IA integration test stability (#5122) --- integration-tests/tests/run-analysis-azure.ts | 6 +++--- integration-tests/tests/run-analysis.js | 6 +++--- integration-tests/tests/run-rstudio.js | 6 +++--- integration-tests/utils/integration-utils.js | 7 +++++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/integration-tests/tests/run-analysis-azure.ts b/integration-tests/tests/run-analysis-azure.ts index 427e7feb9c..5daa3883fd 100644 --- a/integration-tests/tests/run-analysis-azure.ts +++ b/integration-tests/tests/run-analysis-azure.ts @@ -21,7 +21,7 @@ const { getAnimatedDrawer, input, noSpinnersAfter, - waitForNoModal, + waitForNoModalDrawer, waitForNoSpinners, } = require('../utils/integration-utils'); const { registerTest } = require('../utils/jest-utils'); @@ -51,7 +51,7 @@ const testRunAnalysisAzure = _.flowRight( timeout: Millis.ofMinute, }); await click(page, clickable({ textContains: 'Close' }), { timeout: Millis.ofMinute }); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Navigate to analysis launcher await click(page, clickable({ textContains: `${notebookName}.ipynb` })); @@ -63,7 +63,7 @@ const testRunAnalysisAzure = _.flowRight( await click(page, clickable({ textContains: 'Open' })); await findText(page, 'Azure Cloud Environment'); await click(page, clickable({ textContains: 'Create' })); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Wait for env to begin creating await findElement(page, clickable({ textContains: 'JupyterLab Environment' })); diff --git a/integration-tests/tests/run-analysis.js b/integration-tests/tests/run-analysis.js index 351451c745..6d2c2a40e1 100644 --- a/integration-tests/tests/run-analysis.js +++ b/integration-tests/tests/run-analysis.js @@ -16,7 +16,7 @@ const { getAnimatedDrawer, input, noSpinnersAfter, - waitForNoModal, + waitForNoModalDrawer, waitForNoSpinners, } = require('../utils/integration-utils'); const { registerTest } = require('../utils/jest-utils'); @@ -47,7 +47,7 @@ const testRunAnalysisFn = _.flowRight( timeout: Millis.ofMinute, }); await click(page, clickable({ textContains: 'Close' }), { timeout: Millis.ofMinute }); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Navigate to analysis launcher await click(page, clickable({ textContains: `${notebookName}.ipynb` })); @@ -61,7 +61,7 @@ const testRunAnalysisFn = _.flowRight( }); await findText(page, 'Jupyter Cloud Environment'); await click(page, clickable({ text: 'Create' })); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Wait for env to begin creating await findElement(page, clickable({ textContains: 'Jupyter Environment' }), { timeout: Millis.ofSeconds(40) }); diff --git a/integration-tests/tests/run-rstudio.js b/integration-tests/tests/run-rstudio.js index 4399b3fdc2..3941577bd3 100644 --- a/integration-tests/tests/run-rstudio.js +++ b/integration-tests/tests/run-rstudio.js @@ -16,7 +16,7 @@ const { getAnimatedDrawer, input, noSpinnersAfter, - waitForNoModal, + waitForNoModalDrawer, waitForNoSpinners, } = require('../utils/integration-utils'); const { registerTest } = require('../utils/jest-utils'); @@ -50,7 +50,7 @@ const testRunRStudioFn = _.flowRight( timeout: Millis.ofMinute, }); await click(page, clickable({ textContains: 'Close' }), { timeout: Millis.ofMinute }); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Navigate to analysis launcher await click(page, clickable({ textContains: `${rFileName}.Rmd` })); @@ -63,7 +63,7 @@ const testRunRStudioFn = _.flowRight( action: () => click(page, clickable({ textContains: 'Open' })), }); await click(page, clickable({ text: 'Create' })); - await waitForNoModal(page); + await waitForNoModalDrawer(page); // Wait for env to begin creating await findElement(page, clickable({ textContains: 'RStudio Environment' }), { timeout: Millis.ofMinutes(2) }); diff --git a/integration-tests/utils/integration-utils.js b/integration-tests/utils/integration-utils.js index 9fa654afdc..3594309f34 100644 --- a/integration-tests/utils/integration-utils.js +++ b/integration-tests/utils/integration-utils.js @@ -233,6 +233,12 @@ const waitForModal = (page, { timeout = 30000 } = {}) => { return page.waitForSelector('.ReactModal__Overlay', { hidden: false, timeout }); }; +const waitForNoModalDrawer = async (page) => { + await waitForNoModal(page); + // Matches the animation transition time + await delay(200); +}; + // Puppeteer works by internally using MutationObserver. We are setting up the listener before // the action to ensure that the spinner rendering is captured by the observer, followed by // waiting for the spinner to be removed @@ -631,6 +637,7 @@ module.exports = { waitForNoModal, waitForMenu, waitForModal, + waitForNoModalDrawer, waitForNoSpinners, withPageLogging, withScreenshot,