Browse Source

SONAR-20254 Migrate StartupModal test to RTL

tags/10.2.0.77647
Jeremy Davis 9 months ago
parent
commit
5acf315af5

+ 36
- 37
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx View File

@@ -61,45 +61,44 @@ export default function GlobalContainer() {
<ThemeProvider theme={lightTheme}>
<SuggestionsProvider>
<A11yProvider>
<StartupModal>
<A11ySkipLinks />
<div className="global-container">
<div
className={classNames('page-wrapper', {
'new-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND.includes(location.pathname),
'white-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE.includes(
location.pathname
),
})}
id="container"
>
<div className="page-container">
<Workspace>
<IndexationContextProvider>
<LanguagesContextProvider>
<MetricsContextProvider>
<div className="sw-sticky sw-top-0 sw-z-global-navbar">
<SystemAnnouncement />
<IndexationNotification />
<NCDAutoUpdateMessage />
<UpdateNotification dismissable />
<GlobalNav location={location} />
{/* The following is the portal anchor point for the component nav
* See ComponentContainer.tsx
*/}
<div id="component-nav-portal" />
</div>
<Outlet />
</MetricsContextProvider>
</LanguagesContextProvider>
</IndexationContextProvider>
</Workspace>
</div>
<PromotionNotification />
<A11ySkipLinks />
<div className="global-container">
<div
className={classNames('page-wrapper', {
'new-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND.includes(location.pathname),
'white-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE.includes(
location.pathname
),
})}
id="container"
>
<div className="page-container">
<Workspace>
<IndexationContextProvider>
<LanguagesContextProvider>
<MetricsContextProvider>
<div className="sw-sticky sw-top-0 sw-z-global-navbar">
<SystemAnnouncement />
<IndexationNotification />
<NCDAutoUpdateMessage />
<UpdateNotification dismissable />
<GlobalNav location={location} />
{/* The following is the portal anchor point for the component nav
* See ComponentContainer.tsx
*/}
<div id="component-nav-portal" />
</div>
<Outlet />
</MetricsContextProvider>
</LanguagesContextProvider>
</IndexationContextProvider>
</Workspace>
</div>
<GlobalFooter />
<PromotionNotification />
</div>
</StartupModal>
<GlobalFooter />
</div>
<StartupModal />
</A11yProvider>
</SuggestionsProvider>
</ThemeProvider>

+ 5
- 17
server/sonar-web/src/main/js/app/components/StartupModal.tsx View File

@@ -21,7 +21,6 @@ import { differenceInDays } from 'date-fns';
import * as React from 'react';
import { showLicense } from '../../api/editions';
import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal';
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import { parseDate, toShortISO8601String } from '../../helpers/dates';
import { hasMessage } from '../../helpers/l10n';
import { get, save } from '../../helpers/storage';
@@ -31,16 +30,10 @@ import { CurrentUser, isLoggedIn } from '../../types/users';
import withAppStateContext from './app-state/withAppStateContext';
import withCurrentUserContext from './current-user/withCurrentUserContext';

interface StateProps {
interface Props {
currentUser: CurrentUser;
}

type Props = {
children?: React.ReactNode;
location: Location;
router: Router;
appState: AppState;
};
}

