Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,16 @@
"react-i18next": "~15.1.4",
"react-linkify": "^0.2.2",
"react-modal": "^3.16.3",
"react-redux": "8.1.3",
"react-redux": "9.2.0",
"react-router": "5.3.x",
"react-router-dom": "5.3.x",
"react-router-dom-v5-compat": "^6.11.2",
"react-router-hash-link": "^2.0.0",
"react-svg": "^16.2.0",
"react-tagsinput": "3.20.x",
"react-virtualized": "9.x",
"redux": "^4.0.4",
"redux-thunk": "2.4.0",
"redux": "^5.0.1",
"redux-thunk": "3.1.0",
"reselect": "4.x",
"sanitize-html": "^2.3.2",
"semver": "6.x",
Expand Down Expand Up @@ -308,7 +308,6 @@
"puppeteer-core": "^23.9.0",
"react-refresh": "^0.10.0",
"read-pkg": "5.x",
"redux-mock-store": "^1.5.3",
"resolve-url-loader": "2.x",
"sass": "^1.42.1",
"sass-loader": "^10.1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { act, renderHook } from '@testing-library/react';
import { useDispatch } from 'react-redux';
import type { Location } from 'react-router-dom-v5-compat';
import { useLocation } from 'react-router-dom-v5-compat';
import { k8sGet } from '@console/dynamic-plugin-sdk/src/utils/k8s';
import { ALL_NAMESPACES_KEY } from '@console/shared/src/constants';
Expand Down Expand Up @@ -41,19 +42,22 @@ jest.mock('../../user-preferences/namespace/usePreferredNamespace', () => ({
usePreferredNamespace: jest.fn(),
}));

const useDispatchMock = useDispatch as jest.Mock;
const useFlagMock = useFlag as jest.Mock;
const useLocationMock = useLocation as jest.Mock;
const useLastNamespaceMock = useLastNamespace as jest.Mock;
const usePreferredNamespaceMock = usePreferredNamespace as jest.Mock;
const k8sGetMock = k8sGet as jest.Mock;
const useStateMock = useState as jest.Mock;
const useDispatchMock = useDispatch as jest.MockedFunction<typeof useDispatch>;
const useFlagMock = useFlag as jest.MockedFunction<typeof useFlag>;
const useLocationMock = useLocation as jest.MockedFunction<typeof useLocation>;
const useLastNamespaceMock = useLastNamespace as jest.MockedFunction<typeof useLastNamespace>;
const usePreferredNamespaceMock = usePreferredNamespace as jest.MockedFunction<
typeof usePreferredNamespace
>;
const k8sGetMock = k8sGet as jest.MockedFunction<typeof k8sGet>;
const useStateMock = useState as jest.MockedFunction<typeof useState>;

const activeNamespace = 'active-ns';
const urlNamespace: string = 'url-ns';
const getLocationData = (valid = true) => ({
pathname: valid ? `home/ns/${urlNamespace}` : 'home/invalid',
});
const getLocationData = (valid = true) =>
({
pathname: valid ? `home/ns/${urlNamespace}` : 'home/invalid',
} as Location);
const lastNamespace: string = 'last-ns';
const preferredNamespace: string = 'preferred-ns';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jest.mock('@console/dynamic-plugin-sdk/src/perspective/useActivePerspective', ()
default: () => ['dev', jest.fn()],
}));

const useSelectorMock = useSelector as jest.Mock;
const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;
const useResolvedExtensionsMock = useResolvedExtensions as jest.Mock;
const useUserPreferenceCompatibilityMock = useUserPreferenceCompatibility as jest.Mock;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
Additional updates to these shared modules might occur before the 4.22 release is generally available.

