Co-authored-by: Guillaume Peoch <guillaume.peoch@sonarsource.com>tags/8.9.3.48735
@@ -21,7 +21,6 @@ import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; | |||
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon'; | |||
@@ -30,6 +29,7 @@ import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { GitlabProject } from '../../../types/alm-integration'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
@@ -159,6 +159,7 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection | |||
count={projects.length} | |||
loadMore={props.onLoadMore} | |||
loading={loadingMore} | |||
pageSize={projectsPaging.pageSize} | |||
total={projectsPaging.total} | |||
/> | |||
</div> |
@@ -20,8 +20,8 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; | |||
import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import { mockGitlabProject } from '../../../../helpers/mocks/alm-integrations'; | |||
import GitlabProjectSelectionForm, { | |||
GitlabProjectSelectionFormProps |
@@ -137,6 +137,7 @@ exports[`should render correctly: importing 1`] = ` | |||
count={2} | |||
loadMore={[MockFunction]} | |||
loading={false} | |||
pageSize={30} | |||
total={2} | |||
/> | |||
</div> | |||
@@ -194,6 +195,7 @@ exports[`should render correctly: no projects when searching 1`] = ` | |||
count={0} | |||
loadMore={[MockFunction]} | |||
loading={false} | |||
pageSize={30} | |||
total={0} | |||
/> | |||
</div> | |||
@@ -336,6 +338,7 @@ exports[`should render correctly: projects 1`] = ` | |||
count={2} | |||
loadMore={[MockFunction]} | |||
loading={false} | |||
pageSize={30} | |||
total={2} | |||
/> | |||
</div> |
@@ -0,0 +1,82 @@ | |||
/* | |||
* 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 classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||
export interface ListFooterProps { | |||
count: number; | |||
className?: string; | |||
loading?: boolean; | |||
loadMore?: () => void; | |||
needReload?: boolean; | |||
pageSize?: number; | |||
reload?: () => void; | |||
ready?: boolean; | |||
total?: number; | |||
} | |||
export default function ListFooter(props: ListFooterProps) { | |||
const { className, count, loading, needReload, total, pageSize, ready = true } = props; | |||
let hasMore = false; | |||
if (total !== undefined) { | |||
hasMore = total > count; | |||
} else if (pageSize !== undefined) { | |||
hasMore = count % pageSize === 0; | |||
} | |||
let button; | |||
if (needReload && props.reload) { | |||
button = ( | |||
<Button className="spacer-left" data-test="reload" disabled={loading} onClick={props.reload}> | |||
{translate('reload')} | |||
</Button> | |||
); | |||
} else if (hasMore && props.loadMore) { | |||
button = ( | |||
<Button | |||
className="spacer-left" | |||
disabled={loading} | |||
data-test="show-more" | |||
onClick={props.loadMore}> | |||
{translate('show_more')} | |||
</Button> | |||
); | |||
} | |||
return ( | |||
<footer | |||
className={classNames('spacer-top note text-center', { 'new-loading': !ready }, className)}> | |||
{total !== undefined | |||
? translateWithParameters( | |||
'x_of_y_shown', | |||
formatMeasure(count, 'INT', null), | |||
formatMeasure(total, 'INT', null) | |||
) | |||
: translateWithParameters('x_show', formatMeasure(count, 'INT', null))} | |||
{button} | |||
{loading && <DeferredSpinner className="text-bottom spacer-left position-absolute" />} | |||
</footer> | |||
); | |||
} |
@@ -0,0 +1,74 @@ | |||
/* | |||
* 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 { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import ListFooter, { ListFooterProps } from '../ListFooter'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ needReload: true, reload: jest.fn() })).toMatchSnapshot('reload'); | |||
expect(shallowRender({ loading: true, needReload: true, reload: jest.fn() })).toMatchSnapshot( | |||
'reload, loading' | |||
); | |||
expect(shallowRender({ loadMore: undefined })).toMatchSnapshot( | |||
'empty if no loadMore nor reload props' | |||
); | |||
expect(shallowRender({ count: 5 })).toMatchSnapshot('empty if everything is loaded'); | |||
expect(shallowRender({ total: undefined })).toMatchSnapshot('total undefined'); | |||
expect(shallowRender({ total: undefined, count: 60, pageSize: 30 })).toMatchSnapshot( | |||
'force show load more button if count % pageSize is 0, and total is undefined' | |||
); | |||
}); | |||
it.each([ | |||
[undefined, 60, 30, true], | |||
[undefined, 45, 30, false], | |||
[undefined, 60, undefined, false], | |||
[60, 60, 30, false] | |||
])( | |||
'handle showing load more button based on total, count and pageSize', | |||
(total, count, pageSize, expected) => { | |||
const wrapper = shallowRender({ total, count, pageSize }); | |||
expect(wrapper.find(Button).exists()).toBe(expected); | |||
} | |||
); | |||
it('should properly call loadMore', () => { | |||
const loadMore = jest.fn(); | |||
const wrapper = shallowRender({ loadMore }); | |||
click(wrapper.find(Button)); | |||
expect(loadMore).toBeCalled(); | |||
}); | |||
it('should properly call reload', () => { | |||
const reload = jest.fn(); | |||
const wrapper = shallowRender({ needReload: true, reload }); | |||
click(wrapper.find(Button)); | |||
expect(reload).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<ListFooterProps> = {}) { | |||
return shallow<ListFooterProps>( | |||
<ListFooter count={3} loadMore={jest.fn()} total={5} {...props} /> | |||
); | |||
} |
@@ -0,0 +1,108 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.3.5 | |||
<Button | |||
className="spacer-left" | |||
data-test="show-more" | |||
onClick={[MockFunction]} | |||
> | |||
show_more | |||
</Button> | |||
</footer> | |||
`; | |||
exports[`should render correctly: empty if everything is loaded 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.5.5 | |||
</footer> | |||
`; | |||
exports[`should render correctly: empty if no loadMore nor reload props 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.3.5 | |||
</footer> | |||
`; | |||
exports[`should render correctly: force show load more button if count % pageSize is 0, and total is undefined 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_show.60 | |||
<Button | |||
className="spacer-left" | |||
data-test="show-more" | |||
onClick={[MockFunction]} | |||
> | |||
show_more | |||
</Button> | |||
</footer> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.3.5 | |||
<Button | |||
className="spacer-left" | |||
data-test="show-more" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
show_more | |||
</Button> | |||
<DeferredSpinner | |||
className="text-bottom spacer-left position-absolute" | |||
/> | |||
</footer> | |||
`; | |||
exports[`should render correctly: reload 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.3.5 | |||
<Button | |||
className="spacer-left" | |||
data-test="reload" | |||
onClick={[MockFunction]} | |||
> | |||
reload | |||
</Button> | |||
</footer> | |||
`; | |||
exports[`should render correctly: reload, loading 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_of_y_shown.3.5 | |||
<Button | |||
className="spacer-left" | |||
data-test="reload" | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
reload | |||
</Button> | |||
<DeferredSpinner | |||
className="text-bottom spacer-left position-absolute" | |||
/> | |||
</footer> | |||
`; | |||
exports[`should render correctly: total undefined 1`] = ` | |||
<footer | |||
className="spacer-top note text-center" | |||
> | |||
x_show.3 | |||
</footer> | |||
`; |