]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19249 Introduce new variant for the ListFooter
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 12 May 2023 10:11:16 +0000 (12:11 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 22 May 2023 20:02:56 +0000 (20:02 +0000)
server/sonar-web/src/main/js/components/controls/ListFooter.tsx
server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ListFooter-test.tsx.snap [deleted file]

index fbbcb2af4645b4bb003f321639a91060b8233c4c..cd0f7f985d67bf4879c4a9ff61dfaf2f3655a7db 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import styled from '@emotion/styled';
 import classNames from 'classnames';
+import { ButtonSecondary, themeColor, withTheme } from 'design-system';
 import * as React from 'react';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { formatMeasure } from '../../helpers/measures';
+import { MetricType } from '../../types/metrics';
 import DeferredSpinner from '../ui/DeferredSpinner';
 import { Button } from './buttons';
 
@@ -35,6 +38,7 @@ export interface ListFooterProps {
   reload?: () => void;
   ready?: boolean;
   total?: number;
+  useMIUIButtons?: boolean;
 }
 
 export default function ListFooter(props: ListFooterProps) {
@@ -48,6 +52,7 @@ export default function ListFooter(props: ListFooterProps) {
     total,
     pageSize,
     ready = true,
+    useMIUIButtons = false,
   } = props;
 
   const rootNode = React.useRef<HTMLDivElement>(null);
@@ -71,32 +76,38 @@ export default function ListFooter(props: ListFooterProps) {
 
   let button;
   if (needReload && props.reload) {
-    button = (
-      <Button className="spacer-left" data-test="reload" disabled={loading} onClick={props.reload}>
-        {translate('reload')}
-      </Button>
+    button = React.createElement(
+      useMIUIButtons ? ButtonSecondary : Button,
+      {
+        'data-test': 'reload',
+        className: classNames('sw-ml-2', { 'sw-body-sm': useMIUIButtons }),
+        disabled: loading,
+        onClick: props.reload,
+      } as Button['props'],
+      translate('reload')
     );
   } else if (hasMore && props.loadMore) {
-    button = (
-      <Button
-        aria-label={loadMoreAriaLabel}
-        className="spacer-left"
-        disabled={loading}
-        data-test="show-more"
-        onClick={onLoadMore}
-      >
-        {translate('show_more')}
-      </Button>
+    button = React.createElement(
+      useMIUIButtons ? ButtonSecondary : Button,
+      {
+        'aria-label': loadMoreAriaLabel,
+        'data-test': 'show-more',
+        className: classNames('sw-ml-2', { 'sw-body-sm': useMIUIButtons }),
+        disabled: loading,
+        onClick: onLoadMore,
+      } as Button['props'],
+      translate('show_more')
     );
   }
 
   return (
-    <div
+    <StyledDiv
       tabIndex={-1}
       ref={rootNode}
       className={classNames(
-        'list-footer spacer-top note text-center',
-        { 'new-loading': !ready },
+        'list-footer', // .list-footer is only used by Selenium tests; we should find a way to remove it.
+        'sw-body-sm sw-mt-4 sw-flex sw-items-center sw-justify-center',
+        { 'sw-opacity-50 sw-duration-500 sw-ease-in-out': !ready },
         className
       )}
     >
@@ -104,13 +115,17 @@ export default function ListFooter(props: ListFooterProps) {
         {total !== undefined
           ? translateWithParameters(
               'x_of_y_shown',
-              formatMeasure(count, 'INT', null),
-              formatMeasure(total, 'INT', null)
+              formatMeasure(count, MetricType.Integer, null),
+              formatMeasure(total, MetricType.Integer, null)
             )
-          : translateWithParameters('x_show', formatMeasure(count, 'INT', null))}
+          : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer, null))}
       </span>
       {button}
-      {<DeferredSpinner loading={loading} className="text-bottom spacer-left position-absolute" />}
-    </div>
+      {<DeferredSpinner loading={loading} className="sw-ml-2" />}
+    </StyledDiv>
   );
 }
+
+const StyledDiv = withTheme(styled.div`
+  color: ${themeColor('pageContentLight')};
+`);
index 0986463f7ab6c9bac51c06a048309dbc28fff166..08e3d3871079e7bfc155852a4c8999acaed0eee3 100644 (file)
  * 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import * as React from 'react';
-import { click } from '../../../helpers/testUtils';
-import { Button } from '../buttons';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
 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'
-  );
-});
+describe('ListFooter', () => {
+  describe('rendering', () => {
+    it('should render correctly when loading', async () => {
+      renderListFooter({ loading: true });
+      expect(await screen.findByText('loading')).toBeInTheDocument();
+    });
 
-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 not render if there are neither loadmore nor reload props', () => {
+      renderListFooter({ loadMore: undefined, reload: undefined });
+      expect(screen.queryByRole('button')).not.toBeInTheDocument();
+    });
 
-it('should properly call loadMore', () => {
-  const loadMore = jest.fn();
-  const wrapper = shallowRender({ loadMore });
-  click(wrapper.find(Button));
-  expect(loadMore).toHaveBeenCalled();
-});
+    it.each([
+      [undefined, 60, 30, true],
+      [undefined, 45, 30, false],
+      [undefined, 60, undefined, false],
+      [60, 60, 30, false],
+    ])(
+      'should handle showing load more button based on total, count and pageSize',
+      (total, count, pageSize, expected) => {
+        renderListFooter({ total, count, pageSize });
+
+        /* eslint-disable jest/no-conditional-in-test */
+        /* eslint-disable jest/no-conditional-expect */
+        if (expected) {
+          expect(screen.getByRole('button')).toBeInTheDocument();
+        } else {
+          expect(screen.queryByRole('button')).not.toBeInTheDocument();
+        }
+        /* eslint-enable jest/no-conditional-in-test */
+        /* eslint-enable jest/no-conditional-expect */
+      }
+    );
+  });
+
+  it('should properly call load more callback', async () => {
+    const user = userEvent.setup();
+    const loadMore = jest.fn();
+    renderListFooter({ loadMore });
 
-it('should properly call reload', () => {
-  const reload = jest.fn();
-  const wrapper = shallowRender({ needReload: true, reload });
-  click(wrapper.find(Button));
-  expect(reload).toHaveBeenCalled();
+    await user.click(screen.getByRole('button'));
+    expect(loadMore).toHaveBeenCalled();
+  });
+
+  it('should properly call reload callback', async () => {
+    const user = userEvent.setup();
+    const reload = jest.fn();
+    renderListFooter({ needReload: true, reload });
+
+    await user.click(screen.getByRole('button'));
+    expect(reload).toHaveBeenCalled();
+  });
+
+  function renderListFooter(props: Partial<ListFooterProps> = {}) {
+    return renderComponent(<ListFooter count={3} loadMore={jest.fn()} total={5} {...props} />);
+  }
 });
 