- Upgraded from `react` v17 to v18. Plugins must use `react` 18 to remain compatible with Console.
- Upgraded from `react-redux` v7 to v8. Plugins must use `react-redux` v8 to remain compatible with Console.
- Upgraded from `react-i18next` v11 to v15. Plugins must use `react-i18next` v15 to remain compatible with Console.
- Upgraded from `react-redux` v7 to v9. Plugins must use `react-redux` v9 to remain compatible with Console.
- Upgraded from `redux` v4 to v5. Plugins must use `redux` v5 to remain compatible with Console.
- Upgraded from `redux-thunk` v2 to v3. Plugins must use `redux-thunk` v3 to remain compatible with Console.
- Added `@openshift/dynamic-plugin-sdk` to shared modules. Plugins must only use `@openshift-console/dynamic-plugin-sdk`
to remain compatible with Console.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ReactNode, FC } from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { thunk } from 'redux-thunk';
import { receivedResources } from '@console/internal/actions/k8s';
import { ConfigMapModel, SecretModel } from '@console/internal/models';
import { SDKReducers } from '../../../../app';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ReactNode, FC } from 'react';
import { act, cleanup, render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { thunk } from 'redux-thunk';
import { receivedResources } from '@console/internal/actions/k8s';
import { SDKReducers } from '../../../../app';
import type { WatchK8sResource } from '../../../../extensions/console-types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FC, ReactNode } from 'react';
import { act, cleanup, render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { thunk } from 'redux-thunk';
import { receivedResources } from '@console/internal/actions/k8s';
import { SDKReducers } from '../../../../app';
import type { WatchK8sResources } from '../../../../extensions/console-types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ jest.mock('../../../hooks/ols-hook', () => ({
useOLSConfig: jest.fn(),
}));

const useDispatchMock = useDispatch as jest.MockedFunction<typeof useDispatch>;
const useOLSConfigMock = useOLSConfig as jest.MockedFunction<typeof useOLSConfig>;
const useTranslationMock = useTranslation as jest.Mock;

describe('CodeEditorToolbar', () => {
const mockDispatch = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
(useTranslation as jest.Mock).mockReturnValue({ t: (key: string) => key });
(useDispatch as jest.Mock).mockReturnValue(mockDispatch);
useTranslationMock.mockReturnValue({ t: (key: string) => key });
useDispatchMock.mockReturnValue(mockDispatch);
});

