github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/ServiceLane/DotsMenu.test.tsx (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  import { act, render } from '@testing-library/react';
    17  import { DotsMenu, DotsMenuProps } from './DotsMenu';
    18  import { elementQuerySelectorSafe } from '../../../setupTests';
    19  
    20  describe('DotsMenu Rendering', () => {
    21      const getNode = (overrides: DotsMenuProps) => <DotsMenu {...overrides} />;
    22      const getWrapper = (overrides: DotsMenuProps) => render(getNode(overrides));
    23  
    24      const mySpy = jest.fn();
    25  
    26      type TestData = {
    27          name: string;
    28          input: DotsMenuProps;
    29          expectedNumItems: number;
    30      };
    31  
    32      const data: TestData[] = [
    33          {
    34              name: 'renders empty list',
    35              input: { buttons: [] },
    36              expectedNumItems: 0,
    37          },
    38          {
    39              name: 'renders one button',
    40              input: {
    41                  buttons: [
    42                      {
    43                          label: 'test label',
    44                          onClick: mySpy,
    45                      },
    46                  ],
    47              },
    48              expectedNumItems: 1,
    49          },
    50          {
    51              name: 'renders three button',
    52              input: {
    53                  buttons: [
    54                      {
    55                          label: 'test label A',
    56                          onClick: mySpy,
    57                      },
    58                      {
    59                          label: 'test label B',
    60                          onClick: mySpy,
    61                      },
    62                      {
    63                          label: 'test label C',
    64                          onClick: mySpy,
    65                      },
    66                  ],
    67              },
    68              expectedNumItems: 3,
    69          },
    70      ];
    71  
    72      describe.each(data)('DotsMenu Test', (testcase) => {
    73          it(testcase.name, () => {
    74              mySpy.mockReset();
    75              expect(mySpy).toHaveBeenCalledTimes(0);
    76  
    77              const { container } = getWrapper(testcase.input);
    78  
    79              expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1);
    80              const result = elementQuerySelectorSafe(container, '.dots-menu-hidden .mdc-button--unelevated');
    81              act(() => {
    82                  result.click();
    83              });
    84              expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(0);
    85              expect(document.querySelectorAll('li.item').length).toEqual(testcase.expectedNumItems);
    86  
    87              if (testcase.expectedNumItems > 0) {
    88                  expect(mySpy).toHaveBeenCalledTimes(0);
    89                  const result = elementQuerySelectorSafe(container, 'li .mdc-button--unelevated');
    90                  act(() => {
    91                      result.click();
    92                  });
    93                  expect(mySpy).toHaveBeenCalledTimes(1);
    94              }
    95          });
    96      });
    97  });
    98  
    99  const addSibling = (wrappee: JSX.Element) => (
   100      <>
   101          {wrappee}
   102          <div id="sibling-outside"></div>
   103      </>
   104  );
   105  
   106  describe('DotsMenu Close', () => {
   107      const getNode = (overrides: DotsMenuProps) => <DotsMenu {...overrides} />;
   108      const getWrapper = (overrides: DotsMenuProps) => render(addSibling(getNode(overrides)));
   109  
   110      type propMod = (props: Partial<DotsMenuProps>) => void;
   111      const fixtureProps = (...mods: propMod[]) => {
   112          const props = {
   113              buttons: [
   114                  {
   115                      label: 'test label A',
   116                      onClick: () => {},
   117                  },
   118                  {
   119                      label: 'test label B',
   120                      onClick: () => {},
   121                  },
   122                  {
   123                      label: 'test label C',
   124                      onClick: () => {},
   125                  },
   126              ],
   127          };
   128          mods.forEach((m) => m(props));
   129          return props;
   130      };
   131  
   132      type TestData = {
   133          name: string;
   134          props: DotsMenuProps;
   135          sendKey: string;
   136          clickViaEvent: string[];
   137          expectClosed: boolean;
   138      };
   139      const fixtureTestData = () => ({
   140          props: fixtureProps(),
   141          sendKey: '',
   142          clickViaEvent: [],
   143          expectClosed: false,
   144      });
   145  
   146      const data: TestData[] = [
   147          {
   148              name: 'visible if not clicking',
   149              ...fixtureTestData(),
   150              expectClosed: false,
   151          },
   152          {
   153              name: 'close if escape is pressed',
   154              ...fixtureTestData(),
   155              sendKey: 'Escape',
   156              expectClosed: true,
   157          },
   158          {
   159              name: 'visible if any other button is pressed',
   160              ...fixtureTestData(),
   161              sendKey: 'a',
   162              expectClosed: false,
   163          },
   164          {
   165              name: 'close if sibling is clicked',
   166              ...fixtureTestData(),
   167              clickViaEvent: ['#sibling-outside'],
   168              expectClosed: true,
   169          },
   170          {
   171              name: 'visible if elements inside is clicked',
   172              ...fixtureTestData(),
   173              clickViaEvent: ['.item', '.dots-menu-open'],
   174              expectClosed: false,
   175          },
   176      ];
   177  
   178      describe.each(data)('DotsMenu Closing', (testcase) => {
   179          it(testcase.name, () => {
   180              const { container } = getWrapper(testcase.props);
   181  
   182              // open menu
   183              expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1);
   184              const result = elementQuerySelectorSafe(container, '.dots-menu-hidden .mdc-button--unelevated');
   185              act(() => {
   186                  result.click();
   187              });
   188              expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(0);
   189  
   190              testcase.clickViaEvent.forEach((selector) => {
   191                  act(() => {
   192                      const elem = elementQuerySelectorSafe(container, selector);
   193                      ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup'].forEach((mouseEventType) => {
   194                          elem.dispatchEvent(
   195                              new MouseEvent(mouseEventType, {
   196                                  view: window,
   197                                  bubbles: true,
   198                                  buttons: 1,
   199                              })
   200                          );
   201                      });
   202                  });
   203              });
   204  
   205              if (testcase.sendKey !== '') {
   206                  ['keydown', 'keypress', 'keyup'].forEach((keyEventType) =>
   207                      act(() =>
   208                          document.dispatchEvent(
   209                              new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true })
   210                          )
   211                      )
   212                  );
   213              }
   214  
   215              if (testcase.expectClosed) {
   216                  expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1);
   217              } else {
   218                  expect(document.querySelectorAll('.dots-menu-open').length).toEqual(1);
   219              }
   220          });
   221      });
   222  });