diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2023-07-03 10:19:20 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-07-11 20:03:24 +0000 |
commit | c171b51e38f5270e866f87e28bc0382692e1ebaa (patch) | |
tree | 0a949fcf027b5b98a8fb510e4e9dfbd190a605fb | |
parent | 6975252c46f5b76a1018dcd232bcf9d5e200e4d7 (diff) | |
download | sonarqube-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.tsx | 6 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx | 130 |
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} />); + }; } |