-function shallowRender(props: Partial<ListFooterProps> = {}) {
-  return shallow<ListFooterProps>(
-    <ListFooter count={3} loadMore={jest.fn()} total={5} {...props} />
-  );
-}
+// Once the MIUI buttons become the norm, we can use only the above test "suite" and drop this one.
+describe('ListFooter using MIUI buttons', () => {
+  describe('rendering', () => {
+    it('should render correctly when loading', async () => {
+      renderListFooter({ loading: true });
+      expect(await screen.findByText('loading')).toBeInTheDocument();
+    });
+
+    it('should not render if there are neither loadmore nor reload props', () => {
+      renderListFooter({ loadMore: undefined, reload: undefined });
+      expect(screen.queryByRole('button')).not.toBeInTheDocument();
+    });
+
+    it.each([
+      [undefined, 60, 30, true],
+      [undefined, 45, 30, false],
+      [undefined, 60, undefined, false],
+      [60, 60, 30, false],
+    ])(
+      'should handle showing load more button based on total, count and pageSize',
+      (total, count, pageSize, expected) => {
+        renderListFooter({ total, count, pageSize });
+
+        /* eslint-disable jest/no-conditional-in-test */
+        /* eslint-disable jest/no-conditional-expect */
+        if (expected) {
+          expect(screen.getByRole('button')).toBeInTheDocument();
+        } else {
+          expect(screen.queryByRole('button')).not.toBeInTheDocument();
+        }
+        /* eslint-enable jest/no-conditional-in-test */
+        /* eslint-enable jest/no-conditional-expect */
+      }
+    );
+  });
+
+  it('should properly call load more callback', async () => {
+    const user = userEvent.setup();
+    const loadMore = jest.fn();
+    renderListFooter({ loadMore });
+
+    await user.click(screen.getByRole('button'));
+    expect(loadMore).toHaveBeenCalled();
+  });
+
+  it('should properly call reload callback', async () => {
+    const user = userEvent.setup();
+    const reload = jest.fn();
+    renderListFooter({ needReload: true, reload });
+
+    await user.click(screen.getByRole('button'));
+    expect(reload).toHaveBeenCalled();
+  });
+
+  function renderListFooter(props: Partial<ListFooterProps> = {}) {
+    return renderComponent(
+      <ListFooter count={3} loadMore={jest.fn()} total={5} useMIUIButtons={true} {...props} />
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ListFooter-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ListFooter-test.tsx.snap
deleted file mode 100644 (file)
index c565c08..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: default 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_of_y_shown.3.5
-  </span>
-  <Button
-    className="spacer-left"
-    data-test="show-more"
-    disabled={false}
-    onClick={[Function]}
-  >
-    show_more
-  </Button>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;
-
-exports[`should render correctly: empty if everything is loaded 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_of_y_shown.5.5
-  </span>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;
-
-exports[`should render correctly: empty if no loadMore nor reload props 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_of_y_shown.3.5
-  </span>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;
-
-exports[`should render correctly: force show load more button if count % pageSize is 0, and total is undefined 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_show.60
-  </span>
-  <Button
-    className="spacer-left"
-    data-test="show-more"
-    disabled={false}
-    onClick={[Function]}
-  >
-    show_more
-  </Button>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;
-
-exports[`should render correctly: loading 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={true}
-    aria-live="polite"
-  >
-    x_of_y_shown.3.5
-  </span>
-  <Button
-    className="spacer-left"
-    data-test="show-more"
-    disabled={true}
-    onClick={[Function]}
-  >
-    show_more
-  </Button>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={true}
-  />
-</div>
-`;
-
-exports[`should render correctly: reload 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_of_y_shown.3.5
-  </span>
-  <Button
-    className="spacer-left"
-    data-test="reload"
-    disabled={false}
-    onClick={[MockFunction]}
-  >
-    reload
-  </Button>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;
-
-exports[`should render correctly: reload, loading 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={true}
-    aria-live="polite"
-  >
-    x_of_y_shown.3.5
-  </span>
-  <Button
-    className="spacer-left"
-    data-test="reload"
-    disabled={true}
-    onClick={[MockFunction]}
-  >
-    reload
-  </Button>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={true}
-  />
-</div>
-`;
-
-exports[`should render correctly: total undefined 1`] = `
-<div
-  className="list-footer spacer-top note text-center"
-  tabIndex={-1}
->
-  <span
-    aria-busy={false}
-    aria-live="polite"
-  >
-    x_show.3
-  </span>
-  <DeferredSpinner
-    className="text-bottom spacer-left position-absolute"
-    loading={false}
-  />
-</div>
-`;