interface State {
open?: boolean;
@@ -48,7 +41,7 @@ interface State {

const LICENSE_PROMPT = 'sonarqube.license.prompt';

export class StartupModal extends React.PureComponent<Props & StateProps, State> {
export class StartupModal extends React.PureComponent<Props, State> {
state: State = {};

componentDidMount() {
@@ -82,13 +75,8 @@ export class StartupModal extends React.PureComponent<Props & StateProps, State>

render() {
const { open } = this.state;
return (
<>
{this.props.children}
{open && <LicensePromptModal onClose={this.closeLicense} />}
</>
);
return open ? <LicensePromptModal onClose={this.closeLicense} /> : null;
}
}

export default withCurrentUserContext(withRouter(withAppStateContext(StartupModal)));
export default withCurrentUserContext(withAppStateContext(StartupModal));

+ 35
- 62
server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx View File

@@ -17,16 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { differenceInDays } from 'date-fns';
import { shallow, ShallowWrapper } from 'enzyme';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { showLicense } from '../../../api/editions';
import { toShortISO8601String } from '../../../helpers/dates';
import { hasMessage } from '../../../helpers/l10n';
import { mockLicense } from '../../../helpers/mocks/editions';
import { get, save } from '../../../helpers/storage';
import { mockAppState, mockLocation, mockRouter } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { save } from '../../../helpers/storage';
import { mockAppState, mockCurrentUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import { byRole, byText } from '../../../helpers/testSelector';
import { EditionKey } from '../../../types/editions';
import { LoggedInUser } from '../../../types/users';
import { StartupModal } from '../StartupModal';
@@ -40,10 +36,6 @@ jest.mock('../../../helpers/storage', () => ({
save: jest.fn(),
}));

jest.mock('../../../helpers/l10n', () => ({
hasMessage: jest.fn().mockReturnValue(true),
}));

jest.mock('../../../helpers/dates', () => ({
parseDate: jest.fn().mockReturnValue('parsed-date'),
toShortISO8601String: jest.fn().mockReturnValue('short-not-iso-date'),
@@ -61,73 +53,54 @@ const LOGGED_IN_USER: LoggedInUser = {
};

beforeEach(() => {
jest.mocked(differenceInDays).mockClear();
jest.mocked(hasMessage).mockClear();
jest.mocked(get).mockClear();
jest.mocked(save).mockClear();
jest.mocked(showLicense).mockClear();
jest.mocked(toShortISO8601String).mockClear();
jest.clearAllMocks();
});

it('should render only the children', async () => {
const wrapper = getWrapper({ appState: mockAppState({ edition: EditionKey.community }) });
await shouldNotHaveModals(wrapper);
expect(showLicense).toHaveBeenCalledTimes(0);
expect(wrapper.find('div').exists()).toBe(true);

await shouldNotHaveModals(getWrapper({ appState: mockAppState({ canAdmin: false }) }));
it('should check license and open on its own', async () => {
const user = userEvent.setup();
renderStartupModal();

jest.mocked(hasMessage).mockReturnValueOnce(false);
await shouldNotHaveModals(getWrapper());
await new Promise(setImmediate);

jest.mocked(showLicense).mockResolvedValueOnce(mockLicense({ isValidEdition: true }));
await shouldNotHaveModals(getWrapper());
expect(save).toHaveBeenCalled();
expect(ui.modalHeader.get()).toBeInTheDocument();

jest.mocked(get).mockReturnValueOnce('date');
jest.mocked(differenceInDays).mockReturnValueOnce(0);
await shouldNotHaveModals(getWrapper());
await user.click(ui.licensePageLink.get());

await shouldNotHaveModals(
getWrapper({
appState: mockAppState({ canAdmin: false }),
currentUser: { ...LOGGED_IN_USER },
location: mockLocation({ pathname: '/create-organization' }),
})
);
expect(byText('/admin/extension/license/app').get()).toBeInTheDocument();
});

it('should render license prompt', async () => {
await shouldDisplayLicense(getWrapper());
expect(save).toHaveBeenCalledWith('sonarqube.license.prompt', 'short-not-iso-date', 'luke');
it.each([
[
'community edition',
{ appState: mockAppState({ canAdmin: true, edition: EditionKey.community }) },
],
['Cannot admin', { appState: mockAppState({ canAdmin: false, edition: EditionKey.enterprise }) }],
['User is not logged in', { currentUser: mockCurrentUser() }],
])('should not open when not necessary: %s', async (_, props: Partial<StartupModal['props']>) => {
renderStartupModal(props);

jest.mocked(get).mockReturnValueOnce('date');
jest.mocked(differenceInDays).mockReturnValueOnce(1);
await shouldDisplayLicense(getWrapper());
await new Promise(setImmediate);

jest.mocked(showLicense).mockResolvedValueOnce(mockLicense({ isValidEdition: false }));
await shouldDisplayLicense(getWrapper());
expect(save).not.toHaveBeenCalled();
expect(ui.modalHeader.query()).not.toBeInTheDocument();
});

async function shouldNotHaveModals(wrapper: ShallowWrapper) {
await waitAndUpdate(wrapper);
expect(wrapper.find('LicensePromptModal').exists()).toBe(false);
}

async function shouldDisplayLicense(wrapper: ShallowWrapper) {
await waitAndUpdate(wrapper);
expect(wrapper.find('LicensePromptModal').exists()).toBe(true);
}

function getWrapper(props: Partial<StartupModal['props']> = {}) {
return shallow<StartupModal>(
function renderStartupModal(props: Partial<StartupModal['props']> = {}) {
return renderApp(
'/',
<StartupModal
appState={mockAppState({ edition: EditionKey.enterprise, canAdmin: true })}
currentUser={LOGGED_IN_USER}
location={mockLocation({ pathname: 'foo/bar' })}
router={mockRouter()}
{...props}
>
<div />
</StartupModal>
);
}

const ui = {
modalHeader: byRole('heading', { name: 'license.prompt.title' }),
licensePageLink: byRole('link', { name: 'license.prompt.link' }),
modalCancelButton: byRole('button', { name: 'cancel' }),
};

Loading…
Cancel
Save