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';
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';
count={projects.length}
loadMore={props.onLoadMore}
loading={loadingMore}
+ pageSize={projectsPaging.pageSize}
total={projectsPaging.total}
/>
</div>
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
count={2}
loadMore={[MockFunction]}
loading={false}
+ pageSize={30}
total={2}
/>
</div>
count={0}
loadMore={[MockFunction]}
loading={false}
+ pageSize={30}
total={0}
/>
</div>
count={2}
loadMore={[MockFunction]}
loading={false}
+ pageSize={30}
total={2}
/>
</div>
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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} />
+ );
+}
--- /dev/null
+// 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>
+`;