@@ -21,29 +21,18 @@ import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; | |||
import { fetchLanguages } from '../../store/rootActions'; | |||
import { getAppState, getCurrentUser, getGlobalSettingValue, Store } from '../../store/rootReducer'; | |||
import { getGlobalSettingValue, Store } from '../../store/rootReducer'; | |||
import KeyboardShortcutsModal from './KeyboardShortcutsModal'; | |||
const PageTracker = lazyLoadComponent(() => import('./PageTracker')); | |||
interface StateProps { | |||
appState: T.AppState | undefined; | |||
currentUser: T.CurrentUser | undefined; | |||
interface Props { | |||
fetchLanguages: () => void; | |||
enableGravatar: boolean; | |||
gravatarServerUrl: string; | |||
} | |||
interface DispatchProps { | |||
fetchLanguages: () => Promise<void>; | |||
} | |||
interface OwnProps { | |||
children: JSX.Element; | |||
} | |||
type Props = StateProps & DispatchProps & OwnProps; | |||
class App extends React.PureComponent<Props> { | |||
export class App extends React.PureComponent<Props> { | |||
mounted = false; | |||
componentDidMount() { | |||
@@ -102,19 +91,15 @@ class App extends React.PureComponent<Props> { | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => { | |||
const mapStateToProps = (state: Store) => { | |||
const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar'); | |||
const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl'); | |||
return { | |||
appState: getAppState(state), | |||
currentUser: getCurrentUser(state), | |||
enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'), | |||
gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || '' | |||
}; | |||
}; | |||
const mapDispatchToProps = ({ | |||
fetchLanguages | |||
} as any) as DispatchProps; | |||
const mapDispatchToProps = { fetchLanguages }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(App); |
@@ -0,0 +1,73 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { fetchLanguages as realFetchLanguages } from '../../../store/rootActions'; | |||
import { App } from '../App'; | |||
jest.mock('react-redux', () => ({ | |||
connect: jest.fn(() => (a: any) => a) | |||
})); | |||
jest.mock('../../../store/rootReducer', () => ({ | |||
getGlobalSettingValue: jest.fn((_, key: string) => ({ | |||
value: key === 'sonar.lf.enableGravatar' ? 'true' : 'http://gravatar.com' | |||
})) | |||
})); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
shallowRender({ enableGravatar: true, gravatarServerUrl: 'http://example.com' }) | |||
).toMatchSnapshot('with gravatar'); | |||
}); | |||
it('should correctly fetch available languages', () => { | |||
const fetchLanguages = jest.fn(); | |||
shallowRender({ fetchLanguages }); | |||
expect(fetchLanguages).toBeCalled(); | |||
}); | |||
it('should correctly set the scrollbar width as a custom property', () => { | |||
shallowRender(); | |||
expect(document.body.style.getPropertyValue('--sbw')).toBe('0px'); | |||
}); | |||
describe('redux', () => { | |||
it('should correctly map state and dispatch props', () => { | |||
const [mapStateToProps, mapDispatchToProps] = (connect as jest.Mock).mock.calls[0]; | |||
expect(mapStateToProps({})).toEqual({ | |||
enableGravatar: true, | |||
gravatarServerUrl: 'http://gravatar.com' | |||
}); | |||
expect(mapDispatchToProps).toEqual( | |||
expect.objectContaining({ fetchLanguages: realFetchLanguages }) | |||
); | |||
}); | |||
}); | |||
function shallowRender(props: Partial<App['props']> = {}) { | |||
return shallow<App>( | |||
<App fetchLanguages={jest.fn()} enableGravatar={false} gravatarServerUrl="" {...props} /> | |||
); | |||
} |
@@ -0,0 +1,20 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Fragment> | |||
<LazyComponentWrapper /> | |||
<KeyboardShortcutsModal /> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: with gravatar 1`] = ` | |||
<Fragment> | |||
<LazyComponentWrapper> | |||
<link | |||
href="http://example.com" | |||
rel="preconnect" | |||
/> | |||
</LazyComponentWrapper> | |||
<KeyboardShortcutsModal /> | |||
</Fragment> | |||
`; |
@@ -92,7 +92,7 @@ interface Props { | |||
branchLike?: BranchLike; | |||
component?: T.Component; | |||
currentUser: T.CurrentUser; | |||
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => Promise<void>; | |||
fetchBranchStatus: (branchLike: BranchLike, projectKey: string) => void; | |||
fetchIssues: (query: T.RawQuery) => Promise<FetchIssuesPromise>; | |||
location: Location; | |||
onBranchesChange?: () => void; | |||
@@ -859,7 +859,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
renderBulkChange(openIssue: T.Issue | undefined) { | |||
renderBulkChange() { | |||
const { component, currentUser } = this.props; | |||
const { checkAll, bulkChangeModal, checked, issues, paging } = this.state; | |||
@@ -867,7 +867,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
const thirdState = checked.length > 0 && !isAllChecked; | |||
const isChecked = isAllChecked || thirdState; | |||
if (!currentUser.isLoggedIn || openIssue) { | |||
if (!currentUser.isLoggedIn) { | |||
return null; | |||
} | |||
@@ -1062,7 +1062,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<div className="layout-page-main-inner"> | |||
<A11ySkipTarget anchor="issues_main" /> | |||
{this.renderBulkChange(openIssue)} | |||
{this.renderBulkChange()} | |||
<PageActions | |||
canSetHome={!this.props.component} | |||
effortTotal={this.state.effortTotal} |
@@ -25,16 +25,10 @@ import { withRouter } from '../../../components/hoc/withRouter'; | |||
import { parseIssueFromResponse } from '../../../helpers/issues'; | |||
import { fetchBranchStatus } from '../../../store/rootActions'; | |||
import { getCurrentUser, Store } from '../../../store/rootReducer'; | |||
import { FetchIssuesPromise } from '../../../types/issues'; | |||
const IssuesAppContainer = lazyLoadComponent(() => import('./App'), 'IssuesAppContainer'); | |||
interface StateProps { | |||
currentUser: T.CurrentUser; | |||
fetchIssues: (query: T.RawQuery) => Promise<FetchIssuesPromise>; | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
const mapStateToProps = (state: Store) => ({ | |||
currentUser: getCurrentUser(state), | |||
fetchIssues | |||
}); | |||
@@ -54,9 +48,6 @@ const fetchIssues = (query: T.RawQuery) => { | |||
.catch(throwGlobalError); | |||
}; | |||
// have to type cast this, because of async action | |||
const mapDispatchToProps = { | |||
fetchBranchStatus: fetchBranchStatus as any | |||
}; | |||
const mapDispatchToProps = { fetchBranchStatus }; | |||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IssuesAppContainer)); |
@@ -18,9 +18,16 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as key from 'keymaster'; | |||
import * as React from 'react'; | |||
import handleRequiredAuthentication from 'sonar-ui-common/helpers/handleRequiredAuthentication'; | |||
import { KeyCodes } from 'sonar-ui-common/helpers/keycodes'; | |||
import { | |||
addSideBarClass, | |||
addWhitePageClass, | |||
removeSideBarClass, | |||
removeWhitePageClass | |||
} from 'sonar-ui-common/helpers/pages'; | |||
import { KEYCODE_MAP, keydown, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockPullRequest } from '../../../../helpers/mocks/branch-like'; | |||
import { | |||
@@ -33,6 +40,7 @@ import { | |||
mockRouter | |||
} from '../../../../helpers/testMocks'; | |||
import { | |||
disableLocationsNavigator, | |||
enableLocationsNavigator, | |||
selectNextFlow, | |||
selectNextLocation, | |||
@@ -40,6 +48,14 @@ import { | |||
selectPreviousLocation | |||
} from '../../actions'; | |||
import App from '../App'; | |||
import BulkChangeModal from '../BulkChangeModal'; | |||
jest.mock('sonar-ui-common/helpers/pages', () => ({ | |||
addSideBarClass: jest.fn(), | |||
addWhitePageClass: jest.fn(), | |||
removeSideBarClass: jest.fn(), | |||
removeWhitePageClass: jest.fn() | |||
})); | |||
jest.mock('sonar-ui-common/helpers/handleRequiredAuthentication', () => ({ | |||
default: jest.fn() | |||
@@ -54,10 +70,13 @@ jest.mock('keymaster', () => { | |||
return true; | |||
}); | |||
}; | |||
let scope = 'issues'; | |||
key.getScope = () => 'issues'; | |||
key.setScope = () => {}; | |||
key.deleteScope = () => {}; | |||
key.getScope = () => scope; | |||
key.setScope = (newScope: string) => { | |||
scope = newScope; | |||
}; | |||
key.deleteScope = jest.fn(); | |||
return key; | |||
}); | |||
@@ -73,12 +92,36 @@ const PAGING = { pageIndex: 1, pageSize: 100, total: 4 }; | |||
const referencedComponent = { key: 'foo-key', name: 'bar', uuid: 'foo-uuid' }; | |||
const originalAddEventListener = window.addEventListener; | |||
const originalRemoveEventListener = window.removeEventListener; | |||
beforeEach(() => { | |||
Object.defineProperty(window, 'addEventListener', { | |||
value: jest.fn() | |||
}); | |||
Object.defineProperty(window, 'removeEventListener', { | |||
value: jest.fn() | |||
}); | |||
}); | |||
afterEach(() => { | |||
Object.defineProperty(window, 'addEventListener', { | |||
value: originalAddEventListener | |||
}); | |||
Object.defineProperty(window, 'removeEventListener', { | |||
value: originalRemoveEventListener | |||
}); | |||
}); | |||
it('should render a list of issue', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().issues.length).toBe(4); | |||
expect(wrapper.state().referencedComponentsById).toEqual({ 'foo-uuid': referencedComponent }); | |||
expect(wrapper.state().referencedComponentsByKey).toEqual({ 'foo-key': referencedComponent }); | |||
expect(addSideBarClass).toBeCalled(); | |||
expect(addWhitePageClass).toBeCalled(); | |||
}); | |||
it('should not render for anonymous user', () => { | |||
@@ -150,6 +193,37 @@ it('should correctly bind key events for issue navigation', async () => { | |||
keydown(KeyCodes.LeftArrow); | |||
expect(push).toBeCalledTimes(2); | |||
expect(window.addEventListener).toBeCalledTimes(2); | |||
}); | |||
it('should correctly clean up on unmount', () => { | |||
const wrapper = shallowRender(); | |||
wrapper.unmount(); | |||
expect(key.deleteScope).toBeCalled(); | |||
expect(removeSideBarClass).toBeCalled(); | |||
expect(removeWhitePageClass).toBeCalled(); | |||
expect(window.removeEventListener).toBeCalledTimes(2); | |||
}); | |||
it('should be able to bulk change specific issues', async () => { | |||
const wrapper = shallowRender({ currentUser: mockLoggedInUser() }); | |||
await waitAndUpdate(wrapper); | |||
const instance = wrapper.instance(); | |||
expect(wrapper.state().checked.length).toBe(0); | |||
instance.handleIssueCheck('foo'); | |||
instance.handleIssueCheck('bar'); | |||
expect(wrapper.state().checked.length).toBe(2); | |||
instance.handleOpenBulkChange(); | |||
wrapper.update(); | |||
expect(wrapper.find(BulkChangeModal).exists()).toBe(true); | |||
const { issues } = await wrapper | |||
.find(BulkChangeModal) | |||
.props() | |||
.fetchIssues({}); | |||
expect(issues).toHaveLength(2); | |||
}); | |||
it('should be able to uncheck all issue with global checkbox', async () => { | |||
@@ -309,6 +383,10 @@ describe('keydown event handler', () => { | |||
jest.resetAllMocks(); | |||
}); | |||
afterEach(() => { | |||
key.setScope('issues'); | |||
}); | |||
it('should handle alt', () => { | |||
instance.handleKeyDown(mockEvent({ keyCode: 18 })); | |||
expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator); | |||
@@ -329,6 +407,36 @@ describe('keydown event handler', () => { | |||
instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 39 })); | |||
expect(instance.setState).toHaveBeenCalledWith(selectNextFlow); | |||
}); | |||
it('should ignore different scopes', () => { | |||
key.setScope('notissues'); | |||
instance.handleKeyDown(mockEvent({ keyCode: 18 })); | |||
expect(instance.setState).not.toHaveBeenCalled(); | |||
}); | |||
}); | |||
describe('keyup event handler', () => { | |||
const wrapper = shallowRender(); | |||
const instance = wrapper.instance(); | |||
jest.spyOn(instance, 'setState'); | |||
beforeEach(() => { | |||
jest.resetAllMocks(); | |||
}); | |||
afterEach(() => { | |||
key.setScope('issues'); | |||
}); | |||
it('should handle alt', () => { | |||
instance.handleKeyUp(mockEvent({ keyCode: 18 })); | |||
expect(instance.setState).toHaveBeenCalledWith(disableLocationsNavigator); | |||
}); | |||
it('should ignore different scopes', () => { | |||
key.setScope('notissues'); | |||
instance.handleKeyUp(mockEvent({ keyCode: 18 })); | |||
expect(instance.setState).not.toHaveBeenCalled(); | |||
}); | |||
}); | |||
it('should fetch more issues', async () => { |
@@ -0,0 +1,59 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import { searchIssues } from '../../../../api/issues'; | |||
import { mockCurrentUser } from '../../../../helpers/testMocks'; | |||
import { fetchBranchStatus } from '../../../../store/rootActions'; | |||
import '../AppContainer'; | |||
jest.mock('react-redux', () => ({ | |||
connect: jest.fn(() => (a: any) => a) | |||
})); | |||
jest.mock('../../../../api/issues', () => ({ | |||
searchIssues: jest.fn().mockResolvedValue({ issues: [{ some: 'issue' }], bar: 'baz' }) | |||
})); | |||
jest.mock('../../../../helpers/issues', () => ({ | |||
parseIssueFromResponse: jest.fn(() => 'parsedIssue') | |||
})); | |||
jest.mock('../../../../store/rootReducer', () => { | |||
const { mockCurrentUser } = jest.requireActual('../../../../helpers/testMocks'); | |||
return { | |||
getCurrentUser: jest.fn(() => mockCurrentUser()) | |||
}; | |||
}); | |||
describe('redux', () => { | |||
it('should correctly map state and dispatch props', async () => { | |||
const [mapStateToProps, mapDispatchToProps] = (connect as jest.Mock).mock.calls[0]; | |||
const { currentUser, fetchIssues } = mapStateToProps({}); | |||
expect(currentUser).toEqual(mockCurrentUser()); | |||
expect(mapDispatchToProps).toEqual(expect.objectContaining({ fetchBranchStatus })); | |||
const result = await fetchIssues({ foo: 'bar' }); | |||
expect(searchIssues).toBeCalledWith( | |||
expect.objectContaining({ foo: 'bar', additionalFields: '_all' }) | |||
); | |||
expect(result).toEqual({ issues: ['parsedIssue'], bar: 'baz' }); | |||
}); | |||
}); |
@@ -44,7 +44,7 @@ interface State { | |||
qualityGates: T.QualityGate[]; | |||
} | |||
class App extends React.PureComponent<WithRouterProps, State> { | |||
class App extends React.PureComponent<Pick<WithRouterProps, 'params' | 'router'>, State> { | |||
mounted = false; | |||
state: State = { canCreate: false, loading: true, qualityGates: [] }; | |||
@@ -34,27 +34,23 @@ interface State { | |||
name: string; | |||
} | |||
class CreateQualityGateForm extends React.PureComponent<Props, State> { | |||
export class CreateQualityGateForm extends React.PureComponent<Props, State> { | |||
state: State = { name: '' }; | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleCreate = () => { | |||
handleCreate = async () => { | |||
const { name } = this.state; | |||
if (!name) { | |||
return undefined; | |||
} | |||
if (name) { | |||
const qualityGate = await createQualityGate({ name }); | |||
await this.props.onCreate(); | |||
return createQualityGate({ name }) | |||
.then(qualityGate => { | |||
return this.props.onCreate().then(() => qualityGate); | |||
}) | |||
.then(qualityGate => { | |||
this.props.router.push(getQualityGateUrl(String(qualityGate.id))); | |||
}); | |||
this.props.router.push(getQualityGateUrl(String(qualityGate.id))); | |||
} | |||
}; | |||
render() { |
@@ -0,0 +1,92 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { | |||
addSideBarClass, | |||
addWhitePageClass, | |||
removeSideBarClass, | |||
removeWhitePageClass | |||
} from 'sonar-ui-common/helpers/pages'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper'; | |||
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | |||
import { mockRouter } from '../../../../helpers/testMocks'; | |||
import App from '../App'; | |||
jest.mock('sonar-ui-common/helpers/pages', () => ({ | |||
addSideBarClass: jest.fn(), | |||
addWhitePageClass: jest.fn(), | |||
removeSideBarClass: jest.fn(), | |||
removeWhitePageClass: jest.fn() | |||
})); | |||
jest.mock('../../../../api/quality-gates', () => { | |||
const { mockQualityGate } = jest.requireActual('../../../../helpers/mocks/quality-gates'); | |||
return { | |||
fetchQualityGates: jest.fn().mockResolvedValue({ | |||
actions: { create: true }, | |||
qualitygates: [ | |||
mockQualityGate(), | |||
mockQualityGate({ id: '2', name: 'qualitygate 2', isDefault: true }) | |||
] | |||
}) | |||
}; | |||
}); | |||
it('should render correctly', async () => { | |||
const wrapper = shallowRender(); | |||
const replace = jest.fn(() => wrapper.setProps({ params: { id: '2' } })); | |||
wrapper.setProps({ router: mockRouter({ replace }) }); | |||
expect(wrapper).toMatchSnapshot('default'); | |||
expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot('ScreenPositionHelper'); | |||
await waitAndUpdate(wrapper); | |||
// No ID parameter passed, it should redirect to the default gate. | |||
expect(replace).toBeCalledWith({ pathname: '/quality_gates/show/2' }); | |||
expect(wrapper).toMatchSnapshot('default gate'); | |||
// Pass an ID, show a specific gate. | |||
wrapper.setProps({ params: { id: '1' } }); | |||
expect(wrapper).toMatchSnapshot('specific gate'); | |||
expect(addSideBarClass).toBeCalled(); | |||
expect(addWhitePageClass).toBeCalled(); | |||
wrapper.unmount(); | |||
expect(removeSideBarClass).toBeCalled(); | |||
expect(removeWhitePageClass).toBeCalled(); | |||
}); | |||
it('should handle set default correctly', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().qualityGates?.find(gate => gate.isDefault)?.id).toBe('2'); | |||
wrapper.instance().handleSetDefault(mockQualityGate({ id: '1' })); | |||
expect(wrapper.state().qualityGates?.find(gate => gate.isDefault)?.id).toBe('1'); | |||
}); | |||
function shallowRender(props: Partial<App['props']> = {}) { | |||
return shallow<App>(<App params={{}} router={mockRouter()} {...props} />); | |||
} |
@@ -17,12 +17,56 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import CreateQualityGateForm from '../CreateQualityGateForm'; | |||
import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal'; | |||
import { change, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { createQualityGate } from '../../../../api/quality-gates'; | |||
import { mockRouter } from '../../../../helpers/testMocks'; | |||
import { getQualityGateUrl } from '../../../../helpers/urls'; | |||
import { CreateQualityGateForm } from '../CreateQualityGateForm'; | |||
jest.mock('../../../../api/quality-gates', () => ({ | |||
createQualityGate: jest.fn().mockResolvedValue({ id: '1', name: 'newValue' }) | |||
})); | |||
it('should render correctly', () => { | |||
expect( | |||
shallow(<CreateQualityGateForm onClose={jest.fn()} onCreate={jest.fn()} />) | |||
).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should correctly handle create', async () => { | |||
const onCreate = jest.fn().mockResolvedValue(undefined); | |||
const push = jest.fn(); | |||
const wrapper = shallowRender({ onCreate, router: mockRouter({ push }) }); | |||
wrapper | |||
.find(ConfirmModal) | |||
.props() | |||
.onConfirm(); | |||
expect(createQualityGate).not.toHaveBeenCalled(); | |||
change(wrapper.find('#quality-gate-form-name'), 'newValue'); | |||
expect(wrapper.state().name).toBe('newValue'); | |||
wrapper | |||
.find(ConfirmModal) | |||
.props() | |||
.onConfirm(); | |||
expect(createQualityGate).toHaveBeenCalledWith({ name: 'newValue' }); | |||
await waitAndUpdate(wrapper); | |||
expect(onCreate).toHaveBeenCalled(); | |||
expect(push).toHaveBeenCalledWith(getQualityGateUrl('1')); | |||
}); | |||
function shallowRender(props: Partial<CreateQualityGateForm['props']> = {}) { | |||
return shallow<CreateQualityGateForm>( | |||
<CreateQualityGateForm | |||
onClose={jest.fn()} | |||
onCreate={jest.fn()} | |||
router={mockRouter()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,144 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: ScreenPositionHelper 1`] = ` | |||
<div | |||
className="layout-page-side-outer" | |||
> | |||
<div | |||
className="layout-page-side" | |||
style={ | |||
Object { | |||
"top": 0, | |||
} | |||
} | |||
> | |||
<div | |||
className="layout-page-side-inner" | |||
> | |||
<div | |||
className="layout-page-filters" | |||
> | |||
<ListHeader | |||
canCreate={false} | |||
refreshQualityGates={[Function]} | |||
/> | |||
<DeferredSpinner | |||
loading={true} | |||
> | |||
<List | |||
qualityGates={Array []} | |||
/> | |||
</DeferredSpinner> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defaultTitle="quality_gates.page" | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
titleTemplate="%s - quality_gates.page" | |||
/> | |||
<div | |||
className="layout-page" | |||
id="quality-gates-page" | |||
> | |||
<Suggestions | |||
suggestions="quality_gates" | |||
/> | |||
<ScreenPositionHelper | |||
className="layout-page-side-outer" | |||
> | |||
<Component /> | |||
</ScreenPositionHelper> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: default gate 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defaultTitle="quality_gates.page" | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
titleTemplate="%s - quality_gates.page" | |||
/> | |||
<div | |||
className="layout-page" | |||
id="quality-gates-page" | |||
> | |||
<Suggestions | |||
suggestions="quality_gates" | |||
/> | |||
<ScreenPositionHelper | |||
className="layout-page-side-outer" | |||
> | |||
<Component /> | |||
</ScreenPositionHelper> | |||
<Connect(Details) | |||
id="2" | |||
onSetDefault={[Function]} | |||
qualityGates={ | |||
Array [ | |||
Object { | |||
"id": "1", | |||
"name": "qualitygate", | |||
}, | |||
Object { | |||
"id": "2", | |||
"isDefault": true, | |||
"name": "qualitygate 2", | |||
}, | |||
] | |||
} | |||
refreshQualityGates={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: specific gate 1`] = ` | |||
<Fragment> | |||
<Helmet | |||
defaultTitle="quality_gates.page" | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
titleTemplate="%s - quality_gates.page" | |||
/> | |||
<div | |||
className="layout-page" | |||
id="quality-gates-page" | |||
> | |||
<Suggestions | |||
suggestions="quality_gates" | |||
/> | |||
<ScreenPositionHelper | |||
className="layout-page-side-outer" | |||
> | |||
<Component /> | |||
</ScreenPositionHelper> | |||
<Connect(Details) | |||
id="1" | |||
onSetDefault={[Function]} | |||
qualityGates={ | |||
Array [ | |||
Object { | |||
"id": "1", | |||
"name": "qualitygate", | |||
}, | |||
Object { | |||
"id": "2", | |||
"isDefault": true, | |||
"name": "qualitygate 2", | |||
}, | |||
] | |||
} | |||
refreshQualityGates={[Function]} | |||
/> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -1,8 +1,37 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<CreateQualityGateForm | |||
<ConfirmModal | |||
confirmButtonText="save" | |||
confirmDisable={true} | |||
header="quality_gates.create" | |||
onClose={[MockFunction]} | |||
onCreate={[MockFunction]} | |||
/> | |||
onConfirm={[Function]} | |||
size="small" | |||
> | |||
<div | |||
className="modal-field" | |||
> | |||
<label | |||
htmlFor="quality-gate-form-name" | |||
> | |||
name | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<input | |||
autoFocus={true} | |||
id="quality-gate-form-name" | |||
maxLength={100} | |||
onChange={[Function]} | |||
required={true} | |||
size={50} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
</ConfirmModal> | |||
`; |
@@ -0,0 +1,46 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import '../AppContainer'; | |||
jest.mock('react-redux', () => ({ | |||
connect: jest.fn(() => (a: any) => a) | |||
})); | |||
jest.mock('../../../../store/rootReducer', () => { | |||
return { | |||
getLanguages: jest.fn(() => [ | |||
{ key: 'css', name: 'CSS' }, | |||
{ key: 'js', name: 'JS' } | |||
]) | |||
}; | |||
}); | |||
describe('redux', () => { | |||
it('should correctly map state and dispatch props', () => { | |||
const [mapStateToProps] = (connect as jest.Mock).mock.calls[0]; | |||
const { languages } = mapStateToProps({}); | |||
expect(languages).toEqual([ | |||
{ key: 'css', name: 'CSS' }, | |||
{ key: 'js', name: 'JS' } | |||
]); | |||
}); | |||
}); |