diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index e71f4384d5b..897a00cec92 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -15,6 +15,7 @@ module.exports = { 'plugin:react/recommended', 'plugin:console/json', 'plugin:console/prettier', + 'plugin:console/testing-library-tests', ], parser: '@typescript-eslint/parser', parserOptions: { diff --git a/frontend/packages/console-app/src/components/detect-namespace/__tests__/namespace.spec.ts b/frontend/packages/console-app/src/components/detect-namespace/__tests__/namespace.spec.ts index 6abf516591a..57d90c49802 100644 --- a/frontend/packages/console-app/src/components/detect-namespace/__tests__/namespace.spec.ts +++ b/frontend/packages/console-app/src/components/detect-namespace/__tests__/namespace.spec.ts @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { act, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import { useLocation } from 'react-router'; import { k8sGet } from '@console/dynamic-plugin-sdk/src/utils/k8s'; import { ALL_NAMESPACES_KEY } from '@console/shared/src/constants'; @@ -73,14 +73,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([preferredNamespace, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(urlNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(urlNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return activeNamespace if it it already defined', async () => { @@ -91,14 +89,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([preferredNamespace, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(activeNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(activeNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return preferredNamespace if it exists', async () => { @@ -108,14 +104,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([preferredNamespace, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(preferredNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(preferredNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return lastNamespace if it exists and preferredNamespace do not exist', async () => { @@ -125,14 +119,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([undefined, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(lastNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(lastNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return ALL_NAMESPACES_KEY if urlNamespacel, preferredNamespace, lastNamespace are not defined', async () => { @@ -142,14 +134,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([undefined, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([undefined, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => useValuesForNamespaceContext()); - const { namespace, loaded } = result.current; - expect(namespace).toEqual(ALL_NAMESPACES_KEY); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(ALL_NAMESPACES_KEY); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return true for loaded if urlNamespace has loaded irrespective of loaded status for other resources', async () => { @@ -159,14 +149,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([preferredNamespace, jest.fn(), false]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), false]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(urlNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(urlNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return true for loaded if urlNamespace is not defined and preferredNamespace and lastNamespace are loaded, and flags are not pending anymore', async () => { @@ -176,14 +164,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([preferredNamespace, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toEqual(preferredNamespace); - expect(loaded).toBeTruthy(); + await waitFor(() => { + expect(result.current.namespace).toEqual(preferredNamespace); + }); + expect(result.current.loaded).toBeTruthy(); }); it('should return false for loaded if urlNamespace is undefined and no resources is loaded yet', async () => { @@ -193,14 +179,12 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([undefined, jest.fn(), false]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), false]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toBeFalsy(); - expect(loaded).toBeFalsy(); + await waitFor(() => { + expect(result.current.namespace).toBeFalsy(); + }); + expect(result.current.loaded).toBeFalsy(); }); it('should return false for loaded if urlNamespace is undefined and flags are pending', async () => { @@ -210,13 +194,11 @@ describe('useValuesForNamespaceContext', () => { usePreferredNamespaceMock.mockReturnValue([undefined, jest.fn(), true]); useLastNamespaceMock.mockReturnValue([lastNamespace, jest.fn(), true]); - const { result, rerender } = renderHook(() => useValuesForNamespaceContext()); - await act(async () => { - rerender(); - }); - const { namespace, loaded } = result.current; + const { result } = renderHook(() => useValuesForNamespaceContext()); - expect(namespace).toBeFalsy(); - expect(loaded).toBeFalsy(); + await waitFor(() => { + expect(result.current.namespace).toBeFalsy(); + }); + expect(result.current.loaded).toBeFalsy(); }); }); diff --git a/frontend/packages/console-app/src/components/nodes/__tests__/NodeTerminal.spec.tsx b/frontend/packages/console-app/src/components/nodes/__tests__/NodeTerminal.spec.tsx index 4760e2f2927..9c12fb3ba49 100644 --- a/frontend/packages/console-app/src/components/nodes/__tests__/NodeTerminal.spec.tsx +++ b/frontend/packages/console-app/src/components/nodes/__tests__/NodeTerminal.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, act } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import type { NodeKind, PodKind } from '@console/internal/module/k8s'; import { k8sCreate, k8sGet, k8sKillByName } from '@console/internal/module/k8s'; @@ -41,11 +41,13 @@ const setupPodCreation = () => { const renderAndCreatePod = async () => { jest.useFakeTimers(); + render(); await act(async () => { - render(); + await Promise.resolve(); }); await act(async () => { - jest.advanceTimersByTime(1100); + jest.runOnlyPendingTimers(); + await Promise.resolve(); }); jest.useRealTimers(); }; @@ -80,7 +82,7 @@ describe('NodeTerminal', () => { await renderAndCreatePod(); - expect(screen.getByText('Connection refused')).toBeVisible(); + expect(await screen.findByText('Connection refused')).toBeVisible(); }); it('should show loading when watch has not loaded yet', async () => { @@ -102,7 +104,7 @@ describe('NodeTerminal', () => { await renderAndCreatePod(); - expect(screen.getByText('Debug pod not found or was deleted.')).toBeVisible(); + expect(await screen.findByText('Debug pod not found or was deleted.')).toBeVisible(); }); it('should show error with message when pod phase is Failed', async () => { @@ -118,7 +120,7 @@ describe('NodeTerminal', () => { await renderAndCreatePod(); - expect(screen.getByText(/The debug pod failed.*ImagePullBackOff/)).toBeVisible(); + expect(await screen.findByText(/The debug pod failed.*ImagePullBackOff/)).toBeVisible(); }); it('should render terminal when pod is Running', async () => { @@ -134,7 +136,7 @@ describe('NodeTerminal', () => { await renderAndCreatePod(); - expect(screen.getByText('PodConnectLoader')).toBeInTheDocument(); + expect(await screen.findByText('PodConnectLoader')).toBeInTheDocument(); }); it('should show error when pod creation fails', async () => { @@ -143,10 +145,7 @@ describe('NodeTerminal', () => { (k8sKillByName as jest.Mock).mockResolvedValue({}); (useK8sWatchResource as jest.Mock).mockReturnValue([undefined, true, undefined]); - await act(async () => { - render(); - }); - - expect(screen.getByText('Forbidden')).toBeVisible(); + await renderAndCreatePod(); + expect(await screen.findByText('Forbidden')).toBeVisible(); }); }); diff --git a/frontend/packages/console-app/src/components/nodes/modals/__tests__/GroupsEditorModal.spec.tsx b/frontend/packages/console-app/src/components/nodes/modals/__tests__/GroupsEditorModal.spec.tsx index 4bdd79e44b6..39682c5f408 100644 --- a/frontend/packages/console-app/src/components/nodes/modals/__tests__/GroupsEditorModal.spec.tsx +++ b/frontend/packages/console-app/src/components/nodes/modals/__tests__/GroupsEditorModal.spec.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor, within } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { k8sPatchResource } from '@console/dynamic-plugin-sdk/src/utils/k8s'; import { useK8sWatchResource } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks'; @@ -44,6 +44,7 @@ const createMockNode = (name: string, groups?: string): NodeKind => } as NodeKind); describe('GroupsEditorModal', () => { + let user: ReturnType; const mockNodes: NodeKind[] = [ createMockNode('node-1', 'group-a,group-b'), createMockNode('node-2', 'group-b,group-c'), @@ -52,6 +53,7 @@ describe('GroupsEditorModal', () => { ]; beforeEach(() => { + user = userEvent.setup(); jest.clearAllMocks(); (useK8sWatchResource as jest.Mock).mockReturnValue([mockNodes, true, null]); (k8sPatchResource as jest.Mock).mockResolvedValue({}); @@ -127,7 +129,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const groupAButton2 = screen.getByRole('button', { name: 'group-a' }); expect(groupAButton2).toHaveClass('pf-m-current'); @@ -137,7 +139,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); expect(screen.getByText('Nodes for group group-a')).toBeInTheDocument(); @@ -153,13 +155,13 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); // node-4 is not in group-a initially const node4Checkbox = screen.getByLabelText('node-4'); expect(node4Checkbox).not.toBeChecked(); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); expect(node4Checkbox).toBeChecked(); }); @@ -168,12 +170,12 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node1Checkbox = screen.getByLabelText('node-1'); expect(node1Checkbox).toBeChecked(); - await userEvent.click(node1Checkbox); + await user.click(node1Checkbox); expect(node1Checkbox).not.toBeChecked(); }); @@ -184,7 +186,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const addButton = screen.getByText('Add new group'); - await userEvent.click(addButton); + await user.click(addButton); expect(screen.getByPlaceholderText('Enter a group name')).toBeInTheDocument(); }); @@ -193,10 +195,10 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const addButton = screen.getByText('Add new group'); - await userEvent.click(addButton); + await user.click(addButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'new-group{enter}'); + await user.type(input, 'new-group{enter}'); expect(screen.getByText('new-group')).toBeInTheDocument(); }); @@ -205,13 +207,13 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'button-group'); + await user.type(input, 'button-group'); const addButton = screen.getByRole('button', { name: 'Add' }); - await userEvent.click(addButton); + await user.click(addButton); expect(screen.getByText('button-group')).toBeInTheDocument(); }); @@ -220,10 +222,10 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'group-a'); + await user.type(input, 'group-a'); const addButton = screen.getByRole('button', { name: 'Add' }); expect(addButton).toBeDisabled(); @@ -233,7 +235,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const addButton = screen.getByRole('button', { name: 'Add' }); expect(addButton).toBeDisabled(); @@ -243,10 +245,10 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'auto-select{enter}'); + await user.type(input, 'auto-select{enter}'); const newGroupButton = screen.getByRole('button', { name: 'auto-select' }); expect(newGroupButton).toHaveClass('pf-m-current'); @@ -256,10 +258,10 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name') as HTMLInputElement; - await userEvent.type(input, 'clear-test{enter}'); + await user.type(input, 'clear-test{enter}'); expect(input.value).toBe(''); }); @@ -269,12 +271,12 @@ describe('GroupsEditorModal', () => { it('removes group when trash icon is clicked', async () => { renderWithProviders(); - const groupAItem = screen - .getByText('group-a') - .closest('.pf-v6-c-simple-list__item') as HTMLElement; - const trashIcon = within(groupAItem).getByRole('img', { hidden: true }); + // Uninterpolated mock t() gives identical delete labels; groups are sorted, so index 0 is group-a. + const deleteGroupAButton = screen.getAllByRole('button', { + name: 'Delete group {{groupName}}', + })[0]; - await userEvent.click(trashIcon); + await user.click(deleteGroupAButton); expect(screen.queryByText('group-a')).not.toBeInTheDocument(); }); @@ -284,16 +286,15 @@ describe('GroupsEditorModal', () => { // Select group-a const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); expect(screen.getByText('Nodes for group group-a')).toBeInTheDocument(); // Delete group-a - const groupAItem = screen - .getByText('group-a') - .closest('.pf-v6-c-simple-list__item') as HTMLElement; - const trashIcon = within(groupAItem).getByRole('img', { hidden: true }); - await userEvent.click(trashIcon); + const deleteGroupAButton = screen.getAllByRole('button', { + name: 'Delete group {{groupName}}', + })[0]; + await user.click(deleteGroupAButton); // Should no longer show nodes for deleted group expect(screen.queryByText('Nodes for group group-a')).not.toBeInTheDocument(); @@ -307,13 +308,13 @@ describe('GroupsEditorModal', () => { // Select group-a and add node-4 to it const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node4Checkbox = screen.getByLabelText('node-4'); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(k8sPatchResource).toHaveBeenCalled(); @@ -325,13 +326,13 @@ describe('GroupsEditorModal', () => { // Add node-4 to group-a const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node4Checkbox = screen.getByLabelText('node-4'); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(k8sPatchResource).toHaveBeenCalledWith( @@ -359,7 +360,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); // Since no changes were made, k8sPatchResource should not be called await waitFor(() => { @@ -372,13 +373,13 @@ describe('GroupsEditorModal', () => { // Make a change const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node4Checkbox = screen.getByLabelText('node-4'); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(mockCloseOverlay).toHaveBeenCalled(); @@ -393,19 +394,19 @@ describe('GroupsEditorModal', () => { // Make a change const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node4Checkbox = screen.getByLabelText('node-4'); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); const detailsButton = await waitFor(() => { return screen.getByRole('button', { name: 'Show details' }); }); - await userEvent.click(detailsButton); + await user.click(detailsButton); expect(screen.getByText('Error updating {{nodeName}}')).toBeInTheDocument(); expect(screen.getByText(errorMessage)).toBeInTheDocument(); @@ -420,13 +421,13 @@ describe('GroupsEditorModal', () => { // Make a change const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); const node4Checkbox = screen.getByLabelText('node-4'); - await userEvent.click(node4Checkbox); + await user.click(node4Checkbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); expect(saveButton).toBeDisabled(); }); @@ -437,7 +438,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const cancelButton = screen.getByRole('button', { name: 'Cancel' }); - await userEvent.click(cancelButton); + await user.click(cancelButton); expect(mockCloseOverlay).toHaveBeenCalled(); }); @@ -446,7 +447,7 @@ describe('GroupsEditorModal', () => { renderWithProviders(); const reloadButton = screen.getByRole('button', { name: 'Reload' }); - await userEvent.click(reloadButton); + await user.click(reloadButton); // After reload, selected group should be cleared expect(screen.getByText('Select a group')).toBeInTheDocument(); @@ -497,14 +498,14 @@ describe('GroupsEditorModal', () => { // Select group-a const groupAButton = screen.getByRole('button', { name: 'group-a' }); - await userEvent.click(groupAButton); + await user.click(groupAButton); expect(screen.getByLabelText('node-1')).toBeChecked(); expect(screen.getByLabelText('node-3')).toBeChecked(); // Select group-b const groupBButton = screen.getByRole('button', { name: 'group-b' }); - await userEvent.click(groupBButton); + await user.click(groupBButton); expect(screen.getByLabelText('node-1')).toBeChecked(); expect(screen.getByLabelText('node-2')).toBeChecked(); diff --git a/frontend/packages/console-app/src/components/nodes/modals/__tests__/NodeGroupsEditorModal.spec.tsx b/frontend/packages/console-app/src/components/nodes/modals/__tests__/NodeGroupsEditorModal.spec.tsx index 382d637704d..01fdbbae7fe 100644 --- a/frontend/packages/console-app/src/components/nodes/modals/__tests__/NodeGroupsEditorModal.spec.tsx +++ b/frontend/packages/console-app/src/components/nodes/modals/__tests__/NodeGroupsEditorModal.spec.tsx @@ -50,6 +50,7 @@ const createMockNode = (name: string, groups?: string): NodeKind => } as NodeKind); describe('NodeGroupsEditorModal', () => { + let user: ReturnType; const testNode = createMockNode('test-node', 'group-a,group-b'); const mockNodes: NodeKind[] = [ testNode, @@ -59,6 +60,7 @@ describe('NodeGroupsEditorModal', () => { ]; beforeEach(() => { + user = userEvent.setup(); jest.clearAllMocks(); (useK8sWatchResource as jest.Mock).mockReturnValue([mockNodes, true, null]); (k8sPatchResource as jest.Mock).mockResolvedValue({}); @@ -161,7 +163,7 @@ describe('NodeGroupsEditorModal', () => { const groupCCheckbox = screen.getByLabelText('group-c'); expect(groupCCheckbox).not.toBeChecked(); - await userEvent.click(groupCCheckbox); + await user.click(groupCCheckbox); expect(groupCCheckbox).toBeChecked(); }); @@ -174,7 +176,7 @@ describe('NodeGroupsEditorModal', () => { const groupACheckbox = screen.getByLabelText('group-a'); expect(groupACheckbox).toBeChecked(); - await userEvent.click(groupACheckbox); + await user.click(groupACheckbox); expect(groupACheckbox).not.toBeChecked(); }); @@ -185,14 +187,14 @@ describe('NodeGroupsEditorModal', () => { ); // Add group-c and group-d - await userEvent.click(screen.getByLabelText('group-c')); - await userEvent.click(screen.getByLabelText('group-d')); + await user.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-d')); expect(screen.getByLabelText('group-c')).toBeChecked(); expect(screen.getByLabelText('group-d')).toBeChecked(); // Remove group-a - await userEvent.click(screen.getByLabelText('group-a')); + await user.click(screen.getByLabelText('group-a')); expect(screen.getByLabelText('group-a')).not.toBeChecked(); }); @@ -205,7 +207,7 @@ describe('NodeGroupsEditorModal', () => { ); const addButton = screen.getByText('Add new group'); - await userEvent.click(addButton); + await user.click(addButton); expect(screen.getByPlaceholderText('Enter a group name')).toBeInTheDocument(); }); @@ -216,10 +218,10 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'new-group{enter}'); + await user.type(input, 'new-group{enter}'); expect(screen.getByLabelText('new-group')).toBeInTheDocument(); }); @@ -230,13 +232,13 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'button-group'); + await user.type(input, 'button-group'); const addButton = screen.getByRole('button', { name: 'Add' }); - await userEvent.click(addButton); + await user.click(addButton); expect(screen.getByLabelText('button-group')).toBeInTheDocument(); }); @@ -247,10 +249,10 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'group-a'); + await user.type(input, 'group-a'); const addButton = screen.getByRole('button', { name: 'Add' }); expect(addButton).toBeDisabled(); @@ -262,7 +264,7 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const addButton = screen.getByRole('button', { name: 'Add' }); expect(addButton).toBeDisabled(); @@ -274,10 +276,10 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'auto-select{enter}'); + await user.type(input, 'auto-select{enter}'); const newGroupCheckbox = screen.getByLabelText('auto-select'); expect(newGroupCheckbox).toBeChecked(); @@ -289,10 +291,10 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name') as HTMLInputElement; - await userEvent.type(input, 'clear-test{enter}'); + await user.type(input, 'clear-test{enter}'); expect(input.value).toBe(''); }); @@ -303,10 +305,10 @@ describe('NodeGroupsEditorModal', () => { ); const expandButton = screen.getByText('Add new group'); - await userEvent.click(expandButton); + await user.click(expandButton); const input = screen.getByPlaceholderText('Enter a group name'); - await userEvent.type(input, 'group-bb{enter}'); + await user.type(input, 'group-bb{enter}'); // Check order: group-a, group-b, group-bb, group-c, group-d const checkboxes = screen.getAllByRole('checkbox'); @@ -328,10 +330,10 @@ describe('NodeGroupsEditorModal', () => { // Add group-c to the node const groupCCheckbox = screen.getByLabelText('group-c'); - await userEvent.click(groupCCheckbox); + await user.click(groupCCheckbox); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(k8sPatchResource).toHaveBeenCalled(); @@ -344,12 +346,12 @@ describe('NodeGroupsEditorModal', () => { ); // Add group-c and group-d, remove group-a - await userEvent.click(screen.getByLabelText('group-c')); - await userEvent.click(screen.getByLabelText('group-d')); - await userEvent.click(screen.getByLabelText('group-a')); + await user.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-d')); + await user.click(screen.getByLabelText('group-a')); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(k8sPatchResource).toHaveBeenCalledWith( @@ -375,10 +377,10 @@ describe('NodeGroupsEditorModal', () => { ); // Make a change - await userEvent.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-c')); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(mockCloseOverlay).toHaveBeenCalled(); @@ -394,10 +396,10 @@ describe('NodeGroupsEditorModal', () => { ); // Make a change - await userEvent.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-c')); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(screen.getByText('Error occurred')).toBeInTheDocument(); @@ -415,10 +417,10 @@ describe('NodeGroupsEditorModal', () => { ); // Make a change - await userEvent.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-c')); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); const saveButton2 = screen.getByRole('button', { name: 'Save' }); expect(saveButton2).toBeDisabled(); @@ -430,7 +432,7 @@ describe('NodeGroupsEditorModal', () => { ); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); // k8sPatchResource might still be called, but we're just ensuring the test doesn't error // The actual implementation might choose to skip the call or make it anyway @@ -444,7 +446,7 @@ describe('NodeGroupsEditorModal', () => { ); const cancelButton = screen.getByRole('button', { name: 'Cancel' }); - await userEvent.click(cancelButton); + await user.click(cancelButton); expect(mockCloseOverlay).toHaveBeenCalled(); }); @@ -455,11 +457,11 @@ describe('NodeGroupsEditorModal', () => { ); // Make a change - await userEvent.click(screen.getByLabelText('group-c')); + await user.click(screen.getByLabelText('group-c')); expect(screen.getByLabelText('group-c')).toBeChecked(); const reloadButton = screen.getByRole('button', { name: 'Reload' }); - await userEvent.click(reloadButton); + await user.click(reloadButton); // After reload, changes should be reverted expect(screen.getByLabelText('group-c')).not.toBeChecked(); @@ -525,11 +527,11 @@ describe('NodeGroupsEditorModal', () => { ); // Uncheck all groups - await userEvent.click(screen.getByLabelText('group-a')); - await userEvent.click(screen.getByLabelText('group-b')); + await user.click(screen.getByLabelText('group-a')); + await user.click(screen.getByLabelText('group-b')); const saveButton = screen.getByRole('button', { name: 'Save' }); - await userEvent.click(saveButton); + await user.click(saveButton); await waitFor(() => { expect(k8sPatchResource).toHaveBeenCalledWith( diff --git a/frontend/packages/console-app/src/components/nodes/node-dashboard/__tests__/BareMetalInventoryItems.spec.tsx b/frontend/packages/console-app/src/components/nodes/node-dashboard/__tests__/BareMetalInventoryItems.spec.tsx index 7ccef721f4a..3e094a7a961 100644 --- a/frontend/packages/console-app/src/components/nodes/node-dashboard/__tests__/BareMetalInventoryItems.spec.tsx +++ b/frontend/packages/console-app/src/components/nodes/node-dashboard/__tests__/BareMetalInventoryItems.spec.tsx @@ -104,9 +104,9 @@ describe('BareMetalInventoryItems', () => { useIsBareMetalPluginActiveMock.mockReturnValue(false); useWatchBareMetalHostMock.mockReturnValue([null, false, undefined]); - const { container } = renderWithContext(); + renderWithContext(); - expect(container.firstChild).toBeNull(); + expect(screen.queryByTestId('inventory-item')).not.toBeInTheDocument(); }); it('should show loading state when data is loading', () => { @@ -154,9 +154,9 @@ describe('BareMetalInventoryItems', () => { useIsBareMetalPluginActiveMock.mockReturnValue(true); useWatchBareMetalHostMock.mockReturnValue([mockBareMetalHost, true, undefined]); - const { container } = renderWithContext(); + renderWithContext(); - const links = container.querySelectorAll('a'); + const links = screen.getAllByRole('link'); expect(links).toHaveLength(2); // Disk and Network links expect(links[0]).toHaveAttribute( @@ -173,10 +173,9 @@ describe('BareMetalInventoryItems', () => { useIsBareMetalPluginActiveMock.mockReturnValue(true); useWatchBareMetalHostMock.mockReturnValue([null, true, undefined]); - const { container } = renderWithContext(); + renderWithContext(); - const links = container.querySelectorAll('a'); - expect(links).toHaveLength(0); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); it('should call useWatchBareMetalHost with the node object', () => { diff --git a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceCheckboxField.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceCheckboxField.spec.tsx index 33e82f3ac36..7c15db2be41 100644 --- a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceCheckboxField.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceCheckboxField.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { UserPreferenceFieldType } from '@console/dynamic-plugin-sdk/src/extensions/user-preferences'; import { useUserPreference } from '@console/shared/src/hooks/useUserPreference'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; @@ -33,46 +33,38 @@ describe('UserPreferenceCheckboxField', () => { jest.clearAllMocks(); }); - it('should show loading state while user preferences are being fetched', async () => { + it('should show loading state while user preferences are being fetched', () => { setupMocks('', jest.fn(), false); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByTestId('dropdown skeleton id')).toBeInTheDocument(); expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); }); - it('should display checkbox when user preferences have loaded', async () => { + it('should display checkbox when user preferences have loaded', () => { setupMocks('trueValue'); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('checkbox')).toBeVisible(); }); - it('should render with isChecked true if defaultValue is equal to trueValue and user preference has loaded but is not defined', async () => { + it('should render with isChecked true if defaultValue is equal to trueValue and user preference has loaded but is not defined', () => { const mockSetValue = jest.fn(); setupMocks('', mockSetValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(mockSetValue).toHaveBeenCalledWith('trueValue'); // When user preference is explicitly undefined (not just falsy), defaultValue should be set setupMocks(undefined, mockSetValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(mockSetValue).toHaveBeenCalledWith('trueValue'); }); - it('should NOT override falsy user preference values with defaultValue (bug fix)', async () => { + it('should NOT override falsy user preference values with defaultValue (bug fix)', () => { const mockSetValue = jest.fn(); // Test with boolean false as the current value @@ -87,29 +79,23 @@ describe('UserPreferenceCheckboxField', () => { // and override it with the defaultValue mockUserPreference.mockReturnValue([false, mockSetValue, true]); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); // The bug fix ensures that `false` is NOT replaced with the defaultValue expect(mockSetValue).not.toHaveBeenCalled(); expect(screen.getByRole('checkbox')).not.toBeChecked(); }); - it('should render with isChecked true if user preference has loaded and is equal to trueValue', async () => { + it('should render with isChecked true if user preference has loaded and is equal to trueValue', () => { setupMocks('trueValue'); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('checkbox')).toBeChecked(); }); - it('should render with isChecked false if user preference has loaded and is equal to falseValue', async () => { + it('should render with isChecked false if user preference has loaded and is equal to falseValue', () => { setupMocks('falseValue'); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('checkbox')).not.toBeChecked(); }); diff --git a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceDropdownField.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceDropdownField.spec.tsx index 25c7892063e..90cd82136cd 100644 --- a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceDropdownField.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceDropdownField.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { UserPreferenceFieldType } from '@console/dynamic-plugin-sdk/src/extensions/user-preferences'; import { useUserPreference } from '@console/shared/src/hooks/useUserPreference'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; @@ -38,59 +38,49 @@ describe('UserPreferenceDropdownField', () => { jest.clearAllMocks(); }); - it('should show loading state while user preferences are being fetched', async () => { + it('should show loading state while user preferences are being fetched', () => { setupMocks('', jest.fn(), false); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByTestId('select skeleton test-dropdown-field')).toBeVisible(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); - it('should display selected option when user preference is loaded', async () => { + it('should display selected option when user preference is loaded', () => { setupMocks('option1'); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('button')).toBeVisible(); expect(screen.getByText('Option 1')).toBeVisible(); }); - it('should apply default value when user preference is empty', async () => { + it('should apply default value when user preference is empty', () => { const mockSetValue = jest.fn(); setupMocks('', mockSetValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('button')).toBeVisible(); expect(mockSetValue).toHaveBeenCalledWith('#LATEST#'); }); - it('should render description when provided', async () => { + it('should render description when provided', () => { setupMocks('option1'); const testDescription = 'This is a test description for the dropdown'; - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText(testDescription)).toBeVisible(); }); - it('should display placeholder text when no option is selected', async () => { + it('should display placeholder text when no option is selected', () => { setupMocks(''); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText('Select an option')).toBeVisible(); }); diff --git a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceField.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceField.spec.tsx index 14980c5935b..05853b358a7 100644 --- a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceField.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceField.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import UserPreferenceField from '../UserPreferenceField'; import { @@ -25,34 +25,26 @@ describe('UserPreferenceField', () => { jest.clearAllMocks(); }); - it('should render custom component if field type is custom', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should render custom component if field type is custom', () => { + renderWithProviders(); expect(screen.getByTestId('test custom1 component')).toBeInTheDocument(); }); - it('should render dropdown field if field type is dropdown', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should render dropdown field if field type is dropdown', () => { + renderWithProviders(); expect(screen.getByText('Perspective')).toBeVisible(); }); - it('should render checkbox field if field type is checkbox', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should render checkbox field if field type is checkbox', () => { + renderWithProviders(); expect(screen.getByText('Date and time selections')).toBeVisible(); }); - it('should render form group with no interactive elements if field type is invalid or unknown', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should render form group with no interactive elements if field type is invalid or unknown', () => { + renderWithProviders(); expect(screen.getByText('Unknown Input')).toBeVisible(); }); diff --git a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceForm.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceForm.spec.tsx index f3fa30e58b4..c53a6c29b6f 100644 --- a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceForm.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceForm.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import UserPreferenceForm from '../UserPreferenceForm'; import { mockUserPreferenceItems } from './userPreferences.data'; @@ -10,10 +10,8 @@ describe('UserPreferenceForm', () => { expect(screen.queryByRole('form')).not.toBeInTheDocument(); }); - it('should render form with preference fields when items are provided', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should render form with preference fields when items are provided', () => { + renderWithProviders(); expect(screen.getByRole('form')).toBeInTheDocument(); expect(screen.getByText('Project')).toBeVisible(); diff --git a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferencesPage.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferencesPage.spec.tsx index a6586d347e4..7e6a8d7840f 100644 --- a/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferencesPage.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferencesPage.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import * as Router from 'react-router'; import { useResolvedExtensions } from '@console/dynamic-plugin-sdk'; import { useExtensions } from '@console/plugin-sdk/src/api/useExtensions'; @@ -50,7 +50,7 @@ describe('UserPreferencePage', () => { jest.clearAllMocks(); }); - it('should render with default user preference group based on the url params', async () => { + it('should render with default user preference group based on the url params', () => { useParamsMock.mockReturnValue({ group: 'language', }); @@ -58,35 +58,29 @@ describe('UserPreferencePage', () => { useResolvedExtensionsMock.mockReturnValue([mockUserPreferenceItemExtensions, true]); useQueryParamsMock.mockReturnValue(new URLSearchParams()); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('tab', { name: /Language/ })).toBeVisible(); }); - it('should render with "general" user preference group as default when URL has no group param', async () => { + it('should render with "general" user preference group as default when URL has no group param', () => { useParamsMock.mockReturnValue({}); useExtensionsMock.mockReturnValue(mockUserPreferenceGroupExtensions); useResolvedExtensionsMock.mockReturnValue([mockUserPreferenceItemExtensions, true]); useQueryParamsMock.mockReturnValue(new URLSearchParams()); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('tab', { name: /General/ })).toBeVisible(); }); - it('should render loading state when user preference extensions have not resolved', async () => { + it('should render loading state when user preference extensions have not resolved', () => { useParamsMock.mockReturnValue({}); useExtensionsMock.mockReturnValue(mockUserPreferenceGroupExtensions); useResolvedExtensionsMock.mockReturnValue([mockUserPreferenceItemExtensions, false]); useQueryParamsMock.mockReturnValue(new URLSearchParams()); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText('Loading...')).toBeVisible(); expect(screen.queryByRole('tab', { name: /General/ })).not.toBeInTheDocument(); diff --git a/frontend/packages/console-app/src/components/user-preferences/language/__tests__/LanguageDropdown.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/language/__tests__/LanguageDropdown.spec.tsx index 5330867d19c..3e84fcc9fd4 100644 --- a/frontend/packages/console-app/src/components/user-preferences/language/__tests__/LanguageDropdown.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/language/__tests__/LanguageDropdown.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { supportedLocales } from '../const'; import { getLastLanguage } from '../getLastLanguage'; @@ -35,46 +35,38 @@ describe('LanguageDropdown', () => { jest.clearAllMocks(); }); - it('should show loading state while language preferences are being fetched', async () => { + it('should show loading state while language preferences are being fetched', () => { setupMocks('', false); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByTestId('dropdown skeleton console.preferredLanguage')).toBeInTheDocument(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); }); - it('should show "Use browser language" option when no preference is set', async () => { + it('should show "Use browser language" option when no preference is set', () => { setupMocks(undefined, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('checkbox')).toBeChecked(); expect(screen.getByRole('button', { name: 'Select a language' })).toBeDisabled(); }); - it('should enable custom language selection when preference is set', async () => { + it('should enable custom language selection when preference is set', () => { setupMocks(preferredLanguageValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('checkbox')).not.toBeChecked(); expect(screen.getByRole('button', { name: 'Select a language' })).toBeEnabled(); }); - it('should display the selected language name when preference is set', async () => { + it('should display the selected language name when preference is set', () => { setupMocks(preferredLanguageValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText(supportedLocales.ja)).toBeVisible(); }); diff --git a/frontend/packages/console-app/src/components/user-preferences/namespace/__tests__/NamespaceDropdown.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/namespace/__tests__/NamespaceDropdown.spec.tsx index 5465b99f7f5..c0782b2d105 100644 --- a/frontend/packages/console-app/src/components/user-preferences/namespace/__tests__/NamespaceDropdown.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/namespace/__tests__/NamespaceDropdown.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { useProjectOrNamespaceModel } from '@console/internal/components/utils/list-dropdown'; import { NamespaceModel } from '@console/internal/models'; @@ -44,33 +44,27 @@ describe('NamespaceDropdown', () => { jest.clearAllMocks(); }); - it('should show loading state while namespace preferences are being fetched', async () => { + it('should show loading state while namespace preferences are being fetched', () => { setupMocks('', false); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByTestId('dropdown skeleton console.preferredNamespace')).toBeInTheDocument(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); - it('should display selected namespace name when preference is set', async () => { + it('should display selected namespace name when preference is set', () => { setupMocks(preferredNamespace, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('button', { name: preferredNamespace })).toBeVisible(); }); - it('should show "Last viewed" option when no preference is set', async () => { + it('should show "Last viewed" option when no preference is set', () => { setupMocks(undefined, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('button', { name: 'Last viewed' })).toBeVisible(); }); diff --git a/frontend/packages/console-app/src/components/user-preferences/perspective/__tests__/PreferredPerspectiveSelect.spec.tsx b/frontend/packages/console-app/src/components/user-preferences/perspective/__tests__/PreferredPerspectiveSelect.spec.tsx index 31af8896d6c..a5c2a044ef0 100644 --- a/frontend/packages/console-app/src/components/user-preferences/perspective/__tests__/PreferredPerspectiveSelect.spec.tsx +++ b/frontend/packages/console-app/src/components/user-preferences/perspective/__tests__/PreferredPerspectiveSelect.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { useExtensions } from '@console/plugin-sdk/src/api/useExtensions'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import PreferredPerspectiveSelect from '../PreferredPerspectiveSelect'; @@ -35,33 +35,27 @@ describe('PreferredPerspectiveSelect', () => { jest.clearAllMocks(); }); - it('should show loading state while perspective preferences are being fetched', async () => { + it('should show loading state while perspective preferences are being fetched', () => { setupMocks('', false); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByTestId('select skeleton console.preferredPerspective')).toBeInTheDocument(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); - it('should display selected perspective name when preference is set', async () => { + it('should display selected perspective name when preference is set', () => { setupMocks(preferredPerspectiveValue, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText(preferredPerspectiveLabel)).toBeVisible(); }); - it('should show "Last viewed" option when no preference is set', async () => { + it('should show "Last viewed" option when no preference is set', () => { setupMocks(undefined, true); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText('Last viewed')).toBeVisible(); }); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResource.spec.tsx b/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResource.spec.tsx index 20062f0369c..6270f796943 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResource.spec.tsx +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResource.spec.tsx @@ -1,5 +1,5 @@ import type { ReactNode, FC } from 'react'; -import { act, cleanup, render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { combineReducers, createStore, applyMiddleware } from 'redux'; import { thunk } from 'redux-thunk'; @@ -75,8 +75,6 @@ afterEach(async () => { jest.runAllTimers(); }); - cleanup(); - // Ensure that there is no unexpected api calls expect(k8sListMock).toHaveBeenCalledTimes(0); expect(k8sGetMock).toHaveBeenCalledTimes(0); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResources.spec.tsx b/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResources.spec.tsx index d39248e88b8..8640bbec10e 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResources.spec.tsx +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/k8s/hooks/__tests__/useK8sWatchResources.spec.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { act, cleanup, render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { combineReducers, createStore, applyMiddleware } from 'redux'; import { thunk } from 'redux-thunk'; @@ -74,8 +74,6 @@ afterEach(async () => { jest.runAllTimers(); }); - cleanup(); - // Ensure that there is no unexpected api calls expect(k8sListMock).toHaveBeenCalledTimes(0); expect(k8sGetMock).toHaveBeenCalledTimes(0); diff --git a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx index 5002909bb55..785c159392b 100644 --- a/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx +++ b/frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx @@ -29,7 +29,7 @@ describe('CodeEditorToolbar', () => { it('should render null when showShortcuts is false and toolbarLinks is empty', () => { const { container } = render(); - expect(container.firstChild).toBeNull(); + expect(container).toBeEmptyDOMElement(); }); it('should render toolbar with custom links when toolbarLinks are provided', () => { diff --git a/frontend/packages/console-shared/src/components/error/__tests__/error-boundary.spec.tsx b/frontend/packages/console-shared/src/components/error/__tests__/error-boundary.spec.tsx index c62cc3ace8b..494bb09cd4f 100644 --- a/frontend/packages/console-shared/src/components/error/__tests__/error-boundary.spec.tsx +++ b/frontend/packages/console-shared/src/components/error/__tests__/error-boundary.spec.tsx @@ -47,8 +47,9 @@ describe('ErrorBoundary', () => { , ); - expect(container.firstChild).toBeInTheDocument(); - expect(container.firstChild?.textContent).toBe(''); + // Default fallback renders an empty element + expect(container).not.toBeEmptyDOMElement(); + expect(container.textContent).toBe(''); }); }); @@ -73,8 +74,9 @@ describe('withFallback HOC', () => { const WrappedComponent = withFallback(ProblemChild); const { container } = renderWithProviders(); - expect(container.firstChild).toBeInTheDocument(); - expect(container.firstChild?.textContent).toBe(''); + // Default fallback renders an empty element + expect(container).not.toBeEmptyDOMElement(); + expect(container.textContent).toBe(''); }); it('should render the custom fallback when the wrapped component throws an error', () => { diff --git a/frontend/packages/console-shared/src/components/modals/__tests__/TextInputModal.spec.tsx b/frontend/packages/console-shared/src/components/modals/__tests__/TextInputModal.spec.tsx index 9c8c62e908f..539a0769cf6 100644 --- a/frontend/packages/console-shared/src/components/modals/__tests__/TextInputModal.spec.tsx +++ b/frontend/packages/console-shared/src/components/modals/__tests__/TextInputModal.spec.tsx @@ -162,9 +162,9 @@ describe('TextInputModal', () => { it('should show required indicator on label when isRequired is true', () => { renderWithProviders(); - const label = screen.getByText('Name'); - // PatternFly adds an asterisk or required class to the label - expect(label.closest('.pf-v6-c-form__label')).toBeInTheDocument(); + // PatternFly adds required attribute to the input when isRequired is true + const input = screen.getByTestId('input-value'); + expect(input).toBeRequired(); }); it('should submit form when Enter is pressed in input field', async () => { diff --git a/frontend/packages/console-shared/src/components/toast/__tests__/ToastProvider.spec.tsx b/frontend/packages/console-shared/src/components/toast/__tests__/ToastProvider.spec.tsx index c3ae7960827..690c637c501 100644 --- a/frontend/packages/console-shared/src/components/toast/__tests__/ToastProvider.spec.tsx +++ b/frontend/packages/console-shared/src/components/toast/__tests__/ToastProvider.spec.tsx @@ -138,7 +138,7 @@ describe('ToastProvider', () => { const actionLink = await screen.findByText('action 1'); expect(actionLink).toBeVisible(); - expect(actionLink.closest('a')).toBeVisible(); + expect(screen.queryByRole('button', { name: 'action 1' })).not.toBeInTheDocument(); }); it('should dismiss toast on action on anchor click', async () => { @@ -171,9 +171,7 @@ describe('ToastProvider', () => { }); const actionLink = await screen.findByText('action 1'); - const anchorElement = actionLink.closest('a'); - expect(anchorElement).toBeTruthy(); - await user.click(anchorElement as HTMLElement); + await user.click(actionLink); expect(actionFn).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/console-shared/src/hooks/__tests__/usePerspectives.spec.ts b/frontend/packages/console-shared/src/hooks/__tests__/usePerspectives.spec.ts index 82b49731bf4..89976fa46ed 100644 --- a/frontend/packages/console-shared/src/hooks/__tests__/usePerspectives.spec.ts +++ b/frontend/packages/console-shared/src/hooks/__tests__/usePerspectives.spec.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import { checkAccess } from '@console/dynamic-plugin-sdk/src/app/components/utils/rbac'; import { useExtensions } from '@console/plugin-sdk/src/api/useExtensions'; import { usePerspectives, PerspectiveVisibilityState } from '../usePerspectives'; @@ -137,21 +137,20 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: true } })); - const { result, rerender } = renderHook(() => usePerspectives()); + const { result } = renderHook(() => usePerspectives()); - await act(async () => { - rerender(); - }); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); it('should return the admin perspective as default if all the perspectives are disabled', async () => { @@ -186,21 +185,19 @@ describe('usePerspectives', () => { window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: true } })); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'admin', - name: 'Core platform', + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'admin', + name: 'Core platform', + }, }, - }, - ]); + ]); + }); }); it('should return only the enabled perspectives and the perspectives that satisfy the required accessreview checks that are set in the server flags', async () => { @@ -234,30 +231,28 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: true } })); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'dev', - name: 'Developer', - defaultPins: [{ kind: 'ConfigMap' }, { kind: 'Secret' }], + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'dev', + name: 'Developer', + defaultPins: [{ kind: 'ConfigMap' }, { kind: 'Secret' }], + }, }, - }, - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); it('should handle perspectives with accessReview checks', async () => { @@ -292,22 +287,20 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: true } })); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); it('should return only the enabled perspectives and the perspectives that satisfy the required accessreview checks that are set in the server flags for user with limited access', async () => { @@ -341,22 +334,20 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: false } })); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); it('should not return perspective when required accessreview check throws an error', async () => { @@ -390,22 +381,20 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.reject(new Error('Unexpected error'))); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); it('should also return perspectives that are not configured', async () => { @@ -427,36 +416,34 @@ describe('usePerspectives', () => { ]; window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives); (checkAccess as jest.Mock).mockReturnValue(Promise.resolve({ status: { allowed: true } })); - const { result, rerender } = renderHook(() => usePerspectives()); - - await act(async () => { - rerender(); - }); + const { result } = renderHook(() => usePerspectives()); - expect(result.current).toEqual([ - { - type: 'Perspective', - properties: { - id: 'admin', - name: 'Core platform', + await waitFor(() => { + expect(result.current).toEqual([ + { + type: 'Perspective', + properties: { + id: 'admin', + name: 'Core platform', + }, }, - }, - { - type: 'Perspective', - properties: { - id: 'dev', - name: 'Developer', - defaultPins: [{ kind: 'ConfigMap' }, { kind: 'Secret' }], + { + type: 'Perspective', + properties: { + id: 'dev', + name: 'Developer', + defaultPins: [{ kind: 'ConfigMap' }, { kind: 'Secret' }], + }, }, - }, - { - type: 'Perspective', - properties: { - id: 'dev-test', - name: 'Test Developer', - defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + { + type: 'Perspective', + properties: { + id: 'dev-test', + name: 'Test Developer', + defaultPins: [{ kind: 'Deployments' }, { kind: 'Secret' }], + }, }, - }, - ]); + ]); + }); }); }); diff --git a/frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts b/frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts index aeb4ff33405..ed0947ce85e 100644 --- a/frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts +++ b/frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useFavoritesOptions } from '@console/internal/components/useFavoritesOptions'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import type { ConfigMapKind } from '@console/internal/module/k8s'; @@ -87,12 +87,10 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock ConfigMap not found - await act(async () => { - const k8sError: Error & { response?: any } = new Error('Not found'); - k8sError.response = { ok: false, status: 404 }; - useK8sWatchResourceMock.mockReturnValue([null, false, k8sError]); - rerender(); - }); + const k8sError: Error & { response?: any } = new Error('Not found'); + k8sError.response = { ok: false, status: 404 }; + useK8sWatchResourceMock.mockReturnValue([null, false, k8sError]); + rerender(); // Expect loading expect(result.current).toEqual([undefined, expect.any(Function), false]); @@ -101,13 +99,13 @@ describe('useUserPreference', () => { expect(updateConfigMapMock).toHaveBeenCalledTimes(0); // Mock that ConfigMap is now available - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); + rerender(); // Expect default value with loaded - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -131,12 +129,10 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock ConfigMap not found - await act(async () => { - const k8sError: Error & { response?: any } = new Error('Forbidden'); - k8sError.response = { ok: false, status: 403 }; - useK8sWatchResourceMock.mockReturnValue([null, false, k8sError]); - rerender(); - }); + const k8sError2: Error & { response?: any } = new Error('Forbidden'); + k8sError2.response = { ok: false, status: 403 }; + useK8sWatchResourceMock.mockReturnValue([null, false, k8sError2]); + rerender(); // Expect loading expect(result.current).toEqual([undefined, expect.any(Function), false]); @@ -145,13 +141,13 @@ describe('useUserPreference', () => { expect(updateConfigMapMock).toHaveBeenCalledTimes(0); // Mock that ConfigMap is now available - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); + rerender(); // Expect default value with loaded - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -175,13 +171,13 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock empty ConfigMap - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([emptyConfigMap, true, null]); + rerender(); // Expect default value with loaded - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -204,13 +200,13 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock saved ConfigMap - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([savedDataConfigMap, true, null]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([savedDataConfigMap, true, null]); + rerender(); // Expect default value with loaded - expect(result.current).toEqual(['saved value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['saved value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(0); expect(consoleMock).toHaveBeenCalledTimes(0); @@ -281,13 +277,13 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock saved ConfigMap - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([savedDataConfigMap, true, null]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([savedDataConfigMap, true, null]); + rerender(); // Expect saved data - expect(result.current).toEqual(['saved value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['saved value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(0); expect(consoleMock).toHaveBeenCalledTimes(0); @@ -319,14 +315,16 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; + const [, setPreference] = result.current; + act(() => { setPreference('new value'); - rerender(); }); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -350,17 +348,19 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['default value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; + const [, setPreference] = result.current; + act(() => { setPreference((oldValue) => { expect(oldValue).toEqual('default value'); return 'new value'; }); - rerender(); }); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(2); expect(updateConfigMapMock).toHaveBeenLastCalledWith( @@ -384,9 +384,9 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['default value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; - setPreference((oldValue) => { + const [, setPreference2] = result.current; + act(() => { + setPreference2((oldValue) => { expect(oldValue).toEqual('default value'); return 'new value'; }); @@ -394,17 +394,17 @@ describe('useUserPreference', () => { // With sync=true, the hook returns the value from cfData after the request completes. // Simulate the server update by updating the mock to return the new value. - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([ - { ...emptyConfigMap, data: { 'console.key': 'new value' } }, - true, - null, - ]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([ + { ...emptyConfigMap, data: { 'console.key': 'new value' } }, + true, + null, + ]); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(2); expect(updateConfigMapMock).toHaveBeenLastCalledWith( @@ -428,17 +428,19 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; - setPreference((oldValue) => { + const [, setPreference3] = result.current; + act(() => { + setPreference3((oldValue) => { expect(oldValue).toEqual('saved value'); return 'new value'; }); - rerender(); }); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -462,9 +464,9 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; - setPreference((oldValue) => { + const [, setPreference4] = result.current; + act(() => { + setPreference4((oldValue) => { expect(oldValue).toEqual('saved value'); return 'new value'; }); @@ -472,17 +474,17 @@ describe('useUserPreference', () => { // With sync=true, the hook returns the value from cfData after the request completes. // Simulate the server update by updating the mock to return the new value. - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([ - { ...emptyConfigMap, data: { 'console.key': 'new value' } }, - true, - null, - ]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([ + { ...emptyConfigMap, data: { 'console.key': 'new value' } }, + true, + null, + ]); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -506,32 +508,32 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Mock updated data (like, 'from another browser tab/window') - await act(async () => { - const updatedConfigMap = { - ...emptyConfigMap, - data: { - 'console.key': 'magically changed value', - }, - }; - useK8sWatchResourceMock.mockReturnValue([updatedConfigMap, true, null]); - rerender(); - }); + const updatedConfigMap = { + ...emptyConfigMap, + data: { + 'console.key': 'magically changed value', + }, + }; + useK8sWatchResourceMock.mockReturnValue([updatedConfigMap, true, null]); + rerender(); // Expect that data are not changed when sync is disabled! expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; - setPreference((oldValue) => { + const [, setPreference5] = result.current; + act(() => { + setPreference5((oldValue) => { expect(oldValue).toEqual('saved value'); return 'new value'; }); - rerender(); }); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -556,24 +558,24 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['saved value', expect.any(Function), true]); // Mock updated data (like, 'from another browser tab/window') - await act(async () => { - const updatedConfigMap = { - ...emptyConfigMap, - data: { - 'console.key': 'magically changed value', - }, - }; - useK8sWatchResourceMock.mockReturnValue([updatedConfigMap, true, null]); - rerender(); - }); + const updatedConfigMap2 = { + ...emptyConfigMap, + data: { + 'console.key': 'magically changed value', + }, + }; + useK8sWatchResourceMock.mockReturnValue([updatedConfigMap2, true, null]); + rerender(); // Expect changed data if sync option is enabled - expect(result.current).toEqual(['magically changed value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['magically changed value', expect.any(Function), true]); + }); // Call setPreference - await act(async () => { - const [, setPreference] = result.current; - setPreference((oldValue) => { + const [, setPreference6] = result.current; + act(() => { + setPreference6((oldValue) => { expect(oldValue).toEqual('magically changed value'); return 'new value'; }); @@ -581,17 +583,17 @@ describe('useUserPreference', () => { // With sync=true, the hook returns the value from cfData after the request completes. // Simulate the server update by updating the mock to return the new value. - await act(async () => { - useK8sWatchResourceMock.mockReturnValue([ - { ...emptyConfigMap, data: { 'console.key': 'new value' } }, - true, - null, - ]); - rerender(); - }); + useK8sWatchResourceMock.mockReturnValue([ + { ...emptyConfigMap, data: { 'console.key': 'new value' } }, + true, + null, + ]); + rerender(); // Expect new value and API update - expect(result.current).toEqual(['new value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['new value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(1); expect(updateConfigMapMock).toHaveBeenCalledWith( @@ -619,16 +621,16 @@ describe('useUserPreference', () => { ok: false, status: 404, }; - await act(async () => { - createConfigMapMock.mockImplementation(async () => { - throw error; - }); - useK8sWatchResourceMock.mockReturnValue([null, false, error]); - rerender(); + createConfigMapMock.mockImplementation(async () => { + throw error; }); + useK8sWatchResourceMock.mockReturnValue([null, false, error]); + rerender(); // Should call createConfigMap, but not updateConfigMap - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(1); expect(createConfigMapMock).toHaveBeenCalledWith(); expect(updateConfigMapMock).toHaveBeenCalledTimes(0); @@ -650,31 +652,26 @@ describe('useUserPreference', () => { // Expect loading data expect(result.current).toEqual([undefined, expect.any(Function), false]); - // Mock that createConfigMap is 404 API not found. - const error: Error & { response?: any } = new Error('Forbidden'); - error.response = { - ok: false, - status: 403, - }; - await act(async () => { - createConfigMapMock.mockImplementation(async () => { - throw error; - }); - const k8sError: Error & { response?: any } = new Error('Forbidden'); - k8sError.response = { ok: false, status: 403 }; - useK8sWatchResourceMock.mockReturnValue([null, false, k8sError]); - rerender(); + // Same as 404 case above, but API returns 403 Forbidden when the user cannot access the namespace. + const forbiddenError: Error & { response?: any } = new Error('Forbidden'); + forbiddenError.response = { ok: false, status: 403 }; + createConfigMapMock.mockImplementation(async () => { + throw forbiddenError; }); + useK8sWatchResourceMock.mockReturnValue([null, false, forbiddenError]); + rerender(); // Should call createConfigMap, but not updateConfigMap - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(1); expect(createConfigMapMock).toHaveBeenCalledWith(); expect(updateConfigMapMock).toHaveBeenCalledTimes(0); expect(consoleMock).toHaveBeenCalledTimes(1); expect(consoleMock).toHaveBeenCalledWith( 'Could not create ConfigMap for user settings:', - error, + forbiddenError, ); }); @@ -690,16 +687,16 @@ describe('useUserPreference', () => { expect(result.current).toEqual([undefined, expect.any(Function), false]); // Mock that createConfigMap returns an unknown error. - await act(async () => { - createConfigMapMock.mockImplementation(async () => { - throw new Error('Unknown error'); - }); - useK8sWatchResourceMock.mockReturnValue([null, false, new Error('Unknown error')]); - rerender(); + createConfigMapMock.mockImplementation(async () => { + throw new Error('Unknown error'); }); + useK8sWatchResourceMock.mockReturnValue([null, false, new Error('Unknown error')]); + rerender(); // Should call createConfigMap, but not updateConfigMap - expect(result.current).toEqual(['default value', expect.any(Function), true]); + await waitFor(() => { + expect(result.current).toEqual(['default value', expect.any(Function), true]); + }); expect(createConfigMapMock).toHaveBeenCalledTimes(0); expect(updateConfigMapMock).toHaveBeenCalledTimes(0); expect(consoleMock).toHaveBeenCalledTimes(0); @@ -728,11 +725,13 @@ describe('useUserPreference', () => { expect(result.current).toEqual(['impersonate.value', expect.any(Function), true]); - await act(async () => { + act(() => { result.current[1]('newValue'); }); - expect(storageListenerInvoked).toBe(true); + await waitFor(() => { + expect(storageListenerInvoked).toBe(true); + }); window.removeEventListener('storage', storageListener); expect(consoleMock).toHaveBeenCalledTimes(0); diff --git a/frontend/packages/dev-console/src/components/add/AddCardSectionSkeleton.tsx b/frontend/packages/dev-console/src/components/add/AddCardSectionSkeleton.tsx index 5d8f89e82a1..d7f1cba6f73 100644 --- a/frontend/packages/dev-console/src/components/add/AddCardSectionSkeleton.tsx +++ b/frontend/packages/dev-console/src/components/add/AddCardSectionSkeleton.tsx @@ -4,7 +4,10 @@ import './AddCardSectionSkeleton.scss'; const AddCardSectionSkeleton: FC = () => { return ( - + {Array.from({ length: 4 }, (_, i) => (
diff --git a/frontend/packages/dev-console/src/components/add/__tests__/AddPage.spec.tsx b/frontend/packages/dev-console/src/components/add/__tests__/AddPage.spec.tsx index bfc8d1e2e50..64be0e7ce27 100644 --- a/frontend/packages/dev-console/src/components/add/__tests__/AddPage.spec.tsx +++ b/frontend/packages/dev-console/src/components/add/__tests__/AddPage.spec.tsx @@ -1,6 +1,7 @@ import type { ReactNode, ComponentType } from 'react'; -import { screen, cleanup } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import * as Router from 'react-router'; +import { useFlag } from '@console/shared/src/hooks/useFlag'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { PageContents as AddPage } from '../AddPage'; @@ -10,13 +11,17 @@ jest.mock('react-router', () => ({ })); jest.mock('@console/shared', () => { - const originalModule = jest.requireActual('@console/shared'); return { - ...originalModule, - useFlag: jest.fn().mockReturnValue(false), + FLAGS: { + OPENSHIFT: 'OPENSHIFT', + }, }; }); +jest.mock('@console/shared/src/hooks/useFlag', () => ({ + useFlag: jest.fn(), +})); + jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, @@ -42,6 +47,22 @@ jest.mock('../hooks/useShowAddCardItemDetails', () => ({ useShowAddCardItemDetails: () => [true, jest.fn()], })); +jest.mock('../../NamespacedPage', () => ({ + __esModule: true, + default: ({ children }) => children, + NamespacedPageVariants: { light: 'light' }, +})); + +jest.mock('../../projects/CreateProjectListPage', () => ({ + __esModule: true, + default: ({ children }) => (typeof children === 'function' ? children(jest.fn()) : children), + CreateAProjectButton: () => null, +})); + +jest.mock('../../../../../../public/components/start-guide', () => ({ + withStartGuide: (Component) => Component, +})); + jest.mock( '@console/internal/components/dashboard/project-dashboard/getting-started/GettingStartedSection', () => ({ @@ -60,10 +81,14 @@ jest.mock('@console/topology/src/components/quick-search/TopologyQuickSearchButt })); const useParamsMock = Router.useParams as jest.Mock; +const useFlagMock = useFlag as jest.Mock; describe('AddPage', () => { + beforeEach(() => { + useFlagMock.mockReturnValue(false); + }); + afterEach(() => { - cleanup(); jest.clearAllMocks(); }); diff --git a/frontend/packages/dev-console/src/components/add/__tests__/AddPageLayout.spec.tsx b/frontend/packages/dev-console/src/components/add/__tests__/AddPageLayout.spec.tsx index 91f302251d2..82a6acd6d8f 100644 --- a/frontend/packages/dev-console/src/components/add/__tests__/AddPageLayout.spec.tsx +++ b/frontend/packages/dev-console/src/components/add/__tests__/AddPageLayout.spec.tsx @@ -1,4 +1,4 @@ -import { screen, cleanup } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import * as utils from '../../../utils/useAddActionExtensions'; import AddPageLayout from '../AddPageLayout'; @@ -35,7 +35,6 @@ describe('AddPageLayout', () => { const props = { title: 'title' }; afterEach(() => { - cleanup(); jest.clearAllMocks(); }); diff --git a/frontend/packages/dev-console/src/components/add/__tests__/MasonryLayout.spec.tsx b/frontend/packages/dev-console/src/components/add/__tests__/MasonryLayout.spec.tsx index 689b53b8019..7275a781679 100644 --- a/frontend/packages/dev-console/src/components/add/__tests__/MasonryLayout.spec.tsx +++ b/frontend/packages/dev-console/src/components/add/__tests__/MasonryLayout.spec.tsx @@ -1,4 +1,4 @@ -import { act, waitFor } from '@testing-library/react'; +import { act, screen, waitFor } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import AddCardSectionSkeleton from '../AddCardSectionSkeleton'; import { MasonryLayout } from '../layout/MasonryLayout'; @@ -77,7 +77,7 @@ describe('Masonry Layout', () => { it('should render loading component if loading is true and LoadingComponent is defined', async () => { setWidth(1400); - const { container } = renderWithProviders( + renderWithProviders(
Child 1
Child 2
@@ -90,21 +90,17 @@ describe('Masonry Layout', () => { // Wait for width measurement and rendering to complete await waitFor(() => { // Should show 4 columns (Math.floor(1400 / 300)) - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(4); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(4); }); // Should render 4 skeleton placeholders (one per column) - const skeletons = container.querySelectorAll( - '.odc-add-section-skeleton-placeholder__container', - ); - expect(skeletons).toHaveLength(4); + expect(screen.getAllByTestId('add-card-section-skeleton')).toHaveLength(4); }); it('should render children if loading is false', async () => { setWidth(1400); - const { container } = renderWithProviders( + renderWithProviders(
Child 1
Child 2
@@ -117,19 +113,17 @@ describe('Masonry Layout', () => { // Wait for width measurement and rendering to complete await waitFor(() => { // Should show 4 columns (Math.floor(1400 / 300)) - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(4); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(4); }); // Should render all children - const children = container.querySelectorAll('div.child'); - expect(children).toHaveLength(5); + expect(screen.getAllByText(/^Child \d$/)).toHaveLength(5); }); it('should change columns if a resize event exceeds threshold', async () => { setWidth(1200); - const { container } = renderWithProviders( + renderWithProviders(
Child 1
Child 2
@@ -142,8 +136,7 @@ describe('Masonry Layout', () => { // Wait for initial width measurement and rendering to complete await waitFor(() => { // Should show 4 columns initially - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(4); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(4); }); // Change the width and trigger ResizeObserver callbacks @@ -158,15 +151,14 @@ describe('Masonry Layout', () => { // Wait for resize to be processed and re-render to complete await waitFor(() => { // Should show 3 columns now (Math.floor(900 / 300)) - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(3); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(3); }); }); it('should not change columns if a resize event does not exceed threshold', async () => { setWidth(1200); - const { container } = renderWithProviders( + renderWithProviders(
Child 1
Child 2
@@ -179,8 +171,7 @@ describe('Masonry Layout', () => { // Wait for initial width measurement and rendering to complete await waitFor(() => { // Should show 4 columns initially - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(4); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(4); }); act(() => { @@ -197,7 +188,6 @@ describe('Masonry Layout', () => { }); // Should still show 4 columns because new width does not exceed threshold - const columns = container.querySelectorAll('.odc-masonry-layout__column'); - expect(columns).toHaveLength(4); + expect(screen.getAllByTestId('masonry-column')).toHaveLength(4); }); }); diff --git a/frontend/packages/dev-console/src/components/add/hooks/__tests__/useAddActionsAccessReviews.spec.ts b/frontend/packages/dev-console/src/components/add/hooks/__tests__/useAddActionsAccessReviews.spec.ts index 2f2f26d4677..fe3ba82735f 100644 --- a/frontend/packages/dev-console/src/components/add/hooks/__tests__/useAddActionsAccessReviews.spec.ts +++ b/frontend/packages/dev-console/src/components/add/hooks/__tests__/useAddActionsAccessReviews.spec.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import { checkAccess } from '@console/internal/components/utils'; import type { AccessReviewResourceAttributes, @@ -29,15 +29,14 @@ describe('useAddActionsAccessReviews', () => { const extensionsWithAccessReview = addActionExtensions.filter( (action) => !!action.properties.accessReview, ); - const { result, rerender } = renderHook(() => + const { result } = renderHook(() => useAddActionsAccessReviews(namespace, extensionsWithAccessReview), ); - await act(async () => { - rerender(); + await waitFor(() => { + const accessReviewResults = Object.values(result.current); + expect(accessReviewResults.length).toEqual(extensionsWithAccessReview.length); + expect(accessReviewResults.every((x) => x === AccessReviewStatus.DENIED)).toBe(true); }); - const accessReviewResults = Object.values(result.current); - expect(accessReviewResults.length).toEqual(extensionsWithAccessReview.length); - expect(accessReviewResults.every((x) => x === AccessReviewStatus.DENIED)).toBe(true); }); it('should return access review status allowed for all extensions which do not have an accessReview prop', () => { @@ -57,15 +56,14 @@ describe('useAddActionsAccessReviews', () => { const extensionsWithAccessReview = addActionExtensions.filter( (action) => !!action.properties.accessReview, ); - const { result, rerender } = renderHook(() => + const { result } = renderHook(() => useAddActionsAccessReviews(namespace, extensionsWithAccessReview), ); - await act(async () => { - rerender(); + await waitFor(() => { + const accessReviewResults = Object.values(result.current); + expect(accessReviewResults.length).toEqual(extensionsWithAccessReview.length); + expect(accessReviewResults.every((x) => x === AccessReviewStatus.ALLOWED)).toBe(true); }); - const accessReviewResults = Object.values(result.current); - expect(accessReviewResults.length).toEqual(extensionsWithAccessReview.length); - expect(accessReviewResults.every((x) => x === AccessReviewStatus.ALLOWED)).toBe(true); }); it('should return accessReviewResults with proper access review status for extensions which do not have an accessReview prop or for which accessReviewResult is true', async () => { @@ -83,18 +81,15 @@ describe('useAddActionsAccessReviews', () => { (resourceAttributes: AccessReviewResourceAttributes) => createCheckAccessPromise(resourceAttributes.group === mockAccessGroup), ); - const { result, rerender } = renderHook(() => - useAddActionsAccessReviews(namespace, addActionExtensions), - ); - await act(async () => { - rerender(); - }); - expect(Object.entries(result.current).length).toEqual(addActionExtensions.length); - extensionsWithMockAccessGroup.forEach((ext) => { - expect(result.current[ext.properties.id]).toBe(AccessReviewStatus.ALLOWED); - }); - extensionsWithoutMockAccessGroup.forEach((ext) => { - expect(result.current[ext.properties.id]).toBe(AccessReviewStatus.DENIED); + const { result } = renderHook(() => useAddActionsAccessReviews(namespace, addActionExtensions)); + await waitFor(() => { + expect(Object.entries(result.current).length).toEqual(addActionExtensions.length); + extensionsWithMockAccessGroup.forEach((ext) => { + expect(result.current[ext.properties.id]).toBe(AccessReviewStatus.ALLOWED); + }); + extensionsWithoutMockAccessGroup.forEach((ext) => { + expect(result.current[ext.properties.id]).toBe(AccessReviewStatus.DENIED); + }); }); }); }); diff --git a/frontend/packages/dev-console/src/components/add/layout/Masonry.tsx b/frontend/packages/dev-console/src/components/add/layout/Masonry.tsx index 6f1f4af5192..74487f547c8 100644 --- a/frontend/packages/dev-console/src/components/add/layout/Masonry.tsx +++ b/frontend/packages/dev-console/src/components/add/layout/Masonry.tsx @@ -67,7 +67,8 @@ export const Masonry: FC = ({ columnCount, children }) => { return { ...old, [key]: height }; }); }, []); - const groupedColumns = Array.from({ length: columns }, () => ({ + const groupedColumns = Array.from({ length: columns }, (_, idx) => ({ + id: `masonry-column-${idx.toString()}`, height: 0, items: [] as ReactElement[], })); @@ -117,9 +118,12 @@ export const Masonry: FC = ({ columnCount, children }) => { return (
- {groupedColumns.map((groupedColumn, columnIndex) => ( - // eslint-disable-next-line react/no-array-index-key -
+ {groupedColumns.map((groupedColumn) => ( +
{groupedColumn.items}
))} diff --git a/frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx index cb59b8f08b4..3a0c3945707 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import * as Router from 'react-router'; import { usePreferredCreateEditMethod } from '@console/app/src/components/user-preferences/synced-editor/usePreferredCreateEditMethod'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; @@ -51,12 +51,11 @@ const usePreferredCreateEditMethodMock = usePreferredCreateEditMethod as jest.Mo beforeEach(() => { useUserPreferenceMock.mockReturnValue([undefined, jest.fn(), true]); - usePreferredCreateEditMethodMock.mockReturnValue([[undefined, true]]); + usePreferredCreateEditMethodMock.mockReturnValue([undefined, true]); }); afterEach(() => { jest.resetAllMocks(); - cleanup(); }); describe('BuildConfigFormPage', () => { @@ -65,10 +64,10 @@ describe('BuildConfigFormPage', () => { jest.spyOn(Router, 'useParams').mockReturnValue({ ns: 'a-namespace', name: 'a-buildconfig' }); - const renderResult = renderWithProviders(); - expect(renderResult.queryByText('Create BuildConfig')).toBeFalsy(); - expect(renderResult.queryByText('Edit BuildConfig')).toBeFalsy(); - renderResult.getByTestId('loading-indicator'); + renderWithProviders(); + expect(screen.queryByText('Create BuildConfig')).toBeFalsy(); + expect(screen.queryByText('Edit BuildConfig')).toBeFalsy(); + expect(screen.getByTestId('loading-indicator')).toBeVisible(); expect(useK8sWatchResourceMock).toHaveBeenCalledTimes(1); expect(useK8sWatchResourceMock).toHaveBeenCalledWith({ @@ -78,7 +77,7 @@ describe('BuildConfigFormPage', () => { }); }); - it('should fetch BuildConfig and render edit form when BuildConfig is loaded', () => { + it('should fetch BuildConfig and render edit form when BuildConfig is loaded', async () => { const watchedBuildConfig: BuildConfig = { apiVersion: 'build.openshift.io/v1', kind: 'BuildConfig', @@ -93,13 +92,13 @@ describe('BuildConfigFormPage', () => { jest.spyOn(Router, 'useParams').mockReturnValue({ ns: 'a-namespace', name: 'a-buildconfig' }); - const renderResult = renderWithProviders(); - expect(renderResult.queryByText('Create BuildConfig')).toBeFalsy(); - renderResult.findByText('Edit BuildConfig'); - renderResult.findByText('Configure via:'); - renderResult.findByText('Form view'); - renderResult.findByText('YAML view'); - renderResult.findByRole('button', { name: /Submit/ }); + renderWithProviders(); + expect(screen.queryByText('Create BuildConfig')).toBeFalsy(); + expect(await screen.findByText('Edit BuildConfig')).toBeVisible(); + expect(await screen.findByText('Configure via:')).toBeVisible(); + expect(await screen.findByText('Form view')).toBeVisible(); + expect(await screen.findByText('YAML view')).toBeVisible(); + expect(await screen.findByRole('button', { name: 'Save' })).toBeVisible(); expect(useK8sWatchResourceMock).toHaveBeenCalledTimes(1); expect(useK8sWatchResourceMock).toHaveBeenCalledWith({ @@ -109,14 +108,13 @@ describe('BuildConfigFormPage', () => { }); }); - it('should render an error when the BuildConfig fetching fails', () => { + it('should render an error when the BuildConfig fetching fails', async () => { useK8sWatchResourceMock.mockReturnValue([null, true, new Error('Something went wrong')]); jest.spyOn(Router, 'useParams').mockReturnValue({ ns: 'a-namespace', name: 'a-buildconfig' }); - const renderResult = renderWithProviders(); - renderResult.findByText('Error Loading'); - renderResult.findByText('Edit BuildConfig'); - renderResult.findByText('Something went wrong'); + renderWithProviders(); + expect(await screen.findByText(/Error loading/i)).toBeVisible(); + expect(await screen.findByText('Something went wrong')).toBeVisible(); }); }); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/EnvironmentVariablesSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/EnvironmentVariablesSection.spec.tsx index 3fb2619099c..477b56d661d 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/EnvironmentVariablesSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/EnvironmentVariablesSection.spec.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FormikConfig } from 'formik'; import { Formik } from 'formik'; @@ -34,18 +34,18 @@ describe('EnvironmentVariablesSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section environment-variables'); - renderResult.getByText('Environment Variables'); - expect(renderResult.queryAllByPlaceholderText('Name')).toHaveLength(1); - expect(renderResult.queryAllByPlaceholderText('Value')).toHaveLength(1); - renderResult.getByText('Add value'); - renderResult.getByText('Add from ConfigMap or Secret'); + expect(screen.getByTestId('section environment-variables')).toBeInTheDocument(); + expect(screen.getByText('Environment Variables')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Name')).toHaveLength(1); + expect(screen.queryAllByPlaceholderText('Value')).toHaveLength(1); + expect(screen.getByText('Add value')).toBeVisible(); + expect(screen.getByText('Add from ConfigMap or Secret')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -62,16 +62,16 @@ describe('EnvironmentVariablesSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section environment-variables'); - renderResult.getByText('Environment Variables'); - expect(renderResult.queryAllByPlaceholderText('Name')).toHaveLength(3); - expect(renderResult.queryAllByPlaceholderText('Value')).toHaveLength(3); + expect(screen.getByTestId('section environment-variables')).toBeInTheDocument(); + expect(screen.getByText('Environment Variables')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Name')).toHaveLength(3); + expect(screen.queryAllByPlaceholderText('Value')).toHaveLength(3); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -104,16 +104,16 @@ describe('EnvironmentVariablesSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section environment-variables'); - renderResult.getByText('Environment Variables'); - expect(renderResult.queryAllByPlaceholderText('Name')).toHaveLength(3); - expect(renderResult.queryAllByPlaceholderText('Value')).toHaveLength(1); + expect(screen.getByTestId('section environment-variables')).toBeInTheDocument(); + expect(screen.getByText('Environment Variables')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Name')).toHaveLength(3); + expect(screen.queryAllByPlaceholderText('Value')).toHaveLength(1); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -127,20 +127,20 @@ describe('EnvironmentVariablesSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Add value')); - await user.click(renderResult.getByText('Add value')); + await user.click(screen.getByText('Add value')); + await user.click(screen.getByText('Add value')); - expect(renderResult.queryAllByPlaceholderText('Name')).toHaveLength(3); - expect(renderResult.queryAllByPlaceholderText('Value')).toHaveLength(3); + expect(screen.queryAllByPlaceholderText('Name')).toHaveLength(3); + expect(screen.queryAllByPlaceholderText('Value')).toHaveLength(3); - const [name1, name2, name3] = renderResult.queryAllByPlaceholderText('Name'); - const [value1, value2, value3] = renderResult.queryAllByPlaceholderText('Value'); + const [name1, name2, name3] = screen.queryAllByPlaceholderText('Name'); + const [value1, value2, value3] = screen.queryAllByPlaceholderText('Value'); await user.type(name1, 'env key 1'); await user.type(value1, 'env value 1'); @@ -150,7 +150,7 @@ describe('EnvironmentVariablesSection', () => { await user.type(value3, 'env value 3'); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/HooksSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/HooksSection.spec.tsx index f33ff66e789..7fe117d0303 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/HooksSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/HooksSection.spec.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FormikConfig } from 'formik'; import { Formik } from 'formik'; @@ -44,22 +44,22 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section hooks'); - renderResult.getByText('Run build hooks after image is built'); + expect(screen.getByTestId('section hooks')).toBeInTheDocument(); + expect(screen.getByText('Run build hooks after image is built')).toBeVisible(); - const [checkbox] = renderResult.getAllByRole('checkbox') as HTMLInputElement[]; + const [checkbox] = screen.getAllByRole('checkbox') as HTMLInputElement[]; expect(checkbox.checked).toBeFalsy(); // Exeept that the other input elements are not shown. - expect(renderResult.queryByTestId('type')).toBeFalsy(); - expect(renderResult.queryByTestId('Add command')).toBeFalsy(); - expect(renderResult.queryByTestId('Add argument')).toBeFalsy(); + expect(screen.queryByTestId('type')).toBeFalsy(); + expect(screen.queryByTestId('Add command')).toBeFalsy(); + expect(screen.queryByTestId('Add argument')).toBeFalsy(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -79,28 +79,26 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section hooks'); - renderResult.getByText('Run build hooks after image is built'); + expect(screen.getByTestId('section hooks')).toBeInTheDocument(); + expect(screen.getByText('Run build hooks after image is built')).toBeVisible(); - const [checkbox] = renderResult.getAllByRole('checkbox') as HTMLInputElement[]; + const [checkbox] = screen.getAllByRole('checkbox') as HTMLInputElement[]; expect(checkbox.checked).toBeFalsy(); await user.click(checkbox); - await waitFor(() => { - expect(checkbox.checked).toBeTruthy(); - expect(renderResult.getByTestId('type').textContent).toEqual('Command'); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(1); - renderResult.getByText('Add command'); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(0); - renderResult.getByText('Add argument'); - }); + expect(checkbox.checked).toBeTruthy(); + expect(screen.getByTestId('type').textContent).toEqual('Command'); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(1); + expect(screen.getByText('Add command')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(0); + expect(screen.getByText('Add argument')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -119,31 +117,31 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section hooks'); - renderResult.getByText('Run build hooks after image is built'); + expect(screen.getByTestId('section hooks')).toBeInTheDocument(); + expect(screen.getByText('Run build hooks after image is built')).toBeVisible(); - const [checkbox] = renderResult.getAllByRole('checkbox') as HTMLInputElement[]; + const [checkbox] = screen.getAllByRole('checkbox') as HTMLInputElement[]; expect(checkbox.checked).toBeTruthy(); await waitFor(() => { expect(checkbox.checked).toBeTruthy(); - expect(renderResult.getByTestId('type').textContent).toEqual('Command'); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(2); - renderResult.getByDisplayValue('command 1'); - renderResult.getByDisplayValue('command 2'); - renderResult.getByText('Add command'); - renderResult.getByDisplayValue('command 1'); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(2); - renderResult.getByDisplayValue('argument 1'); - renderResult.getByDisplayValue('argument 2'); - renderResult.getByText('Add argument'); }); + expect(screen.getByTestId('type').textContent).toEqual('Command'); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(2); + expect(screen.getByDisplayValue('command 1')).toBeVisible(); + expect(screen.getByDisplayValue('command 2')).toBeVisible(); + expect(screen.getByText('Add command')).toBeVisible(); + expect(screen.getByDisplayValue('command 1')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(2); + expect(screen.getByDisplayValue('argument 1')).toBeVisible(); + expect(screen.getByDisplayValue('argument 2')).toBeVisible(); + expect(screen.getByText('Add argument')).toBeVisible(); }); it('should not render commands when hook type is changed to script', async () => { @@ -161,24 +159,24 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - expect(renderResult.getByTestId('type').textContent).toEqual('Command'); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(2); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(2); + expect(screen.getByTestId('type').textContent).toEqual('Command'); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(2); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(2); - await user.click(renderResult.getByTestId('type')); - await user.click(renderResult.getByText('Shell script')); + await user.click(screen.getByTestId('type')); + await user.click(screen.getByText('Shell script')); await waitFor(() => { - expect(renderResult.baseElement.querySelector('textarea')).toBeTruthy(); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(0); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(2); + expect(screen.getByLabelText('Script')).toBeInTheDocument(); }); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(0); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(2); }); it('should not render commands when hook type is changed to argsOnly', async () => { @@ -196,24 +194,24 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - expect(renderResult.getByTestId('type').textContent).toEqual('Command'); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(2); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(2); + expect(screen.getByTestId('type').textContent).toEqual('Command'); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(2); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(2); - await user.click(renderResult.getByTestId('type')); - await user.click(renderResult.getByText('Arguments to default image entry point')); + await user.click(screen.getByTestId('type')); + await user.click(screen.getByText('Arguments to default image entry point')); await waitFor(() => { - expect(renderResult.baseElement.querySelector('textarea')).toBeFalsy(); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(0); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(2); + expect(screen.queryByLabelText('Script')).not.toBeInTheDocument(); }); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(0); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(2); }); it('should update formik data', async () => { @@ -231,36 +229,36 @@ describe('HooksSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - const [checkbox] = renderResult.getAllByRole('checkbox') as HTMLInputElement[]; + const [checkbox] = screen.getAllByRole('checkbox') as HTMLInputElement[]; await user.click(checkbox); // Wait for subform await waitFor(() => { expect(checkbox.checked).toBeTruthy(); - expect(renderResult.getByTestId('type').textContent).toEqual('Command'); - expect(renderResult.queryAllByPlaceholderText('Command')).toHaveLength(1); - renderResult.getByText('Add command'); - expect(renderResult.queryAllByPlaceholderText('Argument')).toHaveLength(0); - renderResult.getByText('Add argument'); }); + expect(screen.getByTestId('type').textContent).toEqual('Command'); + expect(screen.queryAllByPlaceholderText('Command')).toHaveLength(1); + expect(screen.getByText('Add command')).toBeVisible(); + expect(screen.queryAllByPlaceholderText('Argument')).toHaveLength(0); + expect(screen.getByText('Add argument')).toBeVisible(); // Fill out subform - const [command1] = renderResult.getAllByPlaceholderText('Command'); + const [command1] = screen.getAllByPlaceholderText('Command'); await user.type(command1, 'echo'); - await user.click(renderResult.getByText('Add argument')); - await user.click(renderResult.getByText('Add argument')); - const [argument1, argument2] = renderResult.getAllByPlaceholderText('Argument'); + await user.click(screen.getByText('Add argument')); + await user.click(screen.getByText('Add argument')); + const [argument1, argument2] = screen.getAllByPlaceholderText('Argument'); await user.type(argument1, 'hello'); await user.type(argument2, 'world'); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/ImagesSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/ImagesSection.spec.tsx index 29ddf879f1c..f55a878612a 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/ImagesSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/ImagesSection.spec.tsx @@ -1,13 +1,13 @@ import type { FC, ReactNode } from 'react'; import { FormGroup } from '@patternfly/react-core'; -import { render, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FormikConfig } from 'formik'; import { Formik } from 'formik'; import * as _ from 'lodash'; import { Provider } from 'react-redux'; import store from '@console/internal/redux'; -import { DropdownField } from '@console/shared/src'; +import DropdownField from '@console/shared/src/components/formik-fields/DropdownField'; import { BuildStrategyType } from '../../types'; import type { ImagesSectionFormData } from '../ImagesSection'; import ImagesSection from '../ImagesSection'; @@ -129,16 +129,16 @@ describe('ImagesSection', () => { it('should render form without subforms', () => { const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section images'); - renderResult.getByText('Images'); - renderResult.getByText('Build from'); - renderResult.getByText('Push to'); + expect(screen.getByTestId('section images')).toBeInTheDocument(); + expect(screen.getByText('Images')).toBeVisible(); + expect(screen.getByText('Build from')).toBeVisible(); + expect(screen.getByText('Push to')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -147,24 +147,23 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Dropdown titles - renderResult.getByText('Please select'); - renderResult.getByText('None'); + expect(screen.getByText('Please select')).toBeVisible(); + expect(screen.getByText('None')).toBeVisible(); // Open first dropdown - await user.click(renderResult.getByText('Please select')); + await user.click(screen.getByText('Please select')); // Assert options - const menuList = document.querySelector('[data-test="console-select-menu-list"]'); - expect(menuList).not.toBeNull(); - const options = menuList.querySelectorAll('li button'); - expect(Object.values(options).map((option) => option.textContent)).toEqual([ + const menuList = screen.getByTestId('console-select-menu-list'); + const options = within(menuList).getAllByRole('option'); + expect(options.map((option) => option.textContent)).toEqual([ 'Image Stream Tag', 'Image Stream Image', 'External container image', @@ -179,23 +178,22 @@ describe('ImagesSection', () => { initialValues.formData.images.strategyType = BuildStrategyType.Docker; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Dropdown titles - expect(renderResult.queryAllByText('None')).toHaveLength(2); + expect(screen.queryAllByText('None')).toHaveLength(2); // Open first dropdown - await user.click(renderResult.getAllByText('None')[0]); + await user.click(screen.getAllByText('None')[0]); // Assert options - const menuList = document.querySelector('[data-test="console-select-menu-list"]'); - expect(menuList).not.toBeNull(); - const options = menuList.querySelectorAll('li button'); - expect(Object.values(options).map((option) => option.textContent)).toEqual([ + const menuList = screen.getByTestId('console-select-menu-list'); + const options = within(menuList).getAllByRole('option'); + expect(options.map((option) => option.textContent)).toEqual([ 'None', 'Image Stream Tag', 'Image Stream Image', @@ -209,24 +207,23 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Dropdown titles - renderResult.getByText('Please select'); - renderResult.getByText('None'); + expect(screen.getByText('Please select')).toBeVisible(); + expect(screen.getByText('None')).toBeVisible(); // Open second dropdown - await user.click(renderResult.getByText('None')); + await user.click(screen.getByText('None')); // Assert options - const menuList = document.querySelector('[data-test="console-select-menu-list"]'); - expect(menuList).not.toBeNull(); - const options = menuList.querySelectorAll('li button'); - expect(Object.values(options).map((option) => option.textContent)).toEqual([ + const menuList = screen.getByTestId('console-select-menu-list'); + const options = within(menuList).getAllByRole('option'); + expect(options.map((option) => option.textContent)).toEqual([ 'None', 'Image Stream Tag', 'External container image', @@ -239,18 +236,18 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('Image Stream Tag')); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('Image Stream Tag')); - renderResult.getByText('Project'); - renderResult.getByText('Image Stream'); - renderResult.getByText('Tag'); + expect(screen.getByText('Project')).toBeVisible(); + expect(screen.getByText('Image Stream')).toBeVisible(); + expect(screen.getByText('Tag')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -259,16 +256,16 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('Image Stream Image')); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('Image Stream Image')); - expect(renderResult.getAllByRole('textbox')).toHaveLength(1); + expect(screen.getAllByRole('textbox')).toHaveLength(1); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -277,16 +274,16 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('External container image')); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('External container image')); - expect(renderResult.getAllByRole('textbox')).toHaveLength(1); + expect(screen.getAllByRole('textbox')).toHaveLength(1); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -295,25 +292,25 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('Image Stream Tag')); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('Image Stream Tag')); // Fill form - await user.click(renderResult.getByText('Select Project')); - await user.click(renderResult.getByText('project-a')); - await user.click(renderResult.getByText('Select Image Stream')); - await user.click(renderResult.getByText('imagestream-a')); - await user.click(renderResult.getByText('Select tag')); - await user.click(renderResult.getByText('latest')); + await user.click(screen.getByText('Select Project')); + await user.click(screen.getByText('project-a')); + await user.click(screen.getByText('Select Image Stream')); + await user.click(screen.getByText('imagestream-a')); + await user.click(screen.getByText('Select tag')); + await user.click(screen.getByText('latest')); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); @@ -369,19 +366,19 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Fill form - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('Image Stream Image')); - await user.type(renderResult.getByRole('textbox'), 'my-namespace/an-image'); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('Image Stream Image')); + await user.type(screen.getByRole('textbox'), 'my-namespace/an-image'); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); @@ -412,19 +409,19 @@ describe('ImagesSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Fill form - await user.click(renderResult.getByText('Please select')); - await user.click(renderResult.getByText('External container image')); - await user.type(renderResult.getByRole('textbox'), 'centos'); + await user.click(screen.getByText('Please select')); + await user.click(screen.getByText('External container image')); + await user.type(screen.getByRole('textbox'), 'centos'); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/NameSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/NameSection.spec.tsx index 40a32b3d701..0da9d436554 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/NameSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/NameSection.spec.tsx @@ -26,15 +26,15 @@ describe('NameSection', () => { const initialValues = {} as NameSectionFormData; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section name'); - renderResult.getByText('Name'); - const inputFields = renderResult.getAllByRole('textbox') as HTMLInputElement[]; + expect(screen.getByTestId('section name')).toBeInTheDocument(); + expect(screen.getByText('Name')).toBeVisible(); + const inputFields = screen.getAllByRole('textbox') as HTMLInputElement[]; expect(inputFields[0].getAttribute('label')).toEqual('Name'); expect(inputFields[0].disabled).toBeFalsy(); @@ -47,15 +47,15 @@ describe('NameSection', () => { const initialValues: NameSectionFormData = { formData: {} }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section name'); - renderResult.getByText('Name'); - const inputFields = renderResult.getAllByRole('textbox') as HTMLInputElement[]; + expect(screen.getByTestId('section name')).toBeInTheDocument(); + expect(screen.getByText('Name')).toBeVisible(); + const inputFields = screen.getAllByRole('textbox') as HTMLInputElement[]; expect(inputFields[0].getAttribute('label')).toEqual('Name'); expect(inputFields[0].disabled).toBeFalsy(); @@ -68,15 +68,15 @@ describe('NameSection', () => { const initialValues: NameSectionFormData = { formData: { name: 'initial name' } }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section name'); - renderResult.getByText('Name'); - const inputFields = renderResult.getAllByRole('textbox') as HTMLInputElement[]; + expect(screen.getByTestId('section name')).toBeInTheDocument(); + expect(screen.getByText('Name')).toBeVisible(); + const inputFields = screen.getAllByRole('textbox') as HTMLInputElement[]; expect(inputFields[0].getAttribute('label')).toEqual('Name'); expect(inputFields[0].disabled).toBeTruthy(); @@ -90,13 +90,13 @@ describe('NameSection', () => { const initialValues: NameSectionFormData = { formData: { name: '' } }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - const [nameInput] = renderResult.getAllByRole('textbox') as HTMLInputElement[]; + const [nameInput] = screen.getAllByRole('textbox') as HTMLInputElement[]; expect(nameInput.value).toEqual(''); expect(nameInput.disabled).toBeFalsy(); @@ -111,7 +111,7 @@ describe('NameSection', () => { expect(nameInput.disabled).toBeFalsy(); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/PolicySection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/PolicySection.spec.tsx index 3dda1d3d710..c457b2444b4 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/PolicySection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/PolicySection.spec.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FormikConfig } from 'formik'; import { Formik } from 'formik'; @@ -38,16 +38,16 @@ describe('PolicySectionFormData', () => { it('should render form', () => { const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section policy'); - renderResult.getByText('Policy'); - renderResult.getByText('Run policy'); - renderResult.getByTestId('dropdown run-policy'); + expect(screen.getByTestId('section policy')).toBeInTheDocument(); + expect(screen.getByText('Policy')).toBeVisible(); + expect(screen.getByText('Run policy')).toBeVisible(); + expect(screen.getByTestId('dropdown run-policy')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -56,17 +56,17 @@ describe('PolicySectionFormData', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Serial')); - await user.click(renderResult.getByText('Parallel')); + await user.click(screen.getByText('Serial')); + await user.click(screen.getByText('Parallel')); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); @@ -86,17 +86,17 @@ describe('PolicySectionFormData', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - await user.click(renderResult.getByText('Serial')); - await user.click(renderResult.getByText('Serial latest only')); + await user.click(screen.getByText('Serial')); + await user.click(screen.getByText('Serial latest only')); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx index df0d8aec088..b1f91ac7bd8 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FormikConfig } from 'formik'; import { Formik } from 'formik'; @@ -34,15 +34,15 @@ describe('SecretsSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section secrets'); - renderResult.getByText('Secrets'); - renderResult.getByText('Add secret'); + expect(screen.getByTestId('section secrets')).toBeInTheDocument(); + expect(screen.getByText('Secrets')).toBeVisible(); + expect(screen.getByText('Add secret')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -56,21 +56,21 @@ describe('SecretsSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Do not render table by default - expect(renderResult.queryByText('Secret')).toBeFalsy(); - expect(renderResult.queryByText('Mount point')).toBeFalsy(); + expect(screen.queryByText('Secret')).toBeFalsy(); + expect(screen.queryByText('Mount point')).toBeFalsy(); - await user.click(renderResult.getByText('Add secret')); + await user.click(screen.getByText('Add secret')); // Now expecting that there is a table to select a secret - renderResult.getByText('Secret'); - renderResult.getByText('Mount point'); + expect(screen.getByText('Secret')).toBeVisible(); + expect(screen.getByText('Mount point')).toBeVisible(); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -86,17 +86,17 @@ describe('SecretsSection', () => { }; const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Expecting that the table is automatically shown - renderResult.getByText('Secret'); - renderResult.getByText('Mount point'); - expect(renderResult.container.querySelectorAll('[data-test~="row"]')).toHaveLength(2); - expect(renderResult.queryAllByRole('textbox')).toHaveLength(2); + expect(screen.getByText('Secret')).toBeVisible(); + expect(screen.getByText('Mount point')).toBeVisible(); + // Each row contains a textbox for mount point + expect(screen.queryAllByRole('textbox')).toHaveLength(2); expect(onSubmit).toHaveBeenCalledTimes(0); }); diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx index 3dc06f8839c..ede168ba284 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx @@ -54,8 +54,7 @@ const Wrapper: FC = ({ children, ...formikConfig }) => ( ); -const getPatternFlyInputForLabel = (label: string) => - screen.getByText(label).parentElement.parentElement.parentElement.querySelector('input'); +const getPatternFlyInputForLabel = (label: string) => screen.getByRole('textbox', { name: label }); const initialValues: SourceSectionFormData = { formData: { @@ -109,20 +108,20 @@ describe('SourceSection', () => { it('should render form with minimal form data', () => { const onSubmit = jest.fn(); - const renderResult = render( + render( , ); - renderResult.getByTestId('section source'); - renderResult.getByText('Source type'); - renderResult.getByText('Please select your source type'); + expect(screen.getByTestId('section source')).toBeInTheDocument(); + expect(screen.getByText('Source type')).toBeVisible(); + expect(screen.getByText('Please select your source type')).toBeVisible(); // Expect that git input form and docker input field are not visible yet - expect(renderResult.queryByText('Git Repo URL')).toBeFalsy(); - expect(renderResult.queryByText('Show advanced Git options')).toBeFalsy(); - expect(renderResult.queryAllByText('Dockerfile')).toHaveLength(0); + expect(screen.queryByText('Git Repo URL')).toBeFalsy(); + expect(screen.queryByText('Show advanced Git options')).toBeFalsy(); + expect(screen.queryAllByText('Dockerfile')).toHaveLength(0); expect(onSubmit).toHaveBeenCalledTimes(0); }); @@ -131,21 +130,21 @@ describe('SourceSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); spyUseAccessReview.mockReturnValue([true]); - const renderResult = render( + render( , ); // Select git - await user.click(renderResult.getByText('Please select your source type')); - await user.click(renderResult.getByText('Git')); + await user.click(screen.getByText('Please select your source type')); + await user.click(screen.getByText('Git')); // Assert subforms await waitFor(() => { - expect(renderResult.queryByText('Git Repo URL')).toBeTruthy(); - expect(renderResult.queryByText('Show advanced Git options')).toBeTruthy(); - expect(renderResult.queryAllByText('Dockerfile')).toHaveLength(0); + expect(screen.getByText('Git Repo URL')).toBeVisible(); + expect(screen.getByText('Show advanced Git options')).toBeVisible(); + expect(screen.queryAllByText('Dockerfile')).toHaveLength(0); }); expect(onSubmit).toHaveBeenCalledTimes(0); @@ -155,24 +154,24 @@ describe('SourceSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); - const renderResult = render( + render( , ); // Select Dockerfile - expect(renderResult.queryAllByText('Dockerfile')).toHaveLength(0); - await user.click(renderResult.getByText('Please select your source type')); + expect(screen.queryAllByText('Dockerfile')).toHaveLength(0); + await user.click(screen.getByText('Please select your source type')); - expect(renderResult.queryAllByText('Dockerfile')).toHaveLength(1); - await user.click(renderResult.getByText('Dockerfile')); + expect(screen.queryAllByText('Dockerfile')).toHaveLength(1); + await user.click(screen.getByText('Dockerfile')); // Assert subforms await waitFor(() => { - expect(renderResult.queryByText('Git Repo URL')).toBeFalsy(); - expect(renderResult.queryByText('Show advanced Git options')).toBeFalsy(); - expect(renderResult.queryAllByText('Dockerfile')).toHaveLength(2); + expect(screen.queryByText('Git Repo URL')).toBeFalsy(); + expect(screen.queryByText('Show advanced Git options')).toBeFalsy(); + expect(screen.queryAllByText('Dockerfile')).toHaveLength(2); }); expect(onSubmit).toHaveBeenCalledTimes(0); @@ -182,16 +181,16 @@ describe('SourceSection', () => { const user = userEvent.setup(); const onSubmit = jest.fn(); spyUseAccessReview.mockReturnValue([true]); - const renderResult = render( + render( , ); // Fill out subform - await user.click(renderResult.getByText('Please select your source type')); - await user.click(renderResult.getByText('Git')); - await user.click(renderResult.getByText('Show advanced Git options')); + await user.click(screen.getByText('Please select your source type')); + await user.click(screen.getByText('Git')); + await user.click(screen.getByText('Show advanced Git options')); await user.type( getPatternFlyInputForLabel('Git Repo URL'), @@ -199,7 +198,7 @@ describe('SourceSection', () => { ); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); @@ -240,19 +239,19 @@ describe('SourceSection', () => { isBuilderS2I: false, values: {}, }); - const renderResult = render( + render( , ); // Fill out subform - await user.click(renderResult.getByText('Please select your source type')); - await user.click(renderResult.getByText('Dockerfile')); - await user.type(renderResult.getByRole('textbox'), 'FROM: centos\nRUN echo hello world'); + await user.click(screen.getByText('Please select your source type')); + await user.click(screen.getByText('Dockerfile')); + await user.type(screen.getByRole('textbox'), 'FROM: centos\nRUN echo hello world'); // Submit - const submitButton = renderResult.getByRole('button', { name: 'Submit' }); + const submitButton = screen.getByRole('button', { name: 'Submit' }); await user.click(submitButton); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); diff --git a/frontend/packages/dev-console/src/components/deployments/__tests__/AdvancedSection.spec.tsx b/frontend/packages/dev-console/src/components/deployments/__tests__/AdvancedSection.spec.tsx index 1444204c29f..0225d85c5b0 100644 --- a/frontend/packages/dev-console/src/components/deployments/__tests__/AdvancedSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/deployments/__tests__/AdvancedSection.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, cleanup } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import i18n from 'i18next'; import { setI18n } from 'react-i18next'; @@ -10,8 +10,7 @@ window.HTMLElement.prototype.scrollIntoView = () => {}; // scrollIntoView is not const handleSubmit = jest.fn(); -beforeEach(() => { - // Initialize i18n.services if it doesn't exist +function setupI18n() { if (!i18n.services) { (i18n as any).services = {}; } @@ -23,18 +22,23 @@ beforeEach(() => { nest: (str: string) => str, }; setI18n(i18n); +} +function renderAdvancedSection() { render( {() => } , ); -}); +} -afterEach(() => cleanup()); +beforeEach(() => { + setupI18n(); +}); describe('AdvancedSection', () => { it('should show Pause rollouts section on click', async () => { + renderAdvancedSection(); const user = userEvent.setup(); expect(screen.getByTestId('deployment-form-testid').textContent).toEqual( 'Click on the names to access advanced options for Pause rollouts and Scaling.', @@ -49,7 +53,7 @@ describe('AdvancedSection', () => { await user.click(pauseRolloutsButton); - expect(await screen.findByTestId('pause-rollouts')).toBeInTheDocument(); + expect(await screen.findByTestId('pause-rollouts')).toBeVisible(); expect(await screen.findByRole('checkbox')).toBeVisible(); expect(screen.getByTestId('deployment-form-testid').textContent).toContain( @@ -58,6 +62,7 @@ describe('AdvancedSection', () => { }); it('should show Scaling section on click', async () => { + renderAdvancedSection(); const user = userEvent.setup(); expect(screen.getByTestId('deployment-form-testid').textContent).toEqual( 'Click on the names to access advanced options for Pause rollouts and Scaling.', @@ -72,7 +77,7 @@ describe('AdvancedSection', () => { await user.click(scalingButton); - expect(await screen.findByTestId('scaling')).toBeInTheDocument(); + expect(await screen.findByTestId('scaling')).toBeVisible(); expect(await screen.findByRole('spinbutton', { name: /input/i })).toBeVisible(); expect(screen.getByTestId('deployment-form-testid').textContent).toContain( @@ -81,6 +86,7 @@ describe('AdvancedSection', () => { }); it('should not show message when both advanced options are clicked', async () => { + renderAdvancedSection(); const user = userEvent.setup(); expect(screen.getByTestId('deployment-form-testid').textContent).toEqual( 'Click on the names to access advanced options for Pause rollouts and Scaling.', @@ -94,7 +100,7 @@ describe('AdvancedSection', () => { await user.click(pauseRolloutsButton); - expect(await screen.findByTestId('pause-rollouts')).toBeInTheDocument(); + expect(await screen.findByTestId('pause-rollouts')).toBeVisible(); expect(await screen.findByRole('checkbox')).toBeVisible(); expect(screen.getByTestId('deployment-form-testid').textContent).toContain( @@ -110,7 +116,7 @@ describe('AdvancedSection', () => { await user.click(scalingButton); - expect(await screen.findByTestId('scaling')).toBeInTheDocument(); + expect(await screen.findByTestId('scaling')).toBeVisible(); expect(await screen.findByRole('spinbutton', { name: /input/i })).toBeVisible(); expect(screen.getByTestId('deployment-form-testid').textContent).not.toContain( diff --git a/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx b/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx index 9c12ec33397..501394b8c18 100644 --- a/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx +++ b/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { screen, cleanup, waitFor } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import i18n from 'i18next'; import * as _ from 'lodash'; @@ -52,12 +52,7 @@ const mockedContainerField = jest.mocked(ContainerField); const handleSubmit = jest.fn(); const handleCancel = jest.fn(); -beforeAll(() => { - mockedContainerField.mockImplementation(mockContainerField); -}); - -beforeEach(() => { - // Initialize i18n.services if it doesn't exist +function setupI18n() { if (!i18n.services) { (i18n as any).services = {}; } @@ -69,7 +64,9 @@ beforeEach(() => { nest: (str: string) => str, }; setI18n(i18n); +} +function renderDeploymentForm() { renderWithProviders( {(props) => ( @@ -82,19 +79,27 @@ beforeEach(() => { )} , ); +} + +beforeAll(() => { + mockedContainerField.mockImplementation(mockContainerField); }); -afterEach(() => cleanup()); +beforeEach(() => { + setupI18n(); +}); describe('EditDeploymentForm', () => { it('should show Form/YAML swicther with both options and Form view selected by default', async () => { - expect(await screen.findByTestId('synced-editor-field')).toBeInTheDocument(); + renderDeploymentForm(); + expect(await screen.findByTestId('synced-editor-field')).toBeVisible(); expect(await screen.findByRole('radio', { name: /form view/i })).toBeVisible(); expect(await screen.findByRole('radio', { name: /yaml view/i })).toBeVisible(); expect(screen.getByRole('radio', { name: /form view/i })).toBeChecked(); }); it('should show all the form sections wrt form/YAML view', async () => { + renderDeploymentForm(); const user = userEvent.setup(); const formButton = screen.getByRole('radio', { name: /form view/i }); const yamlButton = screen.getByRole('radio', { @@ -103,20 +108,21 @@ describe('EditDeploymentForm', () => { await user.click(yamlButton); - expect(await screen.findByTestId('yaml-editor')).toBeInTheDocument(); - expect(await screen.findByTestId('form-footer')).toBeInTheDocument(); + expect(await screen.findByTestId('yaml-editor')).toBeVisible(); + expect(await screen.findByTestId('form-footer')).toBeVisible(); await user.click(formButton); - expect(await screen.findByTestId('info-alert')).toBeInTheDocument(); - expect(await screen.findByTestId('deployment-strategy-section')).toBeInTheDocument(); - expect(await screen.findByTestId('images-section')).toBeInTheDocument(); - expect(await screen.findByTestId('environment-variables-section')).toBeInTheDocument(); - expect(await screen.findByTestId('advanced-options-section')).toBeInTheDocument(); - expect(await screen.findByTestId('form-footer')).toBeInTheDocument(); + expect(await screen.findByTestId('info-alert')).toBeVisible(); + expect(await screen.findByTestId('deployment-strategy-section')).toBeVisible(); + expect(await screen.findByTestId('images-section')).toBeVisible(); + expect(await screen.findByTestId('environment-variables-section')).toBeVisible(); + expect(await screen.findByTestId('advanced-options-section')).toBeVisible(); + expect(await screen.findByTestId('form-footer')).toBeVisible(); }); it('should disable save button and show loader on save button click', async () => { + renderDeploymentForm(); const user = userEvent.setup(); const saveButton = screen.getByRole('button', { name: /save/i, @@ -140,13 +146,14 @@ describe('EditDeploymentForm', () => { await user.click(saveButton); await waitFor(() => { - expect(saveButton.hasAttribute('disabled')).toBeTruthy(); - expect(saveButton.querySelector('.pf-v6-c-button__progress')).not.toBeNull(); + expect(saveButton).toBeDisabled(); + expect(within(saveButton).getByRole('progressbar')).toBeInTheDocument(); expect(handleSubmit).toHaveBeenCalledTimes(1); }); }); it('should load the form with current resource values on reload button click', async () => { + renderDeploymentForm(); const user = userEvent.setup(); const reloadButton = screen.getByRole('button', { name: /reload/i, @@ -170,6 +177,7 @@ describe('EditDeploymentForm', () => { }); it('should call handleCancel on Cancel button click ', async () => { + renderDeploymentForm(); const user = userEvent.setup(); const cancelButton = screen.getByRole('button', { name: /cancel/i, diff --git a/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentStrategySection.spec.tsx b/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentStrategySection.spec.tsx index b1a9925544d..72eb3e19e92 100644 --- a/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentStrategySection.spec.tsx +++ b/frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentStrategySection.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import store from '@console/internal/redux'; @@ -15,8 +15,6 @@ import { convertDeploymentToEditForm } from '../utils/deployment-utils'; const handleSubmit = jest.fn(); -afterEach(() => cleanup()); - describe('DeploymentStrategySection(DeploymentConfig)', () => { it('should show strategy fields based on strategy type selected', async () => { const user = userEvent.setup(); @@ -33,7 +31,7 @@ describe('DeploymentStrategySection(DeploymentConfig)', () => { , ); - expect(await screen.findByTestId('rollingParams')).toBeInTheDocument(); + expect(await screen.findByTestId('rollingParams')).toBeVisible(); const strategyDropdown = screen.getByRole('button', { name: /strategy type/i, @@ -45,7 +43,7 @@ describe('DeploymentStrategySection(DeploymentConfig)', () => { await user.click(recreateButton); - expect(await screen.findByTestId('recreateParams')).toBeInTheDocument(); + expect(await screen.findByTestId('recreateParams')).toBeVisible(); await user.click(strategyDropdown); @@ -53,7 +51,7 @@ describe('DeploymentStrategySection(DeploymentConfig)', () => { await user.click(customButton); - expect(await screen.findByTestId('customParams')).toBeInTheDocument(); + expect(await screen.findByTestId('customParams')).toBeVisible(); }); it('should render additional fields for Recreate strategy type', async () => { @@ -88,7 +86,7 @@ describe('DeploymentStrategySection(DeploymentConfig)', () => { await user.click(recreateButton); - expect(await screen.findByTestId('recreateParams')).toBeInTheDocument(); + expect(await screen.findByTestId('recreateParams')).toBeVisible(); const advancedSection = screen.getByText('Show additional parameters and lifecycle hooks'); @@ -153,7 +151,7 @@ describe('DeploymentStrategySection(Deployment)', () => { name: /strategy type/i, }); - expect(await screen.findByTestId('rollingUpdate')).toBeInTheDocument(); + expect(await screen.findByTestId('rollingUpdate')).toBeVisible(); await user.click(strategyDropdown); diff --git a/frontend/packages/dev-console/src/components/deployments/__tests__/EnvironmentVariablesSection.spec.tsx b/frontend/packages/dev-console/src/components/deployments/__tests__/EnvironmentVariablesSection.spec.tsx index ce35d85a88c..900a5b9d164 100644 --- a/frontend/packages/dev-console/src/components/deployments/__tests__/EnvironmentVariablesSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/deployments/__tests__/EnvironmentVariablesSection.spec.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as _ from 'lodash'; import { Provider } from 'react-redux'; @@ -29,7 +29,7 @@ beforeAll(() => { mockedContainerField.mockImplementation(MockContainerField); }); -beforeEach(() => +const renderEnvironmentVariablesSection = () => render( {() => ( @@ -38,13 +38,11 @@ beforeEach(() => )} , - ), -); - -afterEach(() => cleanup()); + ); describe('EnvironmentVariablesSection', () => { it('should show initial name value pairs', async () => { + renderEnvironmentVariablesSection(); const names = screen.getAllByPlaceholderText(/name/i).map((ele: HTMLInputElement) => ele.value); const values = screen .getAllByPlaceholderText(/value/i) @@ -54,6 +52,7 @@ describe('EnvironmentVariablesSection', () => { }); it('should add a new row when (+) Add button is clicked', async () => { + renderEnvironmentVariablesSection(); const user = userEvent.setup(); const addButton = screen.getByRole('button', { name: /add value/i }); @@ -71,6 +70,7 @@ describe('EnvironmentVariablesSection', () => { }); it('should add new row with resourse and key dropdowns when (+) Add ConfigMap or Secret button is clicked', async () => { + renderEnvironmentVariablesSection(); const user = userEvent.setup(); const addCMSButton = screen.getByRole('button', { name: /add from configmap or secret/i, @@ -91,6 +91,7 @@ describe('EnvironmentVariablesSection', () => { }); it('should remove row when (-) button is clicked', async () => { + renderEnvironmentVariablesSection(); const user = userEvent.setup(); const deleteButtons = screen.getAllByRole('button', { name: /delete/i }); diff --git a/frontend/packages/dev-console/src/components/deployments/__tests__/ImagesSection.spec.tsx b/frontend/packages/dev-console/src/components/deployments/__tests__/ImagesSection.spec.tsx index c20d4be6d37..06574fdce87 100644 --- a/frontend/packages/dev-console/src/components/deployments/__tests__/ImagesSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/deployments/__tests__/ImagesSection.spec.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react'; -import type { RenderResult } from '@testing-library/react'; -import { cleanup, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import store from '@console/internal/redux'; @@ -20,60 +19,57 @@ const mockedContainerField = jest.mocked(ContainerField); const handleSubmit = jest.fn(); -let renderResults: RenderResult = null; - beforeAll(() => { mockedContainerField.mockImplementation(MockContainerField); }); -beforeEach(() => { - renderResults = render( +const renderImagesSection = (resourceType = Resources.OpenShift) => + render( {() => ( - + )} , ); -}); - -afterEach(() => cleanup()); describe('ImagesSection', () => { it('should have image-stream-tag dropdowns or image-name text field based on fromImageStreamTagCheckbox value', async () => { + renderImagesSection(); const user = userEvent.setup(); const fromImageStreamTagCheckbox = screen.getByRole('checkbox', { name: /deploy image from an image stream tag/i, }); - expect(screen.queryByTestId('image-stream-tag')).toBeInTheDocument(); + expect(screen.getByTestId('image-stream-tag')).toBeVisible(); expect(screen.queryByTestId('image-name')).not.toBeInTheDocument(); await user.click(fromImageStreamTagCheckbox); await waitFor(() => { - expect(screen.queryByTestId('image-name')).toBeInTheDocument(); + expect(screen.getByTestId('image-name')).toBeVisible(); expect(screen.queryByTestId('image-stream-tag')).not.toBeInTheDocument(); }); }); it('should have the required trigger checkbox fields based on fromImageStreamTagCheckbox value', async () => { + renderImagesSection(); const user = userEvent.setup(); const fromImageStreamTagCheckbox = screen.getByRole('checkbox', { name: /deploy image from an image stream tag/i, }); expect( - screen.queryByRole('checkbox', { + screen.getByRole('checkbox', { name: /auto deploy when new Image is available/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); expect( - screen.queryByRole('checkbox', { + screen.getByRole('checkbox', { name: /auto deploy when deployment configuration changes/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); await user.click(fromImageStreamTagCheckbox); @@ -84,16 +80,17 @@ describe('ImagesSection', () => { }), ).not.toBeInTheDocument(); expect( - screen.queryByRole('checkbox', { + screen.getByRole('checkbox', { name: /auto deploy when deployment configuration changes/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); }); }); it('should have the required trigger checkbox fields based on resourceType', async () => { + const view = renderImagesSection(); const user = userEvent.setup(); - renderResults.rerender( + view.rerender( {() => ( @@ -108,10 +105,10 @@ describe('ImagesSection', () => { }); expect( - screen.queryByRole('checkbox', { + screen.getByRole('checkbox', { name: /auto deploy when new Image is available/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); expect( screen.queryByRole('checkbox', { name: /auto deploy when deployment configuration changes/i, @@ -135,16 +132,17 @@ describe('ImagesSection', () => { }); it('should have the advanced options expand/collapse button', async () => { + renderImagesSection(); const user = userEvent.setup(); const showAdvancedOptions = screen.getByRole('button', { name: /show advanced image options/i, }); expect( - screen.queryByRole('button', { + screen.getByRole('button', { name: /show advanced image options/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); expect( screen.queryByRole('button', { name: /hide advanced image options/i, @@ -160,10 +158,10 @@ describe('ImagesSection', () => { }), ).not.toBeInTheDocument(); expect( - screen.queryByRole('button', { + screen.getByRole('button', { name: /hide advanced image options/i, }), - ).toBeInTheDocument(); + ).toBeVisible(); }); }); }); diff --git a/frontend/packages/dev-console/src/components/import/toast/__tests__/ImportToastContent.spec.tsx b/frontend/packages/dev-console/src/components/import/toast/__tests__/ImportToastContent.spec.tsx index 8c369dd866d..8bab6fb2d47 100644 --- a/frontend/packages/dev-console/src/components/import/toast/__tests__/ImportToastContent.spec.tsx +++ b/frontend/packages/dev-console/src/components/import/toast/__tests__/ImportToastContent.spec.tsx @@ -38,15 +38,15 @@ describe('ImportToastContent', () => { }); it('should render null when no deployed resources', () => { - const { container } = render(); + render(); - expect(container.firstChild).toBeNull(); + expect(screen.queryByText(/created successfully/i)).not.toBeInTheDocument(); }); it('should render null when deployed resources is undefined', () => { - const { container } = render(); + render(); - expect(container.firstChild).toBeNull(); + expect(screen.queryByText(/created successfully/i)).not.toBeInTheDocument(); }); it('should show success message without route when no route provided', () => { diff --git a/frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx b/frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx index a8c85f237cc..02d9e2ab4da 100644 --- a/frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx +++ b/frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx @@ -19,9 +19,9 @@ describe('SecureRouteFields', () => { it('should render Secure Route Fields component', () => { mockUsePreferredRoutingOptions.mockReturnValue([{}, () => {}, true]); render(); - expect(screen.queryByTestId('secure-route-checkbox')).not.toBeNull(); - expect(screen.queryByTestId('insecure-traffic')).not.toBeNull(); - expect(screen.queryByTestId('tls-termination')).not.toBeNull(); + expect(screen.getByTestId('secure-route-checkbox')).toBeVisible(); + expect(screen.getByTestId('insecure-traffic')).toBeVisible(); + expect(screen.getByTestId('tls-termination')).toBeVisible(); }); it('should render skeleton if usePreferredRoutingOptions is not loaded', () => { @@ -51,8 +51,8 @@ describe('SecureRouteFields', () => { await user.click(inSecureTraffic); await waitFor(() => { expect(screen.queryByRole('option', { name: /Allow/i })).toBeNull(); - expect(screen.queryByRole('option', { name: /None/i })).not.toBeNull(); - expect(screen.queryByRole('option', { name: /Redirect/i })).not.toBeNull(); + expect(screen.getByRole('option', { name: /None/i })).toBeVisible(); + expect(screen.getByRole('option', { name: /Redirect/i })).toBeVisible(); }); }); @@ -71,9 +71,9 @@ describe('SecureRouteFields', () => { const inSecureTraffic = screen.getByTestId('insecure-traffic'); await user.click(inSecureTraffic); await waitFor(() => { - expect(screen.queryByRole('option', { name: /Allow/i })).not.toBeNull(); - expect(screen.queryByRole('option', { name: /None/i })).not.toBeNull(); - expect(screen.queryByRole('option', { name: /Redirect/i })).not.toBeNull(); + expect(screen.getByRole('option', { name: /Allow/i })).toBeVisible(); + expect(screen.getByRole('option', { name: /None/i })).toBeVisible(); + expect(screen.getByRole('option', { name: /Redirect/i })).toBeVisible(); }); }); }); diff --git a/frontend/packages/eslint-plugin-console/index.js b/frontend/packages/eslint-plugin-console/index.js index b03b170952e..3a1be7e9228 100644 --- a/frontend/packages/eslint-plugin-console/index.js +++ b/frontend/packages/eslint-plugin-console/index.js @@ -13,6 +13,10 @@ module.exports = { // Augmenting configs: choose one or more jest: require('./lib/config/jest'), + + // React Testing Library (test/spec files only). Also merged into `react-typescript-prettier`. + 'testing-library-tests': require('./lib/config/testing-library-tests'), + node: require('./lib/config/node'), // Add JSON linting (optional) @@ -23,7 +27,7 @@ module.exports = { // ...or use the pre-composed configurations representing common code archetypes (choose one): - // Common web preset: React, TypeScript, Prettier + // Common web preset: React, TypeScript, Prettier, Testing Library on tests 'react-typescript-prettier': { extends: [ 'plugin:console/react', @@ -31,6 +35,7 @@ module.exports = { // TODO enable when we stop using jest with jasmine types // 'plugin:console/jest', 'plugin:console/json', + 'plugin:console/testing-library-tests', 'plugin:console/prettier', ], rules: { diff --git a/frontend/packages/eslint-plugin-console/lib/config/testing-library-tests.js b/frontend/packages/eslint-plugin-console/lib/config/testing-library-tests.js new file mode 100644 index 00000000000..fcef4fe1301 --- /dev/null +++ b/frontend/packages/eslint-plugin-console/lib/config/testing-library-tests.js @@ -0,0 +1,28 @@ +/** + * React Testing Library (RTL) test-only ESLint override. + * Used as `plugin:console/testing-library-tests` from the root and `package` .eslintrc stacks. + * + * Tooling: **ESLint 8** (via `eslint-plugin-console`) and **eslint-plugin-testing-library** 6+ / 7.x. + * `extends: ['plugin:testing-library/react']` applies the upstream React ruleset, which already + * includes (among others): + * - `no-node-access`, `no-unnecessary-act`, `render-result-naming-convention` + * - `await-async-events` (userEvent), `no-await-sync-events` (fireEvent) + * - `prefer-screen-queries`, `prefer-presence-queries`, `prefer-find-by` + * - `no-wait-for-side-effects` (replaces the removed `no-wait-for-empty-callback` in older plugin majors) + * + * The following **additional** rules are turned on, which are not enabled in `plugin:testing-library/react` by default. + */ +module.exports = { + overrides: [ + { + files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + plugins: ['testing-library'], + extends: ['plugin:testing-library/react'], + rules: { + 'testing-library/prefer-explicit-assert': 'error', + 'testing-library/prefer-user-event': 'error', + 'testing-library/prefer-user-event-setup': 'error', + }, + }, + ], +}; diff --git a/frontend/packages/eslint-plugin-console/package.json b/frontend/packages/eslint-plugin-console/package.json index 049ea690c32..8ba3034a2ae 100644 --- a/frontend/packages/eslint-plugin-console/package.json +++ b/frontend/packages/eslint-plugin-console/package.json @@ -19,7 +19,7 @@ "eslint-plugin-cypress": "^3.6.0", "eslint-plugin-graphql": "^4.0.0", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-jest": "^29.0.0", "eslint-plugin-json": "^2.0.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-n": "^17.24.0", @@ -28,6 +28,7 @@ "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-sort-class-members": "^1.20.0", + "eslint-plugin-testing-library": "^7.2.0", "eslint-plugin-tsdoc": "^0.4.0", "merge": "1.2.1" } diff --git a/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetailsPage.spec.tsx b/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetailsPage.spec.tsx index 6cd6f943f16..997f56df853 100644 --- a/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetailsPage.spec.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetailsPage.spec.tsx @@ -3,18 +3,15 @@ import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-ut import HelmReleaseDetailsPage from '../HelmReleaseDetailsPage'; describe('HelmReleaseDetailsPage', () => { - beforeEach(() => { + it('should render the namespaced page content region', () => { renderWithProviders(); - }); - - it('should render the NamespaceBar component', () => { - // NamespacedPage renders a namespace bar with co-namespace-bar class - expect(document.querySelector('.co-namespace-bar')).toBeTruthy(); + expect(screen.getByRole('region', { name: 'Page content' })).toBeInTheDocument(); }); it('should render the loading state initially', () => { + renderWithProviders(); // Component shows loading state initially before HelmReleaseDetails loads - expect(screen.getByTestId('loading-box')).toBeTruthy(); - expect(screen.getByTestId('loading-indicator')).toBeTruthy(); + expect(screen.getByTestId('loading-box')).toBeInTheDocument(); + expect(screen.getByTestId('loading-indicator')).toBeInTheDocument(); }); }); diff --git a/frontend/packages/helm-plugin/src/components/details-page/overview/__tests__/HelmReleaseOverview.spec.tsx b/frontend/packages/helm-plugin/src/components/details-page/overview/__tests__/HelmReleaseOverview.spec.tsx index 4bf7931168c..37b4d47bf98 100644 --- a/frontend/packages/helm-plugin/src/components/details-page/overview/__tests__/HelmReleaseOverview.spec.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/overview/__tests__/HelmReleaseOverview.spec.tsx @@ -34,20 +34,20 @@ describe('HelmReleaseOverview', () => { it('should render the Section Heading for the Overview page', () => { spyUseAccessReview.mockReturnValue([true]); renderWithProviders(); - expect(screen.getByText('Helm Release details')).toBeTruthy(); + expect(screen.getByText('Helm Release details')).toBeInTheDocument(); }); it('should render the ResourceSummary component', () => { spyUseAccessReview.mockReturnValue([true]); renderWithProviders(); - expect(document.querySelector('[data-test-id="resource-summary"]')).toBeTruthy(); + expect(screen.getByRole('link', { name: 'xyz' })).toBeInTheDocument(); }); it('should render the HelmChartSummary component', () => { spyUseAccessReview.mockReturnValue([true]); renderWithProviders(); // HelmChartSummary typically renders chart information - expect(screen.getByText('Chart version')).toBeTruthy(); - expect(screen.getByText('App version')).toBeTruthy(); + expect(screen.getByText('Chart version')).toBeInTheDocument(); + expect(screen.getByText('App version')).toBeInTheDocument(); }); }); diff --git a/frontend/packages/helm-plugin/src/components/details-page/resources/__tests__/HelmResourcesList.spec.tsx b/frontend/packages/helm-plugin/src/components/details-page/resources/__tests__/HelmResourcesList.spec.tsx index bb9c1642fa2..ad654b2ea13 100644 --- a/frontend/packages/helm-plugin/src/components/details-page/resources/__tests__/HelmResourcesList.spec.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/resources/__tests__/HelmResourcesList.spec.tsx @@ -4,7 +4,7 @@ import HelmResourcesList from '../HelmReleaseResourcesList'; import { helmReleaseResourceData } from './helm-release-resource.data'; describe('HelmResourcesList', () => { - beforeEach(() => { + const renderComponent = () => { renderWithProviders( { Header={() => null} />, ); - }); + }; it('should render the ConsoleDataView component', () => { + renderComponent(); // Check that the ConsoleDataView is rendered by looking for the data view table expect(screen.getByTestId('data-view-table')).toBeTruthy(); }); it('should render the proper Headers in the Resources tab', () => { + renderComponent(); const expectedHelmResourcesHeader: string[] = ['Name', 'Type', 'Status', 'Created']; // Check that all expected headers are rendered (use getAllByText to handle multiple matches) @@ -31,6 +33,7 @@ describe('HelmResourcesList', () => { }); it('should render resource data correctly', () => { + renderComponent(); // Check that the resource name is rendered expect(screen.getByText('dotnet')).toBeTruthy(); diff --git a/frontend/packages/helm-plugin/src/providers/__tests__/helm-detection-provider.spec.ts b/frontend/packages/helm-plugin/src/providers/__tests__/helm-detection-provider.spec.ts index ee168affa60..9de221def00 100644 --- a/frontend/packages/helm-plugin/src/providers/__tests__/helm-detection-provider.spec.ts +++ b/frontend/packages/helm-plugin/src/providers/__tests__/helm-detection-provider.spec.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import { HttpError } from '@console/dynamic-plugin-sdk/src/utils/error/http-error'; import { settleAllPromises } from '@console/dynamic-plugin-sdk/src/utils/promise'; import { fetchK8s } from '@console/internal/graphql/client'; @@ -90,11 +90,10 @@ describe('useDetectHelmChartRepositories', () => { settleAllPromisesMock.mockReturnValue( Promise.resolve([[helmChartRepositoryList, { items: [] }], [], []]), ); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(1); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(1); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, true]); }); @@ -102,11 +101,10 @@ describe('useDetectHelmChartRepositories', () => { settleAllPromisesMock.mockReturnValue( Promise.resolve([[{ items: [] }, helmChartRepositoryList], [], []]), ); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(1); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(1); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, true]); }); @@ -114,11 +112,10 @@ describe('useDetectHelmChartRepositories', () => { settleAllPromisesMock.mockReturnValue( Promise.resolve([[helmChartRepositoryList, helmChartRepositoryList], [], []]), ); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(1); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(1); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, true]); }); @@ -126,11 +123,10 @@ describe('useDetectHelmChartRepositories', () => { settleAllPromisesMock.mockReturnValue( Promise.resolve([[{ items: [] }, { items: [] }], [], []]), ); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(1); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(1); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, false]); }); @@ -145,11 +141,10 @@ describe('useDetectHelmChartRepositories', () => { // settleAllPromises mock returns errors in rejectedReasons array settleAllPromisesMock.mockReturnValue(Promise.resolve([[], [error404, error200], []])); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(2); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(2); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, false]); }); @@ -159,11 +154,10 @@ describe('useDetectHelmChartRepositories', () => { } as Response); // settleAllPromises mock returns errors in rejectedReasons array settleAllPromisesMock.mockReturnValue(Promise.resolve([[], [error200, error200], []])); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(setFeatureFlag).toHaveBeenCalledTimes(2); }); - expect(setFeatureFlag).toHaveBeenCalledTimes(2); expect(setFeatureFlag.mock.calls[0]).toEqual([FLAG_OPENSHIFT_HELM, undefined]); }); @@ -186,11 +180,10 @@ describe('useDetectHelmChartRepositories', () => { } as Response); // settleAllPromises mock returns errors in rejectedReasons array settleAllPromisesMock.mockReturnValue(Promise.resolve([[], [error404, error200], []])); - const { rerender } = renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); - await act(async () => { - rerender(); + renderHook(() => useDetectHelmChartRepositories(setFeatureFlag)); + await waitFor(() => { + expect(fetchK8sMock).toHaveBeenCalledTimes(4); }); - expect(fetchK8sMock).toHaveBeenCalledTimes(4); jest.advanceTimersByTime(20 * 1000); expect(fetchK8sMock).toHaveBeenCalledTimes(4); }); diff --git a/frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx b/frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx index 169f49e6237..9daef4f6aef 100644 --- a/frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx +++ b/frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx @@ -19,14 +19,14 @@ describe('TopologyHelmReleaseResourcesPanel', () => { const manifestResources = mockManifest; it('should render the correct number of resource categories', () => { - const { container } = renderWithProviders( + renderWithProviders( , ); - // Check that the component renders successfully - expect(container.firstChild).toBeTruthy(); + expect(screen.getByText('ConfigMaps')).toBeInTheDocument(); + expect(screen.getByText('Deployments')).toBeInTheDocument(); }); }); @@ -44,6 +44,6 @@ describe('TopologyHelmReleaseNotesPanel', () => { mockUserPreference.mockReturnValue(['light', jest.fn(), true]); renderWithProviders(); // Check for empty state text or message - expect(screen.getByText(/no release notes available/i)).toBeTruthy(); + expect(screen.getByText(/no release notes available/i)).toBeInTheDocument(); }); }); diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSink.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSink.spec.tsx index 4309063ad38..9abb4592214 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSink.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSink.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { useConsoleSelector } from '@console/shared/src/hooks/useConsoleSelector'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkAlert.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkAlert.spec.tsx index 37b4f1d1406..593c80bacf0 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkAlert.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkAlert.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import EventSinkAlert from '../EventSinkAlert'; diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkForm.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkForm.spec.tsx index 416898c4121..65aaf3d91bf 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkForm.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkForm.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render } from '@testing-library/react'; import { formikFormProps } from '@console/shared/src/test-utils/formik-props-utils'; diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkPage.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkPage.spec.tsx index fe81115be74..c735419be5d 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkPage.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSinkPage.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as Router from 'react-router'; import { useEventSinkStatus } from '../../../hooks/useEventSinkStatus'; diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSource.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSource.spec.tsx index 0b2ed5a17a1..cab98e4cbfd 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSource.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSource.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { EVENT_SOURCE_CONTAINER_KIND } from '../../../const'; import { getEventSourceIcon } from '../../../utils/get-knative-icon'; diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceAlert.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceAlert.spec.tsx index 7827b3d89c2..3538cff5e7c 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceAlert.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceAlert.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import EventSourceAlert from '../EventSourceAlert'; diff --git a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceForm.spec.tsx b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceForm.spec.tsx index 94d97798c64..e85c07ed850 100644 --- a/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceForm.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/__tests__/EventSourceForm.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render } from '@testing-library/react'; import { formikFormProps } from '@console/shared/src/test-utils/formik-props-utils'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/EventSinkSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/EventSinkSection.spec.tsx index 054763383eb..6c248b2832e 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/EventSinkSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/EventSinkSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { useFormikContext } from 'formik'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/KafkaSinkSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/KafkaSinkSection.spec.tsx index 14ac9c37e59..2a12249b1a7 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/KafkaSinkSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sinks/__tests__/KafkaSinkSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import KafkaSinkSection from '../KafkaSinkSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ApiServerSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ApiServerSection.spec.tsx index 3533fbc14fc..e1506ed718e 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ApiServerSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ApiServerSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import ApiServerSection from '../ApiServerSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ContainerSourceSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ContainerSourceSection.spec.tsx index 1260b9b7d83..91cc11c86d5 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ContainerSourceSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/ContainerSourceSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import ContainerSourceSection from '../ContainerSourceSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/EventSourceSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/EventSourceSection.spec.tsx index aba493f77c7..f9b914ea403 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/EventSourceSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/EventSourceSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import type { FormikValues } from 'formik'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceNetSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceNetSection.spec.tsx index e83ad2943ab..672ed3d6108 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceNetSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceNetSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import KafkaSourceNetSection from '../KafkaSourceNetSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceSection.spec.tsx index f88c7dca8c9..4b6575dcd6f 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/KafkaSourceSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import KafkaSourceSection from '../KafkaSourceSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/SinkBindingSection.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/SinkBindingSection.spec.tsx index 74552962fd0..80a6b81a5a5 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/SinkBindingSection.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/__tests__/SinkBindingSection.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import SinkBindingSection from '../SinkBindingSection'; diff --git a/frontend/packages/knative-plugin/src/components/add/event-sources/form-fields/__tests__/SinkResources.spec.tsx b/frontend/packages/knative-plugin/src/components/add/event-sources/form-fields/__tests__/SinkResources.spec.tsx index 43db5cb66ab..628a1072c39 100644 --- a/frontend/packages/knative-plugin/src/components/add/event-sources/form-fields/__tests__/SinkResources.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/add/event-sources/form-fields/__tests__/SinkResources.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import SinkResources from '../SinkResources'; diff --git a/frontend/packages/knative-plugin/src/components/eventing/__tests__/EventingListPage.spec.tsx b/frontend/packages/knative-plugin/src/components/eventing/__tests__/EventingListPage.spec.tsx index 49a3fd58720..11f270e9141 100644 --- a/frontend/packages/knative-plugin/src/components/eventing/__tests__/EventingListPage.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/eventing/__tests__/EventingListPage.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as Router from 'react-router'; import * as ConsoleShared from '@console/shared'; diff --git a/frontend/packages/knative-plugin/src/components/knatify/__tests__/KnatifyForm.spec.tsx b/frontend/packages/knative-plugin/src/components/knatify/__tests__/KnatifyForm.spec.tsx index d81dae00df1..6a09df332e7 100644 --- a/frontend/packages/knative-plugin/src/components/knatify/__tests__/KnatifyForm.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/knatify/__tests__/KnatifyForm.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render } from '@testing-library/react'; import { formikFormProps } from '@console/shared/src/test-utils/formik-props-utils'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewList.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewList.spec.tsx index 1848ea318b2..63bf2859533 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewList.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewList.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import { sampleKnativeConfigurations } from '../../../topology/__tests__/topology-knative-test-data'; import ConfigurationsOverviewList from '../ConfigurationsOverviewList'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewListItem.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewListItem.spec.tsx index 46758b71071..eb13452775a 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewListItem.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/ConfigurationsOverviewListItem.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import { referenceForModel } from '@console/internal/module/k8s'; import { ConfigurationModel } from '../../../models'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/DeploymentOverviewList.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/DeploymentOverviewList.spec.tsx index 3f950cdf6c2..66066ba3c15 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/DeploymentOverviewList.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/DeploymentOverviewList.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { revisionObj } from '../../../topology/__tests__/topology-knative-test-data'; import { usePodsForRevisions } from '../../../utils/usePodsForRevisions'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventPubSubResources.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventPubSubResources.spec.tsx index e33878c7b9c..b3c0701ea39 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventPubSubResources.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventPubSubResources.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import type { K8sResourceKind } from '@console/internal/module/k8s'; import type { Subscriber } from 'packages/knative-plugin/src/topology/topology-types'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceOwnedList.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceOwnedList.spec.tsx index b665ab3bf4a..caa897c0aae 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceOwnedList.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceOwnedList.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import type { K8sResourceKind } from '@console/internal/module/k8s'; import { EVENT_SOURCE_SINK_BINDING_KIND, KNATIVE_EVENT_SOURCE_APIGROUP } from '../../../const'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceResources.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceResources.spec.tsx index 2865f9b48b3..ed0f5589543 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceResources.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/EventSourceResources.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import * as _ from 'lodash'; import { referenceForModel } from '@console/internal/module/k8s'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRouteSplitListItem.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRouteSplitListItem.spec.tsx index 0a45a59b823..4fd7cfd842a 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRouteSplitListItem.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRouteSplitListItem.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { MockKnativeResources } from '../../../topology/__tests__/topology-knative-test-data'; import { getKnativeRoutesLinks } from '../../../utils/resource-overview-utils'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRoutesOverviewListItem.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRoutesOverviewListItem.spec.tsx index b13131ec1ee..d895e7054ff 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRoutesOverviewListItem.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/KSRoutesOverviewListItem.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import { referenceForModel } from '@console/internal/module/k8s'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewList.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewList.spec.tsx index 93ecf952490..cdb1bc271c5 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewList.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewList.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as _ from 'lodash'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewListItem.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewListItem.spec.tsx index 398d412cab1..47bedb3714f 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewListItem.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewListItem.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { MockKnativeResources } from '../../../topology/__tests__/topology-knative-test-data'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewList.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewList.spec.tsx index f6990b932e9..f63fd10757d 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewList.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewList.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render, screen } from '@testing-library/react'; import type { K8sResourceKind } from '@console/internal/module/k8s/types'; import { MockKnativeResources } from '../../../topology/__tests__/topology-knative-test-data'; diff --git a/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewListItem.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewListItem.spec.tsx index 12c4188521c..e7f6af08e57 100644 --- a/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewListItem.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/__tests__/RoutesOverviewListItem.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import { MockKnativeResources } from '../../../topology/__tests__/topology-knative-test-data'; import { getKnativeRoutesLinks } from '../../../utils/resource-overview-utils'; diff --git a/frontend/packages/knative-plugin/src/components/overview/serving-list/__tests__/ServingListPage.spec.tsx b/frontend/packages/knative-plugin/src/components/overview/serving-list/__tests__/ServingListPage.spec.tsx index 10db99f9b46..d9d10b8f8a6 100644 --- a/frontend/packages/knative-plugin/src/components/overview/serving-list/__tests__/ServingListPage.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/overview/serving-list/__tests__/ServingListPage.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as Router from 'react-router'; import ServingListPage from '../ServingListsPage'; diff --git a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/DynamicResourceLink.spec.tsx b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/DynamicResourceLink.spec.tsx index 7917f51a7ec..12d08d9e05e 100644 --- a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/DynamicResourceLink.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/DynamicResourceLink.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import DynamicResourceLink from '../DynamicResourceLink'; diff --git a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/SubscriptionDetails.spec.tsx b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/SubscriptionDetails.spec.tsx index fd58ce159d9..244965bb06e 100644 --- a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/SubscriptionDetails.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/SubscriptionDetails.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import { subscriptionData } from '../../../../utils/__tests__/knative-eventing-data'; diff --git a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/TriggerDetails.spec.tsx b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/TriggerDetails.spec.tsx index 3018e09b195..08379737723 100644 --- a/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/TriggerDetails.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/pub-sub/details/__test__/TriggerDetails.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import { triggerData } from '../../../../utils/__tests__/knative-eventing-data'; diff --git a/frontend/packages/knative-plugin/src/components/revisions/__tests__/RevisionRow.spec.tsx b/frontend/packages/knative-plugin/src/components/revisions/__tests__/RevisionRow.spec.tsx index ed7cdfb0085..d737fa9bee6 100644 --- a/frontend/packages/knative-plugin/src/components/revisions/__tests__/RevisionRow.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/revisions/__tests__/RevisionRow.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import type { RowFunctionArgs } from '@console/internal/components/factory'; diff --git a/frontend/packages/knative-plugin/src/components/routes/__tests__/RouteRow.spec.tsx b/frontend/packages/knative-plugin/src/components/routes/__tests__/RouteRow.spec.tsx index f672483beed..5e5af0300ce 100644 --- a/frontend/packages/knative-plugin/src/components/routes/__tests__/RouteRow.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/routes/__tests__/RouteRow.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import type { RowFunctionArgs } from '@console/internal/components/factory'; diff --git a/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx b/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx index b10c78829ae..b762d2ea4e4 100644 --- a/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/services/__tests__/ServiceRow.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import { render } from '@testing-library/react'; import * as _ from 'lodash'; import type { RowFunctionArgs } from '@console/internal/components/factory'; diff --git a/frontend/packages/knative-plugin/src/components/sink-pubsub/__tests__/SinkPubsub.spec.tsx b/frontend/packages/knative-plugin/src/components/sink-pubsub/__tests__/SinkPubsub.spec.tsx index 24b78f4fba7..ec12547ca2b 100644 --- a/frontend/packages/knative-plugin/src/components/sink-pubsub/__tests__/SinkPubsub.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/sink-pubsub/__tests__/SinkPubsub.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render } from '@testing-library/react'; import { diff --git a/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSource.spec.tsx b/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSource.spec.tsx index 28e4c815870..5bee9dbf7a2 100644 --- a/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSource.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSource.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render } from '@testing-library/react'; import { sampleEventSourceSinkbinding } from '../../../topology/__tests__/topology-knative-test-data'; diff --git a/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx b/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx index 5f9491c4291..51659a63154 100644 --- a/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx b/frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx index db8486fb29f..4cf8edc5a15 100644 --- a/frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx +++ b/frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-container, testing-library/no-node-access -- Mocked components require container queries */ import type { ComponentProps } from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/frontend/packages/knative-plugin/src/topology/sidebar/__tests__/knative-resource-tab-sections.spec.tsx b/frontend/packages/knative-plugin/src/topology/sidebar/__tests__/knative-resource-tab-sections.spec.tsx index 8b3bce0219c..74aa15cc8d2 100644 --- a/frontend/packages/knative-plugin/src/topology/sidebar/__tests__/knative-resource-tab-sections.spec.tsx +++ b/frontend/packages/knative-plugin/src/topology/sidebar/__tests__/knative-resource-tab-sections.spec.tsx @@ -7,14 +7,14 @@ describe('EventSinkSourceSection', () => { it('should show message no output resources foung if none exists', () => { const mockEventSink = _.omit(eventSinkKamelet, 'spec.source'); render(); - screen.getByTestId('event-sink-text'); + expect(screen.getByTestId('event-sink-text')).toBeVisible(); expect(screen.queryByTestId('event-sink-sb-res')).toBeNull(); expect(screen.queryByTestId('event-sink-target-uri')).toBeNull(); }); it('should show ResourceLink as output resource if source is ref', () => { render(); - screen.getByTestId('event-sink-sb-res'); + expect(screen.getByTestId('event-sink-sb-res')).toBeVisible(); expect(screen.queryByTestId('event-sink-text')).toBeNull(); expect(screen.queryByTestId('event-sink-target-uri')).toBeNull(); }); @@ -23,7 +23,7 @@ describe('EventSinkSourceSection', () => { const mockEventSink = _.omit(eventSinkKamelet, 'spec.source.ref'); mockEventSink.spec.source.uri = 'http://abc.com'; render(); - screen.getByTestId('event-sink-target-uri'); + expect(screen.getByTestId('event-sink-target-uri')).toBeVisible(); expect(screen.queryByTestId('event-sink-text')).toBeNull(); expect(screen.queryByTestId('event-sink-sb-res')).toBeNull(); }); diff --git a/frontend/packages/operator-lifecycle-manager/src/components/__tests__/install-plan.spec.tsx b/frontend/packages/operator-lifecycle-manager/src/components/__tests__/install-plan.spec.tsx index 85457d253b2..f074affcc6f 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/__tests__/install-plan.spec.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/__tests__/install-plan.spec.tsx @@ -72,11 +72,13 @@ describe('InstallPlanTableRow', () => { , ); - const installPlanLinks = screen.getAllByText(installPlan.metadata.name); + const installPlanLinks = screen.getAllByRole('link', { name: installPlan.metadata.name }); + // eslint-disable-next-line testing-library/no-node-access -- Multiple links with same name require href filtering const installPlanLink = installPlanLinks.find((link) => link.getAttribute('href')?.includes('InstallPlan'), ); expect(installPlanLink).toBeVisible(); + expect(installPlanLink).toHaveAttribute('href', expect.stringContaining('InstallPlan')); }); it('renders install plan namespace', () => { @@ -136,7 +138,8 @@ describe('InstallPlanTableRow', () => { ); const csvName = installPlan.spec.clusterServiceVersionNames[0]; - const csvLinks = screen.getAllByText(csvName); + const csvLinks = screen.getAllByRole('link', { name: csvName }); + // eslint-disable-next-line testing-library/no-node-access -- Multiple links with same name require href filtering const csvLink = csvLinks.find((link) => link.getAttribute('href')?.includes('ClusterServiceVersion'), ); @@ -381,10 +384,8 @@ describe('InstallPlanDetails', () => { renderWithProviders(); - const previewButton = screen.getByRole('button', { name: 'Preview InstallPlan' }); - expect(previewButton).toBeVisible(); - - const link = previewButton.closest('a'); + const link = screen.getByRole('link', { name: 'Preview InstallPlan' }); + expect(link).toBeVisible(); expect(link).toHaveAttribute( 'href', `/k8s/ns/default/${referenceForModel(InstallPlanModel)}/${ diff --git a/frontend/packages/shipwright-plugin/src/components/buildrun-duration/__tests__/BuildRunDuration.spec.tsx b/frontend/packages/shipwright-plugin/src/components/buildrun-duration/__tests__/BuildRunDuration.spec.tsx index c1de90a2f4b..53dc6631328 100644 --- a/frontend/packages/shipwright-plugin/src/components/buildrun-duration/__tests__/BuildRunDuration.spec.tsx +++ b/frontend/packages/shipwright-plugin/src/components/buildrun-duration/__tests__/BuildRunDuration.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { incompleteBuildRun } from '../../../__tests__/mock-data-v1beta1'; import type { BuildRun } from '../../../types'; import BuildRunDuration, { getDuration } from '../BuildRunDuration'; @@ -33,8 +33,8 @@ describe('getDuration', () => { describe('BuildRunDuration', () => { it('should render a placeholder for incomplete BuildRuns', () => { const buildRun: BuildRun = incompleteBuildRun; - const renderResult = render(); - expect(renderResult.container.textContent).toEqual('-'); + render(); + expect(screen.getByText('-')).toBeInTheDocument(); }); it('should render a the time between startTime and completionTime', () => { @@ -51,8 +51,8 @@ describe('BuildRunDuration', () => { completionTime: '2022-06-06T13:53:26Z', }, }; - const renderResult = render(); - expect(renderResult.container.textContent).toEqual('52 second'); + render(); + expect(screen.getByText('52 second')).toBeInTheDocument(); }); it('should render a the time between startTime and NOW if completionTime is missing', () => { @@ -69,7 +69,7 @@ describe('BuildRunDuration', () => { }, }; now = '2022-06-06T13:54:34Z'; - const renderResult = render(); - expect(renderResult.container.textContent).toEqual('2 minute'); + render(); + expect(screen.getByText('2 minute')).toBeInTheDocument(); }); }); diff --git a/frontend/packages/shipwright-plugin/src/components/buildrun-status/__tests__/BuildRunStatus.spec.tsx b/frontend/packages/shipwright-plugin/src/components/buildrun-status/__tests__/BuildRunStatus.spec.tsx index a78cda576bf..acacd0ec8a5 100644 --- a/frontend/packages/shipwright-plugin/src/components/buildrun-status/__tests__/BuildRunStatus.spec.tsx +++ b/frontend/packages/shipwright-plugin/src/components/buildrun-status/__tests__/BuildRunStatus.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { incompleteBuildRun, pendingBuildRun, @@ -21,19 +21,19 @@ describe('getBuildRunStatus', () => { describe('BuildRunStatus', () => { it('should render the right status', () => { - const renderResult = render(); - expect(renderResult.container.textContent).toEqual('Unknown'); + const { rerender } = render(); + expect(screen.getByText('Unknown')).toBeInTheDocument(); - renderResult.rerender(); - expect(renderResult.container.textContent).toEqual('Pending'); + rerender(); + expect(screen.getByText('Pending')).toBeInTheDocument(); - renderResult.rerender(); - expect(renderResult.container.textContent).toEqual('Running'); + rerender(); + expect(screen.getByText('Running')).toBeInTheDocument(); - renderResult.rerender(); - expect(renderResult.container.textContent).toEqual('Succeeded'); + rerender(); + expect(screen.getByText('Succeeded')).toBeInTheDocument(); - renderResult.rerender(); - expect(renderResult.container.textContent).toEqual('Failed'); + rerender(); + expect(screen.getByText('Failed')).toBeInTheDocument(); }); }); diff --git a/frontend/packages/shipwright-plugin/src/components/logs/Logs.tsx b/frontend/packages/shipwright-plugin/src/components/logs/Logs.tsx index 403bf699aed..422180d1642 100644 --- a/frontend/packages/shipwright-plugin/src/components/logs/Logs.tsx +++ b/frontend/packages/shipwright-plugin/src/components/logs/Logs.tsx @@ -129,7 +129,7 @@ const Logs: FC = ({ }, [autoScroll, render, addContentAndScroll]); return ( -
+

{name}

{error && ( = ({ /> )}
-
+
diff --git a/frontend/packages/shipwright-plugin/src/components/logs/MultiStreamLogs.tsx b/frontend/packages/shipwright-plugin/src/components/logs/MultiStreamLogs.tsx index 0ae5148b383..a58d879ec7d 100644 --- a/frontend/packages/shipwright-plugin/src/components/logs/MultiStreamLogs.tsx +++ b/frontend/packages/shipwright-plugin/src/components/logs/MultiStreamLogs.tsx @@ -50,7 +50,11 @@ export const MultiStreamLogs: FC = ({ const containerStatus: ContainerStatus[] = resource?.status?.containerStatuses ?? []; return ( <> -
+
{taskName} {stillFetching && ( diff --git a/frontend/packages/shipwright-plugin/src/components/logs/__tests__/Logs.spec.tsx b/frontend/packages/shipwright-plugin/src/components/logs/__tests__/Logs.spec.tsx index d3de2d1b09d..49d05e41064 100644 --- a/frontend/packages/shipwright-plugin/src/components/logs/__tests__/Logs.spec.tsx +++ b/frontend/packages/shipwright-plugin/src/components/logs/__tests__/Logs.spec.tsx @@ -51,14 +51,14 @@ describe('logs component', () => { }); it('should show the logs block based on the render prop', async () => { - const { container, rerender } = render(); - const logsElement = container.querySelector('.odc-logs') as HTMLElement; - expect(logsElement).not.toBeNull(); - expect(logsElement.style.display).toBe('none'); + const { rerender } = render(); + const logsElement = screen.getByTestId('odc-logs'); + expect(logsElement).toHaveStyle({ display: 'none' }); rerender(); await waitFor(() => { - expect(logsElement.style.display).toBe(''); + // React sets `display: ''` when `render` is true; the computed style is not `none`. + expect(screen.getByTestId('odc-logs').style.display).not.toBe('none'); }); }); @@ -103,13 +103,12 @@ describe('logs component', () => { it('should display log content when fetched successfully', async () => { const logContent = 'log line 1\nlog line 2\nlog line 3'; mockCoFetchText.mockResolvedValue(logContent); - const { container } = render(); + render(); // Wait for fetch to complete and throttled content update (throttle is 1000ms) await waitFor( () => { - const contentElement = container.querySelector('.odc-logs__content') as HTMLElement; - expect(contentElement).not.toBeNull(); + const contentElement = screen.getByTestId('odc-logs__content'); const actualContent = contentElement.innerText || contentElement.textContent || ''; // Handle case where innerText might start as undefined const normalizedContent = actualContent.replace(/^undefined/, ''); @@ -127,7 +126,7 @@ describe('logs component', () => { it('should handle autoScroll prop being false', async () => { const logContent = 'test log content'; mockCoFetchText.mockResolvedValue(logContent); - const { container } = render(); + render(); await waitFor(() => { expect(mockCoFetchText).toHaveBeenCalled(); @@ -136,8 +135,7 @@ describe('logs component', () => { // With autoScroll false, scrollIntoView should not be called as frequently // The content should still be added though await waitFor(() => { - const contentElement = container.querySelector('.odc-logs__content') as HTMLElement; - expect(contentElement).not.toBeNull(); + expect(screen.getByTestId('odc-logs__content')).toBeInTheDocument(); }); }); }); diff --git a/frontend/packages/shipwright-plugin/src/components/logs/__tests__/MultiStreamLogs.spec.tsx b/frontend/packages/shipwright-plugin/src/components/logs/__tests__/MultiStreamLogs.spec.tsx index 5b493bea419..93413e45008 100644 --- a/frontend/packages/shipwright-plugin/src/components/logs/__tests__/MultiStreamLogs.spec.tsx +++ b/frontend/packages/shipwright-plugin/src/components/logs/__tests__/MultiStreamLogs.spec.tsx @@ -1,5 +1,5 @@ import type { ComponentProps } from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { cloneDeep } from 'lodash'; import { MultiStreamLogs } from '../MultiStreamLogs'; import { podData } from './logs-test-data'; @@ -49,7 +49,7 @@ jest.mock('../Logs', () => { default: jest.fn(({ container }: { container: { name: string } }) => { return ReactMock.createElement( 'div', - { className: 'odc-logs', 'data-testid': `logs-${container.name}` }, + { className: 'odc-logs', 'data-test': `logs-${container.name}` }, container.name, ); }), @@ -70,25 +70,24 @@ describe('MultiStreamLogs', () => { it('should not render logs when containers is not present', () => { props.resource.spec.containers = []; - const { container } = render(); - const logsElements = container.querySelectorAll('.odc-logs'); - expect(logsElements.length).toBe(0); + render(); + // Mocked Logs use data-testid "logs-${container.name}"; no containers => none rendered. + expect(screen.queryByTestId('logs-step-oc')).not.toBeInTheDocument(); }); it('should render inline loading based on logs completion', () => { - const { container } = render(); - const taskNameElement = container.querySelector('[data-test-id="logs-taskName"]'); - expect(taskNameElement).not.toBeNull(); - expect(taskNameElement?.textContent).toBe('step-oc'); - expect( - taskNameElement?.querySelector('.odc-multi-stream-logs__taskName__loading-indicator'), - ).toBeNull(); + render(); + const taskNameRegion = screen.getByTestId('multi-stream-logs-task-name'); + // When stillFetching is false, only the task name is present (no loading sub-tree from mock data). + expect(taskNameRegion).toHaveTextContent('step-oc'); }); it('should render number of logs equal to number of containers', () => { - const containersLength = props.resource.spec.containers.length; - const { container } = render(); - const logsElements = container.querySelectorAll('.odc-logs'); - expect(logsElements.length).toBe(containersLength); + const { containers: containerSpecs } = props.resource.spec; + render(); + expect(containerSpecs.length).toBeGreaterThan(0); + containerSpecs.forEach((c) => { + expect(screen.getByTestId(`logs-${c.name}`)).toBeInTheDocument(); + }); }); }); diff --git a/frontend/packages/topology/src/components/application-panel/__tests__/ApplicationGroupResource.spec.tsx b/frontend/packages/topology/src/components/application-panel/__tests__/ApplicationGroupResource.spec.tsx index b04af6212e0..3688a78e16f 100644 --- a/frontend/packages/topology/src/components/application-panel/__tests__/ApplicationGroupResource.spec.tsx +++ b/frontend/packages/topology/src/components/application-panel/__tests__/ApplicationGroupResource.spec.tsx @@ -24,13 +24,13 @@ describe('', () => { }); it('should not render component when resourcesData is empty', () => { - const { container } = renderComponent({ + renderComponent({ title: 'Deployments', group: 'a', resourcesData: [], }); - expect(container.firstChild).toBeNull(); + expect(screen.queryByText('Deployments')).not.toBeInTheDocument(); }); it('should render "View all" link if resources exceed MAX_RESOURCES', () => { diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/CloudShellDrawer.spec.tsx b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/CloudShellDrawer.spec.tsx index 94a8c1602ae..b2cf39d0362 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/CloudShellDrawer.spec.tsx +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/CloudShellDrawer.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { useFlag } from '@console/shared/src/hooks/useFlag'; import { useIsCloudShellExpanded } from '@console/webterminal-plugin/src/redux/reducers/cloud-shell-selectors'; import { CloudShellDrawer } from '../CloudShellDrawer'; @@ -31,38 +31,38 @@ describe('CloudShellDrawer', () => { mockUseFlag.mockReturnValue(true); mockUseIsCloudShellExpanded.mockReturnValue(true); - const wrapper = render( + render(

Console webapp

, ); - expect(wrapper.getByText('Console webapp')).toBeInTheDocument(); - expect(wrapper.getByText('Terminal content')).toBeInTheDocument(); + expect(screen.getByText('Console webapp')).toBeVisible(); + expect(screen.getByText('Terminal content')).toBeVisible(); }); it('should still render children when the Drawer is closed', () => { mockUseFlag.mockReturnValue(true); mockUseIsCloudShellExpanded.mockReturnValue(false); - const wrapper = render( + render(

Console webapp

, ); - expect(wrapper.getByTestId('body').innerHTML).toEqual('Console webapp'); - expect(wrapper.queryByText('Terminal content')).not.toBeInTheDocument(); + expect(screen.getByTestId('body').innerHTML).toEqual('Console webapp'); + expect(screen.queryByText('Terminal content')).not.toBeInTheDocument(); }); it('should render children even if web terminal is not available', () => { mockUseFlag.mockReturnValue(false); mockUseIsCloudShellExpanded.mockReturnValue(true); - const wrapper = render( + render(

Console webapp

, ); - expect(wrapper.getByText('Console webapp')).toBeInTheDocument(); - expect(wrapper.queryByText('Terminal content')).not.toBeInTheDocument(); + expect(screen.getByText('Console webapp')).toBeVisible(); + expect(screen.queryByText('Terminal content')).not.toBeInTheDocument(); }); }); diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx index 864bfe97e46..b1cacf49f09 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx @@ -1,3 +1,4 @@ +import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { sendActivityTick } from '../cloud-shell-utils'; @@ -51,48 +52,40 @@ describe('MultiTabTerminal', () => { }); it('should initially load with only one console', () => { - const multiTabTerminalWrapper = renderWithProviders(); + renderWithProviders(); - expect(multiTabTerminalWrapper.getAllByText('Terminal content').length).toBe(1); + expect(screen.getAllByText('Terminal content').length).toBe(1); }); it('should add terminals on add terminal icon click', async () => { - const multiTabTerminalWrapper = renderWithProviders(); + renderWithProviders(); - const addTerminalButton = multiTabTerminalWrapper.getByLabelText('Add new tab'); + const addTerminalButton = screen.getByLabelText('Add new tab'); await user.click(addTerminalButton); - expect(multiTabTerminalWrapper.getAllByText('Terminal content').length).toBe(2); + expect(screen.getAllByText('Terminal content').length).toBe(2); await user.click(addTerminalButton); await user.click(addTerminalButton); - expect(multiTabTerminalWrapper.getAllByText('Terminal content').length).toBe(4); + expect(screen.getAllByText('Terminal content').length).toBe(4); }); it('should not allow more than 8 terminals', async () => { - const multiTabTerminalWrapper = renderWithProviders(); - - await clickMultipleTimes( - user, - () => multiTabTerminalWrapper.queryByLabelText('Add new tab'), - 8, - ); - expect(multiTabTerminalWrapper.getAllByText('Terminal content')).toHaveLength(8); - expect(multiTabTerminalWrapper.queryByLabelText('Add new tab')).toBeNull(); + renderWithProviders(); + + await clickMultipleTimes(user, () => screen.queryByLabelText('Add new tab'), 8); + expect(screen.getAllByText('Terminal content')).toHaveLength(8); + expect(screen.queryByLabelText('Add new tab')).toBeNull(); }); it('should remove terminals on remove terminal icon click', async () => { - const multiTabTerminalWrapper = renderWithProviders(); + renderWithProviders(); - await clickMultipleTimes( - user, - () => multiTabTerminalWrapper.queryByLabelText('Add new tab'), - 8, - ); + await clickMultipleTimes(user, () => screen.queryByLabelText('Add new tab'), 8); - const closeTerminalTabs = () => multiTabTerminalWrapper.getAllByLabelText('Close terminal tab'); + const closeTerminalTabs = () => screen.getAllByLabelText('Close terminal tab'); const tabs = closeTerminalTabs(); expect(tabs[7]).toBeTruthy(); await user.click(tabs[7]); - expect(multiTabTerminalWrapper.getAllByText('Terminal content').length).toBe(7); + expect(screen.getAllByText('Terminal content').length).toBe(7); const tabs2 = closeTerminalTabs(); expect(tabs2[6]).toBeTruthy(); @@ -101,7 +94,7 @@ describe('MultiTabTerminal', () => { const tabs3 = closeTerminalTabs(); expect(tabs3[5]).toBeTruthy(); await user.click(tabs3[5]); - expect(multiTabTerminalWrapper.getAllByText('Terminal content').length).toBe(5); + expect(screen.getAllByText('Terminal content').length).toBe(5); }); jest.clearAllTimers(); diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/TerminalLoadingBox.spec.tsx b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/TerminalLoadingBox.spec.tsx index 2084a5e3fda..06bb45eea4b 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/TerminalLoadingBox.spec.tsx +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/TerminalLoadingBox.spec.tsx @@ -1,14 +1,16 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import TerminalLoadingBox from '../TerminalLoadingBox'; describe('TerminalLoadingBox', () => { it('should render the default message if message prop is not there', () => { - const { getByText } = render(); - getByText('Connecting to your OpenShift command line terminal ...'); + render(); + expect( + screen.getByText('Connecting to your OpenShift command line terminal ...'), + ).toBeVisible(); }); it('should render the message prop', () => { - const { getByText } = render(); - getByText('Lorem ipsum'); + render(); + expect(screen.getByText('Lorem ipsum')).toBeVisible(); }); }); diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/useCloudShellAvailable.spec.ts b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/useCloudShellAvailable.spec.ts index 9bf5453b87e..0a480b1576a 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/useCloudShellAvailable.spec.ts +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/useCloudShellAvailable.spec.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import { checkTerminalAvailable } from '../cloud-shell-utils'; import { useCloudShellAvailable } from '../useCloudShellAvailable'; // Need to import useFlag after useCloudShellAvailable for the mock to work correctly. FInd out why? @@ -33,10 +33,10 @@ describe('useCloudShellAvailable', () => { useFlagMock.mockReturnValue(true); checkTerminalAvailableMock.mockReturnValue(Promise.reject()); const { result, rerender } = renderHook(() => useCloudShellAvailable()); - await act(async () => { - rerender(); + rerender(); + await waitFor(() => { + expect(result.current).toBe(false); }); - expect(result.current).toBe(false); }); it('should be available if flag is set and service is available', async () => { @@ -44,9 +44,9 @@ describe('useCloudShellAvailable', () => { checkTerminalAvailableMock.mockReturnValue(Promise.resolve()); const { result, rerender } = renderHook(() => useCloudShellAvailable()); - await act(async () => { - rerender(); + rerender(); + await waitFor(() => { + expect(result.current).toBe(true); }); - expect(result.current).toBe(true); }); }); diff --git a/frontend/public/components/__tests__/container.spec.tsx b/frontend/public/components/__tests__/container.spec.tsx index b92ae01770d..0458ef04899 100644 --- a/frontend/public/components/__tests__/container.spec.tsx +++ b/frontend/public/components/__tests__/container.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import * as ReactRouter from 'react-router'; import { testPodInstance } from '../../../__mocks__/k8sResourcesMocks'; @@ -67,9 +67,7 @@ describe('ContainerDetails', () => { name: testPodInstance.spec.containers[0].name, }); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByText('crash-app')).toBeVisible(); // Verify "Waiting" appears in both the page heading and details section @@ -83,9 +81,7 @@ describe('ContainerDetails', () => { name: 'non-existing-container', }); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('heading', { name: '404: Page Not Found' })).toBeVisible(); expect(screen.getByText("We couldn't find that page.")).toBeVisible(); @@ -99,9 +95,7 @@ describe('ContainerDetails', () => { name: testPodInstance.spec.containers[0].name, }); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); expect(screen.getByRole('progressbar', { name: 'Contents' })).toBeVisible(); }); diff --git a/frontend/public/components/__tests__/create-yaml.spec.tsx b/frontend/public/components/__tests__/create-yaml.spec.tsx index 0fa3f852ea5..602c72370c1 100644 --- a/frontend/public/components/__tests__/create-yaml.spec.tsx +++ b/frontend/public/components/__tests__/create-yaml.spec.tsx @@ -1,5 +1,5 @@ import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { safeDump } from 'js-yaml'; import { CreateYAMLInner } from '../create-yaml'; @@ -37,11 +37,9 @@ describe('CreateYAMLInner', () => { describe('Loading States', () => { it('verifies the loading box when kindsInFlight is true', async () => { - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText('Loading...')).toBeVisible(); expect(screen.queryByText('YAML Editor:')).not.toBeInTheDocument(); @@ -51,11 +49,9 @@ describe('CreateYAMLInner', () => { it('verifies the loading box when templates are not resolved', async () => { mockUseResolvedExtensions.mockReturnValue([[], false]); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText('Loading...')).toBeVisible(); expect(screen.queryByText('404: Page Not Found')).not.toBeInTheDocument(); @@ -64,11 +60,9 @@ describe('CreateYAMLInner', () => { it('verifies the 404 error when kindObj is null and not loading', async () => { mockUseResolvedExtensions.mockReturnValue([[], true]); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText('404: Page Not Found')).toBeVisible(); expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); @@ -78,27 +72,23 @@ describe('CreateYAMLInner', () => { describe('YAML Editor Rendering', () => { it('renders YAML editor with correct props for Pod creation', async () => { - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText(/YAML Editor:/)).toBeVisible(); expect(screen.getByText(/Create Pod/)).toBeVisible(); }); it('renders YAML editor in edit mode when isCreate is false', async () => { - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText(/YAML Editor:/)).toBeVisible(); expect(screen.getByText(/Edit Pod/)).toBeVisible(); }); @@ -109,11 +99,9 @@ describe('CreateYAMLInner', () => { labelKey: 'ConfigMap', }; - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); expect(screen.getByText(/Create ConfigMap/)).toBeVisible(); }); @@ -124,16 +112,14 @@ describe('CreateYAMLInner', () => { const templateObj = { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'cool-app' } }; const customTemplate = safeDump(templateObj); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const editorText = screen.getByText(/Resource:/).textContent; expect(editorText).toContain('"name":"cool-app"'); @@ -143,11 +129,9 @@ describe('CreateYAMLInner', () => { }); it('verifies the creation of sample object using default YAML template for model when no template provided', async () => { - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); // Verify the default template is used and includes expected Pod properties const editorText = screen.getByText(/Resource:/).textContent; diff --git a/frontend/public/components/__tests__/pod.spec.tsx b/frontend/public/components/__tests__/pod.spec.tsx index 25e13459b63..11979df71f7 100644 --- a/frontend/public/components/__tests__/pod.spec.tsx +++ b/frontend/public/components/__tests__/pod.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { ContainerRow, PodDetailsList, PodsDetailsPage } from '../pod'; @@ -28,46 +28,36 @@ describe(`PodsDetailsPage`, () => { mockUseLocation.mockReturnValue(mockLocationObject); }); - it('verifies pod details page renders successfully for users', async () => { - let container; - await act(async () => { - const renderResult = renderWithProviders(); - container = renderResult.container; - }); - expect(container).toBeInTheDocument(); + it('verifies pod details page renders successfully for users', () => { + const view = renderWithProviders(); + expect(view.container).toBeInTheDocument(); }); - it('shows well-structured pod details layout for users', async () => { - let container; - await act(async () => { - const renderResult = renderWithProviders(); - container = renderResult.container; - }); - expect(container).toBeInTheDocument(); + it('shows well-structured pod details layout for users', () => { + const view = renderWithProviders(); + expect(view.container).toBeInTheDocument(); }); }); describe('PodDetailsList', () => { - const renderPodDetailsList = async (testPod = testPodInstance) => { - await act(async () => { - renderWithProviders(); - }); + const renderPodDetailsList = (testPod = testPodInstance) => { + renderWithProviders(); }; - it('verifies pod status as CrashLoopBackOff', async () => { - await renderPodDetailsList(); + it('verifies pod status as CrashLoopBackOff', () => { + renderPodDetailsList(); expect(screen.getByText(/Crash.*Loop.*Back.*Off/)).toBeVisible(); }); it("verifies restart policy as 'Always restart'", async () => { - await renderPodDetailsList(); + renderPodDetailsList(); expect(screen.getByText('Always restart')).toBeVisible(); }); it('verifies active deadline seconds as not configured', async () => { - await renderPodDetailsList(); + renderPodDetailsList(); expect(screen.getByText('Not configured')).toBeVisible(); }); @@ -76,25 +66,25 @@ describe('PodDetailsList', () => { ...testPodInstance, spec: { ...testPodInstance.spec, activeDeadlineSeconds: 10 }, }; - await renderPodDetailsList(podWithDeadline); + renderPodDetailsList(podWithDeadline); expect(screen.getByText(/10.*second/i)).toBeVisible(); }); it('verifies the Pod IP address', async () => { - await renderPodDetailsList(); + renderPodDetailsList(); expect(screen.getByText('10.131.0.48')).toBeVisible(); }); it('verifies a link to the node', async () => { - await renderPodDetailsList(); + renderPodDetailsList(); expect(screen.getByText('ip-10-0-132-2.ec2.internal')).toBeVisible(); }); it('verifies image pull secrets', async () => { - await renderPodDetailsList(); + renderPodDetailsList(); expect(screen.getByText('default-dockercfg-fcb57')).toBeVisible(); }); @@ -104,7 +94,7 @@ describe('PodDetailsList', () => { ...testPodInstance, spec: { ...testPodInstance.spec, runtimeClassName: 'test-runtime' }, }; - await renderPodDetailsList(podWithRuntime); + renderPodDetailsList(podWithRuntime); expect(screen.getByText('test-runtime')).toBeVisible(); }); @@ -116,51 +106,49 @@ describe('ContainerRow', () => { image: 'quay.io/openshifttest/crashpod', }; - const renderContainerRow = async () => { - await act(async () => { - renderWithProviders( - - - - -
, - ); - }); + const renderContainerRow = () => { + renderWithProviders( + + + + +
, + ); }; it('verifies the container name as a link', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getByText('crash-app')).toBeVisible(); }); it('verifies the container image', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getByText('quay.io/openshifttest/crashpod')).toBeVisible(); }); it('verifies the container restart count', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getByText('29')).toBeVisible(); }); it('verifies container state information', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getByText('Waiting')).toBeVisible(); }); it('verifies the container exit code as dash when not applicable', async () => { - await renderContainerRow(); + renderContainerRow(); const dashElements = screen.getAllByText('-'); expect(dashElements.length).toBeGreaterThan(0); }); it('verifies container timing information', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getAllByText(/Feb 9, 2022/)[0]).toBeVisible(); expect(screen.getAllByText(/11:20|6:20|4:50/)[0]).toBeVisible(); @@ -168,7 +156,7 @@ describe('ContainerRow', () => { }); it('verifies container ready status displays as Not ready', async () => { - await renderContainerRow(); + renderContainerRow(); expect(screen.getByText('Not ready')).toBeVisible(); }); diff --git a/frontend/public/components/__tests__/resource-dropdown.spec.tsx b/frontend/public/components/__tests__/resource-dropdown.spec.tsx index a956256403f..a0b15f8435a 100644 --- a/frontend/public/components/__tests__/resource-dropdown.spec.tsx +++ b/frontend/public/components/__tests__/resource-dropdown.spec.tsx @@ -1,4 +1,5 @@ -import { render, screen, fireEvent, within } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Map as ImmutableMap } from 'immutable'; import { useUserPreference } from '@console/shared/src/hooks/useUserPreference'; @@ -79,26 +80,32 @@ const renderDropdown = (models: K8sKind[], groupToVersionMap = {}, props = {}) = ); }; -const openDropdown = () => { - fireEvent.click(screen.getByRole('combobox')); +const openDropdown = async (user: ReturnType) => { + await user.click(screen.getByRole('combobox')); }; -const typeInSearch = (text: string) => { +const typeInSearch = async (user: ReturnType, text: string) => { const input = screen.getByRole('combobox'); - fireEvent.change(input, { target: { value: text } }); + await user.clear(input); + if (text) { + await user.type(input, text); + } }; const getMenuItems = () => screen.getAllByRole('menuitem'); describe('ResourceListDropdown', () => { + let user: ReturnType; + beforeEach(() => { jest.clearAllMocks(); mockUseUserPreference.mockReturnValue(['[]', jest.fn(), true]); defaultProps.onChange = jest.fn(); + user = userEvent.setup(); }); describe('preferred version filtering', () => { - it('shows only the preferred version when groupToVersionMap is provided', () => { + it('shows only the preferred version when groupToVersionMap is provided', async () => { const models = [ makeModel('Deployment', 'apps', 'v1'), makeModel('Deployment', 'apps', 'v1beta1'), @@ -108,28 +115,28 @@ describe('ResourceListDropdown', () => { }; renderDropdown(models, groupToVersionMap); - openDropdown(); + await openDropdown(user); const items = getMenuItems(); expect(items).toHaveLength(1); expect(items[0]).toHaveTextContent('Deployment'); }); - it('shows all versions when no preferred version exists', () => { + it('shows all versions when no preferred version exists', async () => { const models = [ makeModel('Deployment', 'apps', 'v1'), makeModel('Deployment', 'apps', 'v1beta1'), ]; renderDropdown(models, {}); - openDropdown(); + await openDropdown(user); expect(getMenuItems()).toHaveLength(2); }); }); describe('search filtering', () => { - it('filters resources by reference name (case-insensitive)', () => { + it('filters resources by reference name (case-insensitive)', async () => { const models = [ makeModel('Pod', 'core', 'v1'), makeModel('Deployment', 'apps', 'v1'), @@ -137,60 +144,60 @@ describe('ResourceListDropdown', () => { ]; renderDropdown(models); - openDropdown(); - typeInSearch('pod'); + await openDropdown(user); + await typeInSearch(user, 'pod'); const items = getMenuItems(); expect(items).toHaveLength(1); expect(items[0]).toHaveTextContent('Pod'); }); - it('filters resources by short name', () => { + it('filters resources by short name', async () => { const models = [ makeModel('Pod', 'core', 'v1', { shortNames: ['po'] }), makeModel('Deployment', 'apps', 'v1', { shortNames: ['deploy'] }), ]; renderDropdown(models); - openDropdown(); - typeInSearch('deploy'); + await openDropdown(user); + await typeInSearch(user, 'deploy'); const items = getMenuItems(); expect(items).toHaveLength(1); expect(items[0]).toHaveTextContent('Deployment'); }); - it('shows "No results found" when no resources match', () => { + it('shows "No results found" when no resources match', async () => { const models = [makeModel('Pod', 'core', 'v1')]; renderDropdown(models); - openDropdown(); - typeInSearch('nonexistent'); + await openDropdown(user); + await typeInSearch(user, 'nonexistent'); expect(screen.getByText('No results found')).toBeInTheDocument(); }); - it('shows all resources when search is cleared', () => { + it('shows all resources when search is cleared', async () => { const models = [makeModel('Pod', 'core', 'v1'), makeModel('Deployment', 'apps', 'v1')]; renderDropdown(models); - openDropdown(); - typeInSearch('pod'); + await openDropdown(user); + await typeInSearch(user, 'pod'); expect(getMenuItems()).toHaveLength(1); - typeInSearch(''); + await typeInSearch(user, ''); expect(getMenuItems()).toHaveLength(2); }); }); describe('MAX_VISIBLE_ITEMS cap', () => { - it('caps rendered items at 100 and shows truncation message', () => { + it('caps rendered items at 100 and shows truncation message', async () => { const models = Array.from({ length: 150 }, (_, i) => makeModel(`Resource${String(i).padStart(3, '0')}`, 'test.io', 'v1'), ); renderDropdown(models); - openDropdown(); + await openDropdown(user); const menu = screen.getByRole('menu'); const items = within(menu).getAllByRole('menuitem'); @@ -200,40 +207,40 @@ describe('ResourceListDropdown', () => { expect(screen.getByText('Showing 100 of 150 resources. Type to filter.')).toBeInTheDocument(); }); - it('does not show truncation message when items fit within the cap', () => { + it('does not show truncation message when items fit within the cap', async () => { const models = Array.from({ length: 50 }, (_, i) => makeModel(`Resource${i}`, 'test.io', 'v1'), ); renderDropdown(models); - openDropdown(); + await openDropdown(user); expect(screen.queryByText(/Showing .* of .* resources/)).not.toBeInTheDocument(); }); - it('filtering below the cap removes the truncation message', () => { + it('filtering below the cap removes the truncation message', async () => { const models = Array.from({ length: 150 }, (_, i) => makeModel(`Resource${String(i).padStart(3, '0')}`, 'test.io', 'v1'), ); renderDropdown(models); - openDropdown(); + await openDropdown(user); expect(screen.getByText(/Showing 100 of 150/)).toBeInTheDocument(); - typeInSearch('Resource00'); + await typeInSearch(user, 'Resource00'); expect(screen.queryByText(/Showing .* of .* resources/)).not.toBeInTheDocument(); }); }); describe('auto-open on typing', () => { - it('opens the dropdown when the user starts typing', () => { + it('opens the dropdown when the user starts typing', async () => { const models = [makeModel('Pod', 'core', 'v1')]; renderDropdown(models); expect(screen.queryByRole('menu')).not.toBeInTheDocument(); - typeInSearch('pod'); + await typeInSearch(user, 'pod'); expect(screen.getByRole('menu')).toBeInTheDocument(); }); diff --git a/frontend/public/components/__tests__/storage-class-form.spec.tsx b/frontend/public/components/__tests__/storage-class-form.spec.tsx index 74383fd832c..bea59a0e42b 100644 --- a/frontend/public/components/__tests__/storage-class-form.spec.tsx +++ b/frontend/public/components/__tests__/storage-class-form.spec.tsx @@ -14,14 +14,12 @@ jest.mock('react-router', () => ({ describe('StorageClassForm', () => { let onClose: jest.Mock; - beforeEach(async () => { - onClose = jest.fn(); - + const renderForm = () => { renderWithProviders(); + }; - await waitFor(() => { - expect(screen.getByText('StorageClass')).toBeInTheDocument(); - }); + beforeEach(() => { + onClose = jest.fn(); }); afterAll(() => { @@ -29,14 +27,18 @@ describe('StorageClassForm', () => { }); it('verifies StorageClass as the page title', async () => { + renderForm(); expect(await screen.findByText('StorageClass')).toBeVisible(); }); it('verifies the Edit YAML link', async () => { - expect(await screen.findByText('Edit YAML')).toBeVisible(); + renderForm(); + const editYamlLink = await screen.findByRole('link', { name: 'Edit YAML' }); + expect(editYamlLink).toHaveAttribute('href', '/k8s/cluster/storageclasses/~new'); }); it('verifies a text input for storage class name', async () => { + renderForm(); await verifyInputField({ inputLabel: 'Name', inputType: 'text', @@ -44,6 +46,7 @@ describe('StorageClassForm', () => { }); it('verifies a text input for storage class description', async () => { + renderForm(); await waitFor(() => { expect(screen.getByLabelText('Description')).toBeInTheDocument(); }); @@ -54,6 +57,7 @@ describe('StorageClassForm', () => { }); it('verifies a dropdown for selecting reclaim policy with correct options and help text', async () => { + renderForm(); await waitFor(() => { expect(screen.getByLabelText('Reclaim policy')).toBeInTheDocument(); }); @@ -66,6 +70,7 @@ describe('StorageClassForm', () => { }); it('verifies a dropdown for selecting volume binding mode with correct options and help text', async () => { + renderForm(); await waitFor(() => { expect(screen.getByLabelText('Volume binding mode')).toBeInTheDocument(); }); @@ -76,6 +81,7 @@ describe('StorageClassForm', () => { }); it('verifies a dropdown for selecting provisioner with correct help text', async () => { + renderForm(); await waitFor(() => { expect(screen.getByLabelText('Provisioner')).toBeInTheDocument(); }); @@ -86,12 +92,14 @@ describe('StorageClassForm', () => { }); it('shows additional parameters section is not visible without selected provisioner', async () => { + renderForm(); await waitFor(() => { expect(screen.queryByText('Additional parameters')).not.toBeInTheDocument(); }); }); it('should render control buttons', async () => { + renderForm(); await waitFor(() => { expect(screen.getByRole('button', { name: /create/i })).toBeVisible(); expect(screen.getByRole('button', { name: /cancel/i })).toBeVisible(); diff --git a/frontend/public/components/cluster-settings/__tests__/basicauth-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/basicauth-idp-form.spec.tsx index 1f4afdb3cc6..d161c35d36e 100644 --- a/frontend/public/components/cluster-settings/__tests__/basicauth-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/basicauth-idp-form.spec.tsx @@ -1,5 +1,5 @@ -import { cleanup, act } from '@testing-library/react'; import { AddBasicAuthPage } from '../../cluster-settings/basicauth-idp-form'; +import { screen } from '@testing-library/react'; import { renderWithProviders, verifyInputField, @@ -13,25 +13,21 @@ import { } from './test-utils'; describe('Add Identity Provider: Basic Authentication', () => { + const renderPage = async () => { + renderWithProviders(); + expect(await screen.findByRole('button', { name: 'Add' })).toBeInTheDocument(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); - it('should render page title and sub title', () => { + it('should render page title and sub title', async () => { + await renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: Basic Authentication', subtitle: @@ -40,6 +36,7 @@ describe('Add Identity Provider: Basic Authentication', () => { }); it('should render the Name label, input element, and help text', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'basic-auth', @@ -50,6 +47,7 @@ describe('Add Identity Provider: Basic Authentication', () => { }); it('should render the URL label, input element, and help text', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'URL', inputType: 'url', @@ -60,26 +58,33 @@ describe('Add Identity Provider: Basic Authentication', () => { }); it('should render the CA file label and elements', async () => { + await renderPage(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render the certificate label and elements', async () => { + await renderPage(); await verifyIDPFileFields({ inputLabel: 'Certificate', + fieldId: 'cert-file-input', helpText: 'PEM-encoded TLS client certificate file', }); }); it('should render the key label and elements', async () => { + await renderPage(); await verifyIDPFileFields({ inputLabel: 'Key', + fieldId: 'key-file-input', helpText: 'PEM-encoded TLS private key file', }); }); - it("should render 'Add' and 'Cancel' buttons in a button bar", () => { + it("should render 'Add' and 'Cancel' buttons in a button bar", async () => { + await renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/cluster-status.spec.tsx b/frontend/public/components/cluster-settings/__tests__/cluster-status.spec.tsx index e5e34d6ceee..dad0a5689da 100644 --- a/frontend/public/components/cluster-settings/__tests__/cluster-status.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/cluster-status.spec.tsx @@ -128,7 +128,9 @@ describe('UpdateStatus', () => { expect(mockK8sPatch).toHaveBeenCalled(); // Wait for the promise to reject and the modal to be launched - await screen.findByRole('button', { name: /Cancel update/ }); + await expect( + screen.findByRole('button', { name: /Cancel update/ }), + ).resolves.toBeInTheDocument(); expect(mockLaunchModal).toHaveBeenCalledWith( expect.any(Function), expect.objectContaining({ error: errorMessage }), diff --git a/frontend/public/components/cluster-settings/__tests__/github-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/github-idp-form.spec.tsx index d174d56c12b..263fe638776 100644 --- a/frontend/public/components/cluster-settings/__tests__/github-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/github-idp-form.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup, screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { AddGitHubPage } from '../../cluster-settings/github-idp-form'; import { verifyIDPAddAndCancelButtons, @@ -14,25 +14,21 @@ import { } from '@console/shared/src/test-utils/unit-test-utils'; describe('Add Identity Provider: GitHub', () => { + const renderPage = async () => { + renderWithProviders(); + expect(await screen.findByRole('button', { name: 'Add' })).toBeInTheDocument(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); - it('should render page title and sub title', () => { + it('should render page title and sub title', async () => { + await renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: GitHub', subtitle: @@ -41,6 +37,7 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the Name label, input element, and help text', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'github', @@ -51,6 +48,7 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the Client ID label, input element, and help text', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'Client ID', testValue: mockData.updatedFormValues.id, @@ -59,6 +57,7 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the Client Secret label and input password element', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'Client secret', inputType: 'password', @@ -68,6 +67,7 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the Hostname label, input element, and help text', async () => { + await renderPage(); await verifyInputField({ inputLabel: 'Hostname', testValue: mockData.updatedFormValues.name, @@ -76,14 +76,17 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the CA file label and elements, and verify upload file selection', async () => { + await renderPage(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', fileName: 'ca-certificate.pem', fileContent: 'test certificate content', }); }); it('should render the Organizations sub heading and input element', async () => { + await renderPage(); expect(screen.getByRole('heading', { name: 'Organizations' })).toBeVisible(); // Verify the text content @@ -107,6 +110,7 @@ describe('Add Identity Provider: GitHub', () => { }); it('should render the Teams sub heading', async () => { + await renderPage(); expect(screen.getByRole('heading', { name: 'Teams' })).toBeVisible(); // Verify the text content @@ -129,7 +133,8 @@ describe('Add Identity Provider: GitHub', () => { }); }); - it("should render 'Add' and 'Cancel' buttons in a button bar", () => { + it("should render 'Add' and 'Cancel' buttons in a button bar", async () => { + await renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/gitlab-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/gitlab-idp-form.spec.tsx index 5626daa4ebd..ae17821b2db 100644 --- a/frontend/public/components/cluster-settings/__tests__/gitlab-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/gitlab-idp-form.spec.tsx @@ -1,4 +1,3 @@ -import { cleanup, act } from '@testing-library/react'; import { verifyIDPAddAndCancelButtons, verifyPageTitleAndSubtitle, @@ -13,25 +12,20 @@ import { import { AddGitLabPage } from '../../cluster-settings/gitlab-idp-form'; describe('Add Identity Provider: GitLab', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: GitLab', subtitle: 'You can use GitLab integration for users authenticating with GitLab credentials.', @@ -39,6 +33,7 @@ describe('Add Identity Provider: GitLab', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'gitlab', @@ -49,6 +44,7 @@ describe('Add Identity Provider: GitLab', () => { }); it('should render the URL label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'URL', inputType: 'url', @@ -59,6 +55,7 @@ describe('Add Identity Provider: GitLab', () => { }); it('should render the Client ID label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client ID', testValue: mockData.updatedFormValues.id, @@ -67,6 +64,7 @@ describe('Add Identity Provider: GitLab', () => { }); it('should render the Client Secret label and input password element', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client secret', inputType: 'password', @@ -76,12 +74,15 @@ describe('Add Identity Provider: GitLab', () => { }); it('should render the CA file label and elements', async () => { + renderPage(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/google-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/google-idp-form.spec.tsx index 7cf8fc96eec..1de76f6420d 100644 --- a/frontend/public/components/cluster-settings/__tests__/google-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/google-idp-form.spec.tsx @@ -1,4 +1,3 @@ -import { cleanup } from '@testing-library/react'; import { renderWithProviders, verifyInputField, @@ -7,15 +6,12 @@ import { verifyIDPAddAndCancelButtons, verifyPageTitleAndSubtitle, mockData } fr import { AddGooglePage } from '../../cluster-settings/google-idp-form'; describe('Add Identity Provider: Google', () => { - beforeEach(() => { + const renderPage = () => { renderWithProviders(); - }); - - afterEach(() => { - cleanup(); - }); + }; it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: Google', subtitle: 'You can use Google integration for users authenticating with Google credentials.', @@ -23,6 +19,7 @@ describe('Add Identity Provider: Google', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'google', @@ -33,6 +30,7 @@ describe('Add Identity Provider: Google', () => { }); it('should render the Client ID label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client ID', testValue: mockData.updatedFormValues.id, @@ -41,6 +39,7 @@ describe('Add Identity Provider: Google', () => { }); it('should render the Client Secret label and input password element', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client secret', inputType: 'password', @@ -50,6 +49,7 @@ describe('Add Identity Provider: Google', () => { }); it('should render the Hosted Domain label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Hosted domain', testValue: mockData.updatedFormValues.domain, @@ -59,6 +59,7 @@ describe('Add Identity Provider: Google', () => { }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/htpasswd-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/htpasswd-idp-form.spec.tsx index ea47f738190..f6215bda8c1 100644 --- a/frontend/public/components/cluster-settings/__tests__/htpasswd-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/htpasswd-idp-form.spec.tsx @@ -1,4 +1,3 @@ -import { cleanup, act } from '@testing-library/react'; import { renderWithProviders, verifyInputField, @@ -13,25 +12,20 @@ import { import { AddHTPasswdPage } from '../../cluster-settings/htpasswd-idp-form'; describe('Add Identity Provider: HTPasswd', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: HTPasswd', subtitle: @@ -40,6 +34,7 @@ describe('Add Identity Provider: HTPasswd', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'htpasswd', @@ -50,8 +45,10 @@ describe('Add Identity Provider: HTPasswd', () => { }); it('should render the HTPasswd file file label and elements', async () => { + renderPage(); await verifyIDPFileFields({ inputLabel: 'HTPasswd file', + fieldId: 'htpasswd-file', helpText: 'Upload an HTPasswd file created using the htpasswd command.', fileName: 'example.htpasswd', fileContent: mockData.testHtpasswdFileContent, @@ -59,6 +56,7 @@ describe('Add Identity Provider: HTPasswd', () => { }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/keystone-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/keystone-idp-form.spec.tsx index e97436c94cf..069c46c7efe 100644 --- a/frontend/public/components/cluster-settings/__tests__/keystone-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/keystone-idp-form.spec.tsx @@ -1,4 +1,3 @@ -import { cleanup, act } from '@testing-library/react'; import { renderWithProviders, verifyInputField, @@ -13,25 +12,20 @@ import { import { AddKeystonePage } from '../../cluster-settings/keystone-idp-form'; describe('Add Identity Provider: Keystone Authentication', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: Keystone Authentication', subtitle: @@ -40,6 +34,7 @@ describe('Add Identity Provider: Keystone Authentication', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'keystone', @@ -50,6 +45,7 @@ describe('Add Identity Provider: Keystone Authentication', () => { }); it('should render the Domain name label and input element', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Domain name', testValue: mockData.updatedFormValues.url, @@ -58,6 +54,7 @@ describe('Add Identity Provider: Keystone Authentication', () => { }); it('should render the URL label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'URL', inputType: 'url', @@ -68,26 +65,33 @@ describe('Add Identity Provider: Keystone Authentication', () => { }); it('should render the CA file label and elements', async () => { + renderPage(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render the certificate label and elements', async () => { + renderPage(); await verifyIDPFileFields({ inputLabel: 'Certificate', + fieldId: 'cert-file-input', helpText: 'PEM-encoded TLS client certificate file', }); }); it('should render the key label and elements', async () => { + renderPage(); await verifyIDPFileFields({ inputLabel: 'Key', + fieldId: 'key-file-input', helpText: 'PEM-encoded TLS private key file', }); }); it("should render 'Add' and 'Cancel' buttons in a button bar", () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/ldap-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/ldap-idp-form.spec.tsx index d0a5c6e411e..67cdd35eb5a 100644 --- a/frontend/public/components/cluster-settings/__tests__/ldap-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/ldap-idp-form.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup, screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { verifyIDPAddAndCancelButtons, verifyPageTitleAndSubtitle, @@ -14,25 +14,20 @@ import { import { AddLDAPPage } from '../../cluster-settings/ldap-idp-form'; describe('Add Identity Provider: LDAP', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: LDAP', subtitle: 'Integrate with an LDAP identity provider.', @@ -40,6 +35,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', containerId: 'idp-name-form', @@ -51,6 +47,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the URL label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'URL', inputType: 'url', @@ -61,6 +58,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Bind DN label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Bind DN', testValue: mockData.updatedFormValues.updatedValue, @@ -69,6 +67,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Bind Password label and input password element', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Bind password', inputType: 'password', @@ -78,11 +77,13 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Attributes sub heading', () => { + renderPage(); expect(screen.getByRole('heading', { name: 'Attributes' })).toBeVisible(); expect(screen.getByText('Attributes map LDAP attributes to identities.')).toBeVisible(); }); it('should render the Attributes > ID label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'ID', initialValue: 'dn', @@ -93,6 +94,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Attributes > Preferred username label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Preferred username', initialValue: 'uid', @@ -103,6 +105,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Attributes Name label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Name', initialValue: 'cn', @@ -113,6 +116,7 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the Attributes Email label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Email', testValue: mockData.updatedFormValues.email, @@ -122,14 +126,17 @@ describe('Add Identity Provider: LDAP', () => { }); it('should render the More options sub heading and CA file label and input element', async () => { + renderPage(); expect(screen.getByRole('heading', { name: 'More options' })).toBeVisible(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/openid-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/openid-idp-form.spec.tsx index 40fb8b9d9cc..daa1f36c6e5 100644 --- a/frontend/public/components/cluster-settings/__tests__/openid-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/openid-idp-form.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup, screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { verifyIDPAddAndCancelButtons, verifyPageTitleAndSubtitle, @@ -14,25 +14,20 @@ import { import { AddOpenIDIDPPage } from '../../cluster-settings/openid-idp-form'; describe('Add Identity Provider: OpenID Connect', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: OpenID Connect', subtitle: @@ -41,6 +36,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'openid', @@ -52,6 +48,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Client ID label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client ID', testValue: mockData.updatedFormValues.id, @@ -60,6 +57,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Client Secret label and input password element', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Client secret', inputType: 'password', @@ -69,6 +67,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Issuer URL label and elements', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Issuer URL', inputType: 'url', @@ -80,6 +79,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Claims sub heading abd text', () => { + renderPage(); expect(screen.getByRole('heading', { name: 'Claims' })).toBeVisible(); expect( screen.getByText( @@ -89,6 +89,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Preferred username label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Preferred username', initialValue: 'preferred_username', @@ -99,6 +100,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Name', initialValue: 'name', @@ -109,6 +111,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the Email label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Email', initialValue: 'email', @@ -119,14 +122,17 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render the More options sub heading and CA file label and input element', async () => { + renderPage(); expect(screen.getByRole('heading', { name: 'More options' })).toBeVisible(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render the Extra scopes label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Extra scopes', testValue: mockData.updatedFormValues.updatedValue, @@ -136,6 +142,7 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/request-header-idp-form.spec.tsx b/frontend/public/components/cluster-settings/__tests__/request-header-idp-form.spec.tsx index 6daa4403d59..015d39cdabb 100644 --- a/frontend/public/components/cluster-settings/__tests__/request-header-idp-form.spec.tsx +++ b/frontend/public/components/cluster-settings/__tests__/request-header-idp-form.spec.tsx @@ -1,4 +1,4 @@ -import { cleanup, screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { verifyIDPAddAndCancelButtons, verifyPageTitleAndSubtitle, @@ -14,25 +14,20 @@ import { import { AddRequestHeaderPage } from '../../cluster-settings/request-header-idp-form'; describe('Add Identity Provider: Request Header', () => { + const renderPage = () => { + renderWithProviders(); + }; + beforeAll(() => { setupFileReaderMock(); }); - beforeEach(async () => { - await act(async () => { - renderWithProviders(); - }); - }); - - afterEach(() => { - cleanup(); - }); - afterAll(() => { jest.resetAllMocks(); }); it('should render page title and sub title', () => { + renderPage(); verifyPageTitleAndSubtitle({ title: 'Add Identity Provider: Request Header', subtitle: @@ -41,6 +36,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Name label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Name', initialValue: 'request-header', @@ -51,11 +47,13 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the URLs sub heading', () => { + renderPage(); expect(screen.getByRole('heading', { name: 'URLs' })).toBeVisible(); expect(screen.getByText('At least one URL must be provided.')).toBeVisible(); }); it('should render the Challenge URL label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Challenge URL', inputType: 'url', @@ -66,6 +64,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Login URL label, input element, and help text', async () => { + renderPage(); await verifyInputField({ inputLabel: 'Login URL', inputType: 'url', @@ -76,17 +75,21 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the More options sub heading', () => { + renderPage(); expect(screen.getByRole('heading', { name: 'More options' })).toBeVisible(); }); it('should render the More options sub heading and CA file label and input element', async () => { + renderPage(); expect(screen.getByRole('heading', { name: 'More options' })).toBeVisible(); await verifyIDPFileFields({ inputLabel: 'CA file', + fieldId: 'ca-file-input', }); }); it('should render the Client common names label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Client common names', testValue: mockData.updatedFormValues.name, @@ -96,6 +99,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Headers label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Headers', testValue: mockData.updatedFormValues.headers, @@ -106,6 +110,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Preferred username headers label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Preferred username headers', testValue: mockData.updatedFormValues.name, @@ -115,6 +120,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Name headers label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Name headers', testValue: mockData.updatedFormValues.headers, @@ -124,6 +130,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render the Email headers label, input element, and help text', async () => { + renderPage(); await verifyIDPListInputFields({ inputLabel: 'Email headers', testValue: mockData.updatedFormValues.email, @@ -133,6 +140,7 @@ describe('Add Identity Provider: Request Header', () => { }); it('should render control buttons in a button bar', () => { + renderPage(); verifyIDPAddAndCancelButtons(); }); }); diff --git a/frontend/public/components/cluster-settings/__tests__/test-utils.ts b/frontend/public/components/cluster-settings/__tests__/test-utils.ts index d2d8c34c911..a9e5cdc9b1f 100644 --- a/frontend/public/components/cluster-settings/__tests__/test-utils.ts +++ b/frontend/public/components/cluster-settings/__tests__/test-utils.ts @@ -120,34 +120,42 @@ export const verifyPageTitleAndSubtitle = ({ // Verifies file input UI elements and test file upload selection functionality and filename verifications. export const verifyIDPFileFields = async ({ inputLabel, + fieldId, helpText, fileName = 'test-idp.pem', fileContent = mockData.testPemFileContent, }: { inputLabel: string; + /** `id` of the `DroppableFileInput` (see `data-test` on the PatternFly `FileUpload` in `file-input`). */ + fieldId: string; helpText?: string; fileName?: string; fileContent?: string; }) => { const user = userEvent.setup(); + const fileUpload = await screen.findByTestId(fieldId); + // Wait for the async component to load and find the filename input by its aria-label - const filenameInput = await screen.findByLabelText(`${inputLabel} filename`); + const filenameInput = within(fileUpload).getByLabelText(`${inputLabel} filename`); verifyFormElementBasics(filenameInput, 'text', ''); - // Find the file upload container by traversing up from the filename input - const fileUploadContainer = filenameInput.closest('.pf-v6-c-file-upload'); - expect(fileUploadContainer).toBeInTheDocument(); - // Verify browse button is visible within the container - const browseButton = within(fileUploadContainer as HTMLElement).getByRole('button', { + const browseButton = within(fileUpload).getByRole('button', { name: 'Browse...', }); expect(browseButton).toBeVisible(); - // Find the hidden file input element within the container - const fileInput = fileUploadContainer.querySelector('input[type="file"]') as HTMLInputElement; - expect(fileInput).toBeTruthy(); + // PF FileUpload renders an unlabeled hidden input[type=file], so we locate it from this scoped container. + const fileInput = within(fileUpload) + .getAllByDisplayValue('') + .find((element) => element instanceof HTMLInputElement && element.type === 'file') as + | HTMLInputElement + | undefined; + expect(fileInput).toBeDefined(); + if (!fileInput) { + throw new Error(`Could not find file input for ${inputLabel}`); + } // Verify help text if provided - in PF FileUpload, this is the placeholder attribute if (helpText) { @@ -161,7 +169,7 @@ export const verifyIDPFileFields = async ({ await user.upload(fileInput, file); // Verify the file was properly selected and assigned to the input element - expect(fileInput.files).toBeTruthy(); + expect(fileInput.files).toBeDefined(); expect(fileInput.files?.length).toBe(1); expect(fileInput.files?.[0]).toBe(file); expect(fileInput.files?.[0]?.name).toBe(fileName); diff --git a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/__tests__/getting-started-section.spec.tsx b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/__tests__/getting-started-section.spec.tsx index bfac9564034..2c7a903fae3 100644 --- a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/__tests__/getting-started-section.spec.tsx +++ b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/__tests__/getting-started-section.spec.tsx @@ -60,12 +60,10 @@ describe('GettingStartedSection', () => { , ); - await waitFor(() => { - const contentContainer = screen.getByTestId('getting-started-content'); - expect(contentContainer).toHaveTextContent('Set up your cluster'); - expect(contentContainer).toHaveTextContent('Learn with guided tours'); - expect(contentContainer).toHaveTextContent('Explore new features'); - }); + const contentContainer = await screen.findByTestId('getting-started-content'); + expect(contentContainer).toHaveTextContent('Set up your cluster'); + expect(contentContainer).toHaveTextContent('Learn with guided tours'); + expect(contentContainer).toHaveTextContent('Explore new features'); }); it('should render nothing when useFlag(FLAGS.OPENSHIFT) returns false', async () => { diff --git a/frontend/public/components/factory/__tests__/details.spec.tsx b/frontend/public/components/factory/__tests__/details.spec.tsx index ba6f878a0f7..d70f6a17abe 100644 --- a/frontend/public/components/factory/__tests__/details.spec.tsx +++ b/frontend/public/components/factory/__tests__/details.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { DetailsPage } from '@console/internal/components/factory/details'; import { PodModel } from '@console/internal/models'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; @@ -36,10 +36,8 @@ describe('Resource DetailsPage', () => { mockUseK8sWatchResources.mockReturnValue({ obj: { data: null, loaded: true } }); }); - it('should verify the detail page basic information and navigation tabs', async () => { - await act(async () => { - renderWithProviders(); - }); + it('should verify the detail page basic information and navigation tabs', () => { + renderWithProviders(); // Verify hook was called expect(mockUseK8sWatchResources).toHaveBeenCalled(); @@ -57,7 +55,7 @@ describe('Resource DetailsPage', () => { expect(screen.getByRole('tab', { name: 'Events' })).toBeVisible(); }); - it('should verify details page with extra resources passed to useK8sWatchResources', async () => { + it('should verify details page with extra resources passed to useK8sWatchResources', () => { const extraResource = [ { kind: 'ConfigMap', @@ -72,9 +70,7 @@ describe('Resource DetailsPage', () => { configMap: { data: null, loaded: true }, }); - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); // Verify hook was called with both resources expect(mockUseK8sWatchResources).toHaveBeenCalled(); diff --git a/frontend/public/components/modals/__tests__/column-management-modal.spec.tsx b/frontend/public/components/modals/__tests__/column-management-modal.spec.tsx index 24f8b873760..e6c11de5685 100644 --- a/frontend/public/components/modals/__tests__/column-management-modal.spec.tsx +++ b/frontend/public/components/modals/__tests__/column-management-modal.spec.tsx @@ -150,16 +150,14 @@ describe('ColumnManagementModal component', () => { }; describe('basic rendering', () => { - beforeEach(() => { - renderColumnManagementModal(); - }); - it('renders title and subtitle', () => { + renderColumnManagementModal(); expect(screen.getByText('Manage columns')).toBeVisible(); expect(screen.getByText('Selected columns will appear in the table.')).toBeVisible(); }); it('renders max row info alert', () => { + renderColumnManagementModal(); expect(screen.getByText('You can select up to {{MAX_VIEW_COLS}} columns')).toBeVisible(); expect( screen.getByText('The namespace column is only shown when in "All projects"'), @@ -167,11 +165,13 @@ describe('ColumnManagementModal component', () => { }); it('renders data lists', () => { + renderColumnManagementModal(); expect(screen.getByLabelText('Default column list')).toBeVisible(); expect(screen.getByLabelText('Additional column list')).toBeVisible(); }); it('renders 12 checkboxes with name, and last 3 disabled', () => { + renderColumnManagementModal(); const checkboxes = screen.getAllByRole('checkbox'); expect(checkboxes).toHaveLength(12); @@ -184,6 +184,7 @@ describe('ColumnManagementModal component', () => { }); it('renders restore default column, save and cancel buttons', () => { + renderColumnManagementModal(); expect(screen.getByRole('button', { name: 'Restore default columns' })).toBeVisible(); expect(screen.getByRole('button', { name: 'Save' })).toBeVisible(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible(); diff --git a/frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx b/frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx index 0a7b476fb80..6f67fa9bca9 100644 --- a/frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx +++ b/frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx @@ -137,11 +137,8 @@ describe('ConfigureUpdateStrategy component', () => { }); describe('Recreate strategy', () => { - beforeEach(() => { - renderWithProviders(); - }); - it('shows recreate strategy as selected', () => { + renderWithProviders(); const rollingUpdateRadio = screen.getByTestId('rolling-update-strategy-radio'); const recreateRadio = screen.getByTestId('recreate-update-strategy-radio'); @@ -152,6 +149,7 @@ describe('ConfigureUpdateStrategy component', () => { }); it('disables max unavailable and max surge inputs', () => { + renderWithProviders(); const maxUnavailableInput = screen.getByTestId('max-unavailable-input'); const maxSurgeInput = screen.getByTestId('max-surge-input'); diff --git a/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx b/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx index 1010b6a10d8..f313db6f593 100644 --- a/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx +++ b/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx @@ -3,7 +3,7 @@ * Tests the modal integrated with Redux actions and state */ -import { render, screen, waitFor, act } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; @@ -61,13 +61,11 @@ describe('ImpersonateUserModal Integration Tests', () => { mockStartImpersonate('User', username); }); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const usernameInput = screen.getByTestId('username-input'); await user.clear(usernameInput); @@ -93,13 +91,11 @@ describe('ImpersonateUserModal Integration Tests', () => { } }); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const usernameInput = screen.getByTestId('username-input'); await user.clear(usernameInput); @@ -107,15 +103,11 @@ describe('ImpersonateUserModal Integration Tests', () => { // Open groups dropdown const groupsInput = screen.getByPlaceholderText('Enter groups'); - await act(async () => { - await user.click(groupsInput); - }); + await user.click(groupsInput); // Wait for dropdown to open and select first group const developersOption = await screen.findByText('developers'); - await act(async () => { - await user.click(developersOption); - }); + await user.click(developersOption); const submitButton = screen.getByTestId('impersonate-button'); await user.click(submitButton); @@ -134,13 +126,11 @@ describe('ImpersonateUserModal Integration Tests', () => { const user = userEvent.setup(); const onImpersonate = jest.fn(); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); // Enter username const usernameInput = screen.getByTestId('username-input'); @@ -149,30 +139,27 @@ describe('ImpersonateUserModal Integration Tests', () => { // Open dropdown const groupsInput = screen.getByPlaceholderText('Enter groups'); - await act(async () => { - await user.click(groupsInput); - }); + await user.click(groupsInput); // Select first group const developersOption = await screen.findByText('developers'); - await act(async () => { - await user.click(developersOption); - }); + await user.click(developersOption); // Verify group chip appears + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs await waitFor(() => { + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs const chips = document.querySelectorAll('.pf-v6-c-label'); expect(chips.length).toBeGreaterThan(0); }); // Select second group const adminsOption = screen.getByText('admins'); - await act(async () => { - await user.click(adminsOption); - }); + await user.click(adminsOption); // Verify two groups are selected await waitFor(() => { + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs const chips = document.querySelectorAll('.pf-v6-c-label'); expect(chips.length).toBe(2); }); @@ -190,13 +177,11 @@ describe('ImpersonateUserModal Integration Tests', () => { const user = userEvent.setup(); const onImpersonate = jest.fn(); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const usernameInput = screen.getByTestId('username-input'); await user.clear(usernameInput); @@ -204,29 +189,26 @@ describe('ImpersonateUserModal Integration Tests', () => { // Open and select group const groupsInput = screen.getByPlaceholderText('Enter groups'); - await act(async () => { - await user.click(groupsInput); - }); + await user.click(groupsInput); const developersOption = await screen.findByText('developers'); - await act(async () => { - await user.click(developersOption); - }); + await user.click(developersOption); // Wait for chip to appear await waitFor(() => { + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs const chips = document.querySelectorAll('.pf-v6-c-label'); expect(chips.length).toBe(1); }); // Remove the group by clicking the X button + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs const closeButtons = document.querySelectorAll('.pf-v6-c-label__actions button'); if (closeButtons.length > 0) { - await act(async () => { - await user.click(closeButtons[0]); - }); + await user.click(closeButtons[0]); await waitFor(() => { + // eslint-disable-next-line testing-library/no-node-access -- PatternFly chips don't have accessible test IDs const chips = document.querySelectorAll('.pf-v6-c-label'); expect(chips.length).toBe(0); }); @@ -245,27 +227,21 @@ describe('ImpersonateUserModal Integration Tests', () => { describe('Search and filter workflow', () => { it('should filter groups based on search input', async () => { const user = userEvent.setup(); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const groupsInput = screen.getByPlaceholderText('Enter groups'); // Open dropdown first - await act(async () => { - await user.click(groupsInput); - }); + await user.click(groupsInput); - await screen.findByText('developers'); + expect(await screen.findByText('developers')).toBeInTheDocument(); // Type to filter - await act(async () => { - await user.type(groupsInput, 'dev'); - }); + await user.type(groupsInput, 'dev'); await waitFor(() => { // Should show developers (matches "dev") @@ -277,27 +253,21 @@ describe('ImpersonateUserModal Integration Tests', () => { it('should show no results when filter matches nothing', async () => { const user = userEvent.setup(); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const groupsInput = screen.getByPlaceholderText('Enter groups'); // Open dropdown first - await act(async () => { - await user.click(groupsInput); - }); + await user.click(groupsInput); - await screen.findByText('developers'); + expect(await screen.findByText('developers')).toBeInTheDocument(); // Type to filter with non-matching text - await act(async () => { - await user.type(groupsInput, 'nonexistent'); - }); + await user.type(groupsInput, 'nonexistent'); expect(await screen.findByText('No results found')).toBeVisible(); }); @@ -308,13 +278,11 @@ describe('ImpersonateUserModal Integration Tests', () => { const error = new Error('Failed to fetch groups'); (useK8sWatchResource as jest.Mock).mockReturnValue([[], false, error]); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); expect(await screen.findByText('Failed to load groups')).toBeVisible(); @@ -344,22 +312,18 @@ describe('ImpersonateUserModal Integration Tests', () => { await ue.type(usernameInput, 'tempuser'); // Close modal - await act(async () => { - rerender( - - - , - ); - }); + rerender( + + + , + ); // Reopen modal - await act(async () => { - rerender( - - - , - ); - }); + rerender( + + + , + ); // Form should be reset const resetUsernameInput = screen.getByTestId('username-input') as HTMLInputElement; @@ -370,13 +334,11 @@ describe('ImpersonateUserModal Integration Tests', () => { const ue = userEvent.setup(); const onClose = jest.fn(); - await act(async () => { - render( - - - , - ); - }); + render( + + + , + ); const cancelButton = screen.getByTestId('cancel-button'); await ue.click(cancelButton); @@ -386,20 +348,18 @@ describe('ImpersonateUserModal Integration Tests', () => { }); describe('Readonly mode workflow', () => { - it('should support readonly username for prefilled scenarios', async () => { - await act(async () => { - render( - - - , - ); - }); + it('should support readonly username for prefilled scenarios', () => { + render( + + + , + ); const usernameInput = screen.getByTestId('username-input') as HTMLInputElement; expect(usernameInput.value).toBe('readonly-user'); diff --git a/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx b/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx index 9ac38efe82e..cd4f511620f 100644 --- a/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx +++ b/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx @@ -551,12 +551,7 @@ describe('ImpersonateUserModal', () => { expect(await screen.findByText('group6')).toBeVisible(); - // Remove one group using the close button on the chip - const group6Chip = screen.getByText('group6').closest('.pf-v6-c-label'); - const closeButton = group6Chip?.querySelector('button'); - expect(closeButton).toBeInTheDocument(); - - await user.click(closeButton!); + await user.click(screen.getByRole('button', { name: /close.*group6/i })); // Now only 5 groups remain, so it should collapse automatically await waitFor(() => { diff --git a/frontend/public/components/modals/__tests__/replace-code-modal.spec.tsx b/frontend/public/components/modals/__tests__/replace-code-modal.spec.tsx index 0d3d1b78f93..1fe4d06977e 100644 --- a/frontend/public/components/modals/__tests__/replace-code-modal.spec.tsx +++ b/frontend/public/components/modals/__tests__/replace-code-modal.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReplaceCodeModal } from '../replace-code-modal'; @@ -19,50 +19,52 @@ describe('ReplaceCodeModal', () => { render(); it('should render the modal with correct title and message', () => { - const { getByText } = renderComponent(); + renderComponent(); - expect(getByText('Replace current content?')).toBeTruthy(); - expect(getByText('Existing content will be replaced. Do you want to continue?')).toBeTruthy(); + expect(screen.getByText('Replace current content?')).toBeVisible(); + expect( + screen.getByText('Existing content will be replaced. Do you want to continue?'), + ).toBeVisible(); }); it('should render buttons with correct text', () => { - const { getByText } = renderComponent(); + renderComponent(); - expect(getByText('Yes')).toBeTruthy(); - expect(getByText('No')).toBeTruthy(); - expect(getByText('Keep both')).toBeTruthy(); + expect(screen.getByText('Yes')).toBeVisible(); + expect(screen.getByText('No')).toBeVisible(); + expect(screen.getByText('Keep both')).toBeVisible(); }); it('should call handleCodeReplace when "Yes" button is clicked', async () => { const user = userEvent.setup(); - const { getByText } = renderComponent(); + renderComponent(); - await user.click(getByText('Yes')); + await user.click(screen.getByText('Yes')); expect(handleCodeReplaceMock).toHaveBeenCalledTimes(1); }); it('should call handleCodeReplace when "No" button is clicked', async () => { const user = userEvent.setup(); - const { getByText } = renderComponent(); + renderComponent(); - await user.click(getByText('No')); + await user.click(screen.getByText('No')); expect(handleCodeReplaceMock).toHaveBeenCalledTimes(1); }); it('should call handleCodeReplace when "Keep both" button is clicked', async () => { const user = userEvent.setup(); - const { getByText } = renderComponent(); + renderComponent(); - await user.click(getByText('Keep both')); + await user.click(screen.getByText('Keep both')); expect(handleCodeReplaceMock).toHaveBeenCalledTimes(1); }); it('should call handleCodeReplace when close button (X) is clicked', async () => { const user = userEvent.setup(); - const { getByLabelText } = renderComponent(); + renderComponent(); - const closeButton = getByLabelText('Close'); - expect(closeButton).toBeTruthy(); + const closeButton = screen.getByLabelText('Close'); + expect(closeButton).toBeVisible(); await user.click(closeButton); expect(handleCodeReplaceMock).toHaveBeenCalledTimes(1); diff --git a/frontend/public/components/utils/__tests__/async.spec.tsx b/frontend/public/components/utils/__tests__/async.spec.tsx index 2910f2b479b..e68e803bf3a 100644 --- a/frontend/public/components/utils/__tests__/async.spec.tsx +++ b/frontend/public/components/utils/__tests__/async.spec.tsx @@ -127,11 +127,9 @@ describe('AsyncComponent', () => { renderWithProviders(); - await waitFor(() => { - const component = screen.getByText('Foo Component'); - expect(component).toBeInTheDocument(); - expect(component).toHaveClass(className); - }); + const component = await screen.findByText('Foo Component'); + expect(component).toBeInTheDocument(); + expect(component).toHaveClass(className); }); it('renders new component if props.loader changes', async () => { diff --git a/frontend/public/components/utils/__tests__/connected-page-heading.spec.tsx b/frontend/public/components/utils/__tests__/connected-page-heading.spec.tsx index b002ea4558e..0d80274580d 100644 --- a/frontend/public/components/utils/__tests__/connected-page-heading.spec.tsx +++ b/frontend/public/components/utils/__tests__/connected-page-heading.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; import { ConnectedPageHeading } from '../../../components/utils/headings'; import { testResourceInstance } from '../../../../__mocks__/k8sResourcesMocks'; @@ -6,9 +6,7 @@ import { testResourceInstance } from '../../../../__mocks__/k8sResourcesMocks'; describe('ConnectedPageHeading', () => { it('renders resource icon if given `kind`', async () => { const kind = 'Pod'; - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); await waitFor(() => { const icon = screen.getByTitle(kind); @@ -19,9 +17,7 @@ describe('ConnectedPageHeading', () => { it('renders custom title component if given', async () => { const title = My Custom Title; - await act(async () => { - renderWithProviders(); - }); + renderWithProviders(); await waitFor(() => { expect(screen.getByText('My Custom Title')).toBeInTheDocument(); @@ -30,14 +26,12 @@ describe('ConnectedPageHeading', () => { it('renders breadcrumbs if given `breadcrumbsFor` function', async () => { const breadcrumbs = []; - await act(async () => { - renderWithProviders( - breadcrumbs} - />, - ); - }); + renderWithProviders( + breadcrumbs} + />, + ); await waitFor(() => { expect(screen.getByTestId('page-heading-breadcrumbs')).toBeInTheDocument(); @@ -45,11 +39,9 @@ describe('ConnectedPageHeading', () => { }); it('does not render breadcrumbs if object has not loaded', async () => { - await act(async () => { - renderWithProviders( - []} />, - ); - }); + renderWithProviders( + []} />, + ); await waitFor(() => { expect(screen.queryByTestId('page-heading-breadcrumbs')).not.toBeInTheDocument(); diff --git a/frontend/public/components/utils/__tests__/download-button.spec.tsx b/frontend/public/components/utils/__tests__/download-button.spec.tsx index 0946f391ecd..142727c8c60 100644 --- a/frontend/public/components/utils/__tests__/download-button.spec.tsx +++ b/frontend/public/components/utils/__tests__/download-button.spec.tsx @@ -33,16 +33,12 @@ describe('DownloadButton', () => { it('renders button which calls `consoleFetch` to download URL when clicked', async () => { const user = userEvent.setup(); - await act(async () => { - render(); - }); + render(); const downloadButton = screen.getByRole('button'); expect(downloadButton).toBeInTheDocument(); - await act(async () => { - await user.click(downloadButton); - }); + await user.click(downloadButton); // Verify consoleFetch was called with the correct URL expect(mockConsoleFetch).toHaveBeenCalledWith(url, {}, 30000); @@ -60,16 +56,12 @@ describe('DownloadButton', () => { }; mockConsoleFetch.mockResolvedValue(mockResponse as Response); - await act(async () => { - render(); - }); + render(); const downloadButton = screen.getByRole('button'); // Click to start download - await act(async () => { - await user.click(downloadButton); - }); + await user.click(downloadButton); // Check that button shows "Downloading..." while in flight await waitFor(() => { diff --git a/frontend/public/components/utils/__tests__/kebab.spec.tsx b/frontend/public/components/utils/__tests__/kebab.spec.tsx index 98465a79536..d5e6d9d9098 100644 --- a/frontend/public/components/utils/__tests__/kebab.spec.tsx +++ b/frontend/public/components/utils/__tests__/kebab.spec.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { KebabItem, KebabItemAccessReview_ } from '../kebab'; import { useAccessReview } from '../rbac'; @@ -19,24 +19,24 @@ describe('KebabItem', () => { const user = userEvent.setup(); const nothingOption = { ...mockOption, href: undefined }; const trackOnClick = jest.fn(); - const renderItem = render(); - await user.click(renderItem.getByRole('menuitem')); + render(); + await user.click(screen.getByRole('menuitem')); expect(trackOnClick).toHaveBeenCalledTimes(0); }); it('should enable when option has href', async () => { const user = userEvent.setup(); const hrefOption = { ...mockOption }; const trackOnClick = jest.fn(); - const renderItem = render(); - await user.click(renderItem.getByRole('menuitem')); + render(); + await user.click(screen.getByRole('menuitem')); expect(trackOnClick).toHaveBeenCalledTimes(1); }); it('should enable when option has a callback', async () => { const user = userEvent.setup(); const callbackOption = { ...mockOption, href: undefined, callback: () => {} }; const trackOnClick = jest.fn(); - const renderItem = render(); - await user.click(renderItem.getByRole('menuitem')); + render(); + await user.click(screen.getByRole('menuitem')); expect(trackOnClick).toHaveBeenCalledTimes(1); }); }); @@ -53,14 +53,14 @@ describe('KebabItemAccessReview_', () => { const useAccessReviewMock = useAccessReview as jest.Mock; const trackOnClick = jest.fn(); useAccessReviewMock.mockReturnValue(false); - const renderItem = render( + render( , ); - await user.click(renderItem.getByRole('menuitem')); + await user.click(screen.getByRole('menuitem')); expect(trackOnClick).toHaveBeenCalledTimes(0); }); it('should enable option when option.accessReview present and allowed', async () => { @@ -68,14 +68,14 @@ describe('KebabItemAccessReview_', () => { const useAccessReviewMock = useAccessReview as jest.Mock; const trackOnClick = jest.fn(); useAccessReviewMock.mockReturnValue(true); - const renderItem = render( + render( , ); - await user.click(renderItem.getByRole('menuitem')); + await user.click(screen.getByRole('menuitem')); expect(trackOnClick).toHaveBeenCalledTimes(1); }); }); diff --git a/frontend/public/components/utils/__tests__/single-typeahead-dropdown.spec.tsx b/frontend/public/components/utils/__tests__/single-typeahead-dropdown.spec.tsx index 21cc7c14ea5..a2723ff9ee0 100644 --- a/frontend/public/components/utils/__tests__/single-typeahead-dropdown.spec.tsx +++ b/frontend/public/components/utils/__tests__/single-typeahead-dropdown.spec.tsx @@ -1,4 +1,4 @@ -import { screen, act, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils'; @@ -12,16 +12,14 @@ describe('SingleTypeaheadDropdown', () => { }); it('should render with placeholder text', async () => { - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); expect(combobox).toBeInTheDocument(); @@ -32,24 +30,20 @@ describe('SingleTypeaheadDropdown', () => { it('should display the clear button when input value is present', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); // Type some text into the input - await act(async () => { - await user.click(combobox); - await user.type(combobox, 'test'); - }); + await user.click(combobox); + await user.type(combobox, 'test'); const clearButton = await screen.findByRole('button', { name: /clear input value/i }); expect(clearButton).toBeVisible(); @@ -57,23 +51,19 @@ describe('SingleTypeaheadDropdown', () => { it('should not display the clear button when hideClearButton is true', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); - await act(async () => { - await user.click(combobox); - await user.type(combobox, 'test'); - }); + await user.click(combobox); + await user.type(combobox, 'test'); await waitFor(() => { const clearButton = screen.queryByRole('button', { name: /clear input value/i }); @@ -83,26 +73,22 @@ describe('SingleTypeaheadDropdown', () => { it('should focus the first item when ArrowDown key is pressed', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); // Press ArrowDown to open dropdown and focus first item - await act(async () => { - await user.click(combobox); - await user.keyboard('{ArrowDown}'); - }); + await user.click(combobox); + await user.keyboard('{ArrowDown}'); const firstOption = await screen.findByRole('option', { name: 'test1' }); expect(firstOption).toBeVisible(); @@ -115,26 +101,22 @@ describe('SingleTypeaheadDropdown', () => { it('should focus the last item when ArrowUp key is pressed on the first item', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); // Press ArrowUp to open dropdown and focus last item - await act(async () => { - await user.click(combobox); - await user.keyboard('{ArrowUp}'); - }); + await user.click(combobox); + await user.keyboard('{ArrowUp}'); expect(await screen.findByRole('option', { name: 'test1' })).toBeVisible(); expect(await screen.findByRole('option', { name: 'test2' })).toBeVisible(); @@ -147,30 +129,24 @@ describe('SingleTypeaheadDropdown', () => { it('should call onChange when an option is selected', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); - await act(async () => { - await user.click(combobox); - }); + await user.click(combobox); // Wait for dropdown to open and click on the first option const firstOption = await screen.findByRole('option', { name: 'test1' }); - await act(async () => { - await user.click(firstOption); - }); + await user.click(firstOption); await waitFor(() => { expect(onChange).toHaveBeenCalledWith('test1'); @@ -179,29 +155,23 @@ describe('SingleTypeaheadDropdown', () => { it('should clear the input when clear button is clicked', async () => { const user = userEvent.setup(); - await act(async () => { - renderWithProviders( - , - ); - }); + renderWithProviders( + , + ); const combobox = screen.getByRole('combobox'); - await act(async () => { - await user.click(combobox); - await user.clear(combobox); - await user.type(combobox, 'some text'); - }); + await user.click(combobox); + await user.clear(combobox); + await user.type(combobox, 'some text'); const clearButton = await screen.findByRole('button', { name: /clear input value/i }); - await act(async () => { - await user.click(clearButton); - }); + await user.click(clearButton); await waitFor(() => { expect(onChange).toHaveBeenCalledWith(''); diff --git a/frontend/public/components/utils/file-input.tsx b/frontend/public/components/utils/file-input.tsx index 29866e44198..621fe46c95f 100644 --- a/frontend/public/components/utils/file-input.tsx +++ b/frontend/public/components/utils/file-input.tsx @@ -121,6 +121,7 @@ export const DroppableFileInput: FC = ({ =4.8.4 <7.0.0" peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true jest: optional: true - checksum: 10c0/b8b09f7d8ba3d84a8779a6e95702a6e4dce45ab034e4edf5ddb631e77cd38dcdf791dfd9228e0a0d1d80d1eb2d278deb62ad2ec39f10fb8fd43cec07304e0c38 + typescript: + optional: true + checksum: 10c0/be8d59b0d4c1ff1afd5294b227cdd190a3a7741fb28f8092d359eda1010cb87ef61c3db6c0a794e9612f745ae008c4d53ee78754d8faf87a22a823b71c9b14e4 languageName: node linkType: hard @@ -11184,6 +11118,18 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-testing-library@npm:^7.2.0": + version: 7.16.2 + resolution: "eslint-plugin-testing-library@npm:7.16.2" + dependencies: + "@typescript-eslint/scope-manager": "npm:^8.56.0" + "@typescript-eslint/utils": "npm:^8.56.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/ea99f41eee929e0b139d927f8a89bef7ffe3bd33c51fc3c2c93d8e9d146e146395e1f7bb8a34d4b5bd9a1ffe7a1879959fc171e952c731e1e8310b079d9d5c97 + languageName: node + linkType: hard + "eslint-plugin-tsdoc@npm:^0.4.0": version: 0.4.0 resolution: "eslint-plugin-tsdoc@npm:0.4.0" @@ -11194,7 +11140,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -11214,7 +11160,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -11671,7 +11617,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.1.1, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.1.1": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -12733,20 +12679,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 - languageName: node - linkType: hard - "globrex@npm:^0.1.2": version: 0.1.2 resolution: "globrex@npm:0.1.2" @@ -16658,7 +16590,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": +"merge2@npm:^1.3.0": version: 1.4.1 resolution: "merge2@npm:1.4.1" checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb @@ -20491,7 +20423,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2, semver@npm:^7.7.3": +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.5.4, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2, semver@npm:^7.7.3": version: 7.7.4 resolution: "semver@npm:7.7.4" bin: @@ -21777,10 +21709,10 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.0.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0": - version: 2.3.0 - resolution: "tapable@npm:2.3.0" - checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 +"tapable@npm:^2.0.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0, tapable@npm:^2.3.3": + version: 2.3.3 + resolution: "tapable@npm:2.3.3" + checksum: 10c0/47992e861053f861154e92fb4a98ac4ab47b6463717e60792dd1e8c755da0c4964cd8bb68c308a9066d6da89000b6310457b4d5d985c30de4ccc29066068cc17 languageName: node linkType: hard @@ -22386,7 +22318,7 @@ __metadata: languageName: node linkType: hard -"tsutils@npm:3.21.0, tsutils@npm:^3.21.0": +"tsutils@npm:3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" dependencies: