aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2023-07-03 10:19:20 +0200
committersonartech <sonartech@sonarsource.com>2023-07-11 20:03:24 +0000
commitc171b51e38f5270e866f87e28bc0382692e1ebaa (patch)
tree0a949fcf027b5b98a8fb510e4e9dfbd190a605fb
parent6975252c46f5b76a1018dcd232bcf9d5e200e4d7 (diff)
downloadsonarqube-c171b51e38f5270e866f87e28bc0382692e1ebaa.tar.gz
sonarqube-c171b51e38f5270e866f87e28bc0382692e1ebaa.zip
[NO JIRA] Improve test for extension.
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/Extension.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx130
2 files changed, 102 insertions, 34 deletions
diff --git a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
index 8f40bc13306..d3535c539be 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
@@ -36,7 +36,7 @@ import * as theme from '../../theme';
import withAppStateContext from '../app-state/withAppStateContext';
import withCurrentUserContext from '../current-user/withCurrentUserContext';
-interface Props extends WrappedComponentProps {
+export interface ExtensionProps extends WrappedComponentProps {
theme: Theme;
appState: AppState;
currentUser: CurrentUser;
@@ -51,7 +51,7 @@ interface State {
extensionElement?: React.ReactElement<any>;
}
-class Extension extends React.PureComponent<Props, State> {
+class Extension extends React.PureComponent<ExtensionProps, State> {
container?: HTMLElement | null;
stop?: Function;
state: State = {};
@@ -60,7 +60,7 @@ class Extension extends React.PureComponent<Props, State> {
this.startExtension();
}
- componentDidUpdate(prevProps: Props) {
+ componentDidUpdate(prevProps: ExtensionProps) {
if (prevProps.extension !== this.props.extension) {
this.stopExtension();
this.startExtension();
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
index 3c9d4d6abd5..647e202f81d 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
@@ -17,52 +17,120 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { screen } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { lightTheme } from 'design-system';
import * as React from 'react';
-import { getExtensionStart } from '../../../../helpers/extensions';
+import { IntlShape } from 'react-intl';
+import { getEnhancedWindow } from '../../../../helpers/browser';
+import { installExtensionsHandler } from '../../../../helpers/extensionsHandler';
+import { addGlobalErrorMessage } from '../../../../helpers/globalMessages';
+import {
+ mockAppState,
+ mockCurrentUser,
+ mockLocation,
+ mockRouter,
+} from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import Extension from '../Extension';
+import { ExtensionStartMethodParameter } from '../../../../types/extension';
+import Extension, { ExtensionProps } from '../Extension';
-jest.mock('../../../../helpers/extensions', () => ({
- getExtensionStart: jest.fn().mockResolvedValue({}),
-}));
+jest.mock('../../../../helpers/globalMessages');
-beforeEach(() => {
- jest.clearAllMocks();
-});
+beforeAll(() => {
+ installExtensionsHandler();
+
+ getEnhancedWindow().registerExtension(
+ 'first-react-extension',
+ ({ location, router }: ExtensionStartMethodParameter) => {
+ const suffix = location.pathname === '/new' ? ' change' : '';
+ const handleClick = () => {
+ router.push('new');
+ };
+ return (
+ <div>
+ <button onClick={handleClick} type="button">
+ Click first react{suffix}
+ </button>
+ </div>
+ );
+ }
+ );
-const extensionView = <button type="button">Extension</button>;
+ getEnhancedWindow().registerExtension(
+ 'not-react-extension',
+ ({ el }: ExtensionStartMethodParameter) => {
+ if (el) {
+ el.innerHTML = '<button type="button">Click not react</button>';
+ return () => {
+ el.innerHTML = '';
+ };
+ }
+ }
+ );
+
+ getEnhancedWindow().registerExtension('second-extension', () => {
+ return (
+ <div>
+ <button type="button">Click second</button>
+ </div>
+ );
+ });
+});
it('should render React extensions correctly', async () => {
- const start = jest.fn().mockReturnValue(extensionView);
- jest.mocked(getExtensionStart).mockResolvedValue(start);
+ const user = userEvent.setup();
+ renderExtention();
+
+ expect(await screen.findByRole('button', { name: 'Click first react' })).toBeInTheDocument();
- const { rerender } = renderExtension();
- expect(await screen.findByRole('button', { name: 'Extension' })).toBeInTheDocument();
- expect(start).toHaveBeenCalled();
+ await user.click(screen.getByRole('button', { name: 'Click first react' }));
- rerender(<Extension extension={{ key: 'BAR', name: 'BAR' }} />);
- expect(screen.queryByRole('button', { name: 'Extension' })).not.toBeInTheDocument();
- expect(await screen.findByRole('button', { name: 'Extension' })).toBeInTheDocument();
+ expect(
+ await screen.findByRole('button', { name: 'Click first react change' })
+ ).toBeInTheDocument();
});
-it('should handle Function extensions correctly', async () => {
- const stop = jest.fn();
- const start = jest.fn(() => stop);
- jest.mocked(getExtensionStart).mockResolvedValue(start);
+it('should unmount an extension before starting a new one', async () => {
+ const rerender = renderExtention();
+ await screen.findByRole('button', { name: 'Click first react' });
- const { rerender } = renderExtension();
- await new Promise(setImmediate);
- expect(start).toHaveBeenCalled();
+ rerender({ extension: { key: 'not-react-extension', name: 'not-react-extension' } });
+ expect(await screen.findByRole('button', { name: 'Click not react' })).toBeInTheDocument();
+
+ rerender({ extension: { key: 'second-extension', name: 'second-extension' } });
+ expect(await screen.findByRole('button', { name: 'Click second' })).toBeInTheDocument();
+});
+
+it('should warn when no extension found', async () => {
+ renderExtention({ extension: { key: 'unknown', name: 'null' } });
- rerender(<Extension extension={{ key: 'BAR', name: 'BAR' }} />);
+ // JSDOM is not handling script loading so we need to simulate that.
+ await waitFor(() => {
+ // eslint-disable-next-line testing-library/no-node-access
+ const script = document.querySelector('script');
+ expect(script).toBeInTheDocument();
+ script!.onload!(new Event(''));
+ });
- expect(stop).toHaveBeenCalled();
+ await new Promise(setImmediate);
+ expect(addGlobalErrorMessage).toHaveBeenCalled();
});
-function renderExtension(props: Partial<typeof Extension> = {}) {
- return renderComponent(
- <Extension theme={lightTheme} extension={{ key: 'foo', name: 'Foo' }} {...props} />
- );
+function renderExtention(props: Partial<ExtensionProps> = {}) {
+ const originalProp = {
+ theme: lightTheme,
+ appState: mockAppState(),
+ currentUser: mockCurrentUser(),
+ extension: { key: 'first-react-extension', name: 'first-react-extension' },
+ intl: {} as IntlShape,
+ location: mockLocation(),
+ router: mockRouter(),
+ updateCurrentUserHomepage: jest.fn(),
+ } as const;
+ const { rerender } = renderComponent(<Extension {...originalProp} {...props} />);
+
+ return (rerenderProp: Partial<ExtensionProps> = {}) => {
+ rerender(<Extension {...originalProp} {...props} {...rerenderProp} />);
+ };
}