it('should render null when showShortcuts is false and toolbarLinks is empty', () => {
Expand All @@ -37,19 +41,19 @@ describe('CodeEditorToolbar', () => {
});

it('should render "Ask OpenShift Lightspeed" button when showLightspeedButton is true', () => {
(useOLSConfig as jest.Mock).mockReturnValue(true);
useOLSConfigMock.mockReturnValue(true);
render(<AskOpenShiftLightspeedButton />);
expect(screen.getByRole('button')).toBeInTheDocument();
});

it('should not render "Ask OpenShift Lightspeed" button when showLightspeedButton is false', () => {
(useOLSConfig as jest.Mock).mockReturnValue(false);
useOLSConfigMock.mockReturnValue(false);
render(<AskOpenShiftLightspeedButton />);
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

it('should dispatch OpenOLS action when "Ask OpenShift Lightspeed" button is clicked', () => {
(useOLSConfig as jest.Mock).mockReturnValue(true);
useOLSConfigMock.mockReturnValue(true);
render(<AskOpenShiftLightspeedButton />);
const button = screen.getByRole('button');
fireEvent.click(button);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useGetUserSettingConfigMap } from '../useGetUserSettingConfigMap';

// Mock dependencies
const useK8sWatchResourceMock = useK8sWatchResource as jest.Mock;
const useSelectorMock = useSelector as jest.Mock;
const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;

jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({
useK8sWatchResource: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { renderHook } from '@testing-library/react';
import { useSelector, useDispatch } from 'react-redux';
import type { K8sResourceKind } from '@console/dynamic-plugin-sdk/src';
import { useK8sGet } from '@console/internal/components/utils/k8s-get-hook';
import { useUser } from '../useUser';

Expand All @@ -19,6 +20,11 @@ jest.mock('@console/internal/components/utils/k8s-get-hook', () => ({
useK8sGet: jest.fn(),
}));

const baseUserResource: K8sResourceKind = {
apiVersion: 'user.openshift.io/v1',
kind: 'User',
};

const mockSetUserResource = jest.fn((userResource) => ({
type: 'setUserResource',
payload: { userResource },
Expand All @@ -31,26 +37,30 @@ jest.mock('@console/dynamic-plugin-sdk', () => ({
setUserResource: (userResource: unknown) => mockSetUserResource(userResource),
}));

const mockDispatch = jest.fn();
const mockUseSelector = useSelector as jest.Mock;
const mockUseK8sGet = useK8sGet as jest.Mock;
const mockUseDispatch = useDispatch as jest.Mock;
const dispatchMock = jest.fn();
const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;
const useDispatchMock = useDispatch as jest.MockedFunction<typeof useDispatch>;
const useK8sGetMock = useK8sGet as jest.MockedFunction<typeof useK8sGet>;

describe('useUser', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseDispatch.mockReturnValue(mockDispatch);
useDispatchMock.mockReturnValue(dispatchMock);
});

it('should return user data with displayName from fullName when available', () => {
const mockUser = { username: 'testuser@example.com', uid: '123' };
const mockUserResource = { fullName: 'Test User', identities: ['testuser'] };
const mockUserResource = {
...baseUserResource,
fullName: 'Test User',
identities: ['testuser'],
};

mockUseSelector
useSelectorMock
.mockReturnValueOnce(mockUser) // for getUser
.mockReturnValueOnce(mockUserResource); // for getUserResource

mockUseK8sGet.mockReturnValue([mockUserResource, true, null]);
useK8sGetMock.mockReturnValue([mockUserResource, true, null]);

const { result } = renderHook(() => useUser());

Expand All @@ -63,11 +73,11 @@ describe('useUser', () => {

it('should fallback to username when fullName is not available', () => {
const mockUser = { username: 'testuser@example.com', uid: '123' };
const mockUserResource = { identities: ['testuser'] }; // No fullName
const mockUserResource = { ...baseUserResource, identities: ['testuser'] }; // No fullName

mockUseSelector.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);
useSelectorMock.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);

mockUseK8sGet.mockReturnValue([mockUserResource, true, null]);
useK8sGetMock.mockReturnValue([mockUserResource, true, null]);

const { result } = renderHook(() => useUser());

Expand All @@ -77,27 +87,27 @@ describe('useUser', () => {

it('should dispatch setUserResource when user resource is loaded', () => {
const mockUser = { username: 'testuser@example.com' };
const mockUserResource = { fullName: 'Test User' };
const mockUserResource = { ...baseUserResource, fullName: 'Test User' };

mockUseSelector.mockReturnValueOnce(mockUser).mockReturnValueOnce(null); // No userResource in Redux yet
useSelectorMock.mockReturnValueOnce(mockUser).mockReturnValueOnce(null); // No userResource in Redux yet

mockUseK8sGet.mockReturnValue([mockUserResource, true, null]);
useK8sGetMock.mockReturnValue([mockUserResource, true, null]);

renderHook(() => useUser());

expect(mockDispatch).toHaveBeenCalledWith({
expect(dispatchMock).toHaveBeenCalledWith({
type: 'setUserResource',
payload: { userResource: mockUserResource },
});
});

it('should handle edge cases with empty strings and fallback to "Unknown user"', () => {
const mockUser = { username: '' }; // Empty username
const mockUserResource = { fullName: ' ' }; // Whitespace-only fullName
const mockUserResource = { ...baseUserResource, fullName: ' ' }; // Whitespace-only fullName

mockUseSelector.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);
useSelectorMock.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);

mockUseK8sGet.mockReturnValue([mockUserResource, true, null]);
useK8sGetMock.mockReturnValue([mockUserResource, true, null]);

const { result } = renderHook(() => useUser());

Expand All @@ -106,11 +116,11 @@ describe('useUser', () => {

it('should trim whitespace from fullName and username', () => {
const mockUser = { username: ' testuser@example.com ' };
const mockUserResource = { fullName: ' Test User ' };
const mockUserResource = { ...baseUserResource, fullName: ' Test User ' };

mockUseSelector.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);
useSelectorMock.mockReturnValueOnce(mockUser).mockReturnValueOnce(mockUserResource);

mockUseK8sGet.mockReturnValue([mockUserResource, true, null]);
useK8sGetMock.mockReturnValue([mockUserResource, true, null]);

const { result } = renderHook(() => useUser());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {
import { useUserPreference } from '../useUserPreference';

const useK8sWatchResourceMock = useK8sWatchResource as jest.Mock;
const createConfigMapMock = createConfigMap as jest.Mock;
const updateConfigMapMock = updateConfigMap as jest.Mock;
const useSelectorMock = useSelector as jest.Mock;
const useFavoritesOptionsMock = useFavoritesOptions as jest.Mock;
const createConfigMapMock = createConfigMap as jest.MockedFunction<typeof createConfigMap>;
const updateConfigMapMock = updateConfigMap as jest.MockedFunction<typeof updateConfigMap>;
const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;
const useFavoritesOptionsMock = useFavoritesOptions as jest.MockedFunction<
typeof useFavoritesOptions
>;

jest.mock('@console/internal/components/useFavoritesOptions', () => ({
useFavoritesOptions: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type WrapperProps = {
};

// Create a Redux store with reducer and initial state.
const rootReducer = combineReducers<RootState>(baseReducers);
const rootReducer = combineReducers(baseReducers);
const setupStore = (initialState?: Partial<RootState>) => {
const store = createStore(rootReducer, initialState);
// Set the store in storeHandler so that modules like rbac.tsx can access it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));

const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;

describe('usePerspectiveDetection', () => {
it('should return loading as true if CAN_GET_NS flag is pending', () => {
(useSelector as jest.Mock).mockImplementation(() => ({
useSelectorMock.mockImplementation(() => ({
CAN_GET_NS: undefined,
}));

Expand All @@ -21,7 +23,7 @@ describe('usePerspectiveDetection', () => {
});

it('should return loading as false if CAN_GET_NS flag is loaded', () => {
(useSelector as jest.Mock).mockImplementation(() => ({
useSelectorMock.mockImplementation(() => ({
CAN_GET_NS: false,
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../__mocks__/Kamelet-data';
import EventSink from '../EventSink';

const useSelectorMock = useSelector as jest.Mock;
const useSelectorMock = useSelector as jest.MockedFunction<typeof useSelector>;

jest.mock('react-redux', () => {
const originalModule = jest.requireActual('react-redux');
Expand Down
8 changes: 4 additions & 4 deletions frontend/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import * as _ from 'lodash';
import { useState, useRef, useCallback, useEffect, useLayoutEffect, memo, Suspense } from 'react';
import type { FC, Provider as ProviderComponent, ReactNode } from 'react';
import { render } from 'react-dom';
import { createRoot } from 'react-dom/client';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { linkify } from 'react-linkify';
import * as Modal from 'react-modal';
Expand Down Expand Up @@ -351,7 +351,8 @@ const AppWithExtensions: FC = () => {
return <LoadingBox blame="AppWithExtensions" />;
};

render(<LoadingBox blame="Init" />, document.getElementById('app'));
const renderRoot = createRoot(document.getElementById('app')!);
renderRoot.render(<LoadingBox blame="Init" />);

const AppRouter: FC = () => {
const standaloneRouteExtensions = useExtensions(isStandaloneRoutePage);
Expand Down Expand Up @@ -526,7 +527,7 @@ graphQLReady.onReady(() => {
}
}

render(
renderRoot.render(
<Suspense fallback={<LoadingBox blame="Root suspense" />}>
<Provider store={store}>
<PluginStoreProvider store={pluginStore}>
Expand All @@ -543,6 +544,5 @@ graphQLReady.onReady(() => {
</PluginStoreProvider>
</Provider>
</Suspense>,
document.getElementById('app'),
);
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReactNode, FC } from 'react';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { thunk } from 'redux-thunk';
import { Provider } from 'react-redux';
import { act, cleanup, render } from '@testing-library/react';
import { SDKReducers } from '@console/dynamic-plugin-sdk/src/app';
Expand Down
Loading