github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/dialog/ConfirmationDialog.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  
    17  import React from 'react';
    18  
    19  import { ConfirmationDialog, ConfirmationDialogProps, PlainDialog, PlainDialogProps } from './ConfirmationDialog';
    20  import { act, getByTestId, render } from '@testing-library/react';
    21  import { MemoryRouter } from 'react-router-dom';
    22  
    23  const getNodeConfirm = (overrides?: {}): JSX.Element | any => {
    24      const props: any = {
    25          children: null,
    26          open: true,
    27          ...overrides,
    28      };
    29      // given
    30      return (
    31          <div>
    32              <MemoryRouter>
    33                  <ConfirmationDialog {...props} />
    34              </MemoryRouter>
    35          </div>
    36      );
    37  };
    38  
    39  const getNodePlain = (overrides?: {}): JSX.Element | any => {
    40      const props: any = {
    41          open: true,
    42          center: false,
    43          ...overrides,
    44      };
    45      // given
    46      return (
    47          <div>
    48              <MemoryRouter>
    49                  <PlainDialog {...props} />
    50              </MemoryRouter>
    51          </div>
    52      );
    53  };
    54  
    55  const addSibling = (wrappee: JSX.Element) => (
    56      <>
    57          {wrappee}
    58          <div data-testid="sibling-outside"></div>
    59      </>
    60  );
    61  
    62  const getWrapperConfirm = (overrides?: Partial<ConfirmationDialogProps>) =>
    63      render(addSibling(getNodeConfirm(overrides)));
    64  const getWrapperPlain = (overrides?: Partial<PlainDialogProps>) => render(addSibling(getNodePlain(overrides)));
    65  
    66  const testIdRootRefParent = 'test-root-ref-parent';
    67  
    68  describe('ConfirmDialog', () => {
    69      type TestData = {
    70          name: string;
    71          props: Partial<ConfirmationDialogProps>;
    72          // if we click these elements
    73          clickViaEvent: string[];
    74          // click button
    75          clickButton: string[];
    76          sendKey: string;
    77          // then we expect dialog to be
    78          expectClose: boolean;
    79          expectRootRefClasses: string[];
    80      };
    81  
    82      type propMod = (props: Partial<ConfirmationDialogProps>) => void;
    83      const fixtureProps = (...mods: propMod[]) => {
    84          const props = {
    85              testIdRootRefParent: testIdRootRefParent,
    86              headerLabel: 'Header label',
    87              confirmLabel: 'Confirm label',
    88          };
    89          mods.forEach((m: propMod) => m(props));
    90          return props;
    91      };
    92      const fixtureTestData = () => ({
    93          props: fixtureProps(),
    94          clickViaEvent: [],
    95          clickButton: [],
    96          sendKey: '',
    97          expectClose: false,
    98          expectRootRefClasses: ['confirmation-dialog-container', 'confirmation-dialog-container-open'],
    99      });
   100  
   101      const data: TestData[] = [
   102          {
   103              name: 'visible if not clicked',
   104              ...fixtureTestData(),
   105          },
   106          {
   107              name: 'close if clicked outside',
   108              ...fixtureTestData(),
   109              clickViaEvent: ['sibling-outside'],
   110              expectClose: true,
   111          },
   112          {
   113              name: 'close if escape pressed',
   114              ...fixtureTestData(),
   115              clickViaEvent: ['sibling-outside'],
   116              sendKey: 'Escape',
   117              expectClose: true,
   118          },
   119          {
   120              name: 'close if cancel button pressed',
   121              ...fixtureTestData(),
   122              clickButton: ['test-confirm-button-cancel'],
   123              expectClose: true,
   124          },
   125          {
   126              name: 'do not close on random key press',
   127              ...fixtureTestData(),
   128              sendKey: 'a',
   129              expectClose: false,
   130          },
   131      ];
   132  
   133      const mouseClickEvents = ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup'];
   134  
   135      describe.each(data)('Closes confirm dialog on user interaction', (testcase) => {
   136          it(testcase.name, () => {
   137              let calledClose = false;
   138              const onClose = () => {
   139                  calledClose = true;
   140              };
   141              const { container } = getWrapperConfirm({
   142                  ...testcase.props,
   143                  onCancel: onClose,
   144                  children: (
   145                      <>
   146                          <div data-testid="test-content"></div>
   147                      </>
   148                  ),
   149              });
   150  
   151              testcase.clickViaEvent.forEach((id) => {
   152                  const elem = getByTestId(container, id);
   153                  mouseClickEvents.forEach((mouseEventType) => {
   154                      act(() =>
   155                          elem.dispatchEvent(
   156                              new MouseEvent(mouseEventType, {
   157                                  view: window,
   158                                  bubbles: true,
   159                                  // cancelable: true,
   160                                  buttons: 1,
   161                              })
   162                          )
   163                      );
   164                  });
   165              });
   166              testcase.clickButton.forEach((id) => {
   167                  act(() => getByTestId(container, id).click());
   168              });
   169  
   170              if (testcase.sendKey !== '') {
   171                  ['keydown', 'keypress', 'keyup'].forEach((keyEventType) =>
   172                      act(() =>
   173                          document.dispatchEvent(
   174                              new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true })
   175                          )
   176                      )
   177                  );
   178              }
   179  
   180              expect(calledClose).toBe(testcase.expectClose);
   181  
   182              if (!testcase.expectClose) {
   183                  const header = container.getElementsByClassName('confirmation-dialog-header');
   184                  expect(header).toHaveLength(1);
   185                  expect(header[0]).toHaveTextContent(testcase.props.headerLabel ?? '');
   186  
   187                  const confirm = getByTestId(container, 'test-confirm-button-confirm');
   188                  expect(confirm).toHaveTextContent(testcase.props.confirmLabel ?? '');
   189                  expect(getByTestId(container, 'test-confirm-button-cancel')).toBeVisible();
   190  
   191                  testcase.expectRootRefClasses.forEach((clas) =>
   192                      expect(getByTestId(container, testIdRootRefParent)).toHaveClass(clas)
   193                  );
   194              }
   195          });
   196      });
   197  });
   198  
   199  describe('PlainDialog Closing', () => {
   200      type TestData = {
   201          name: string;
   202          props: Partial<PlainDialogProps>;
   203          // if we click these elements
   204          clickViaEvent: string[];
   205          sendKey: string;
   206          // then we expect dialog to be
   207          expectClose: boolean;
   208          expectRootRefClasses: string[];
   209      };
   210  
   211      type propMod = (props: Partial<PlainDialogProps>) => void;
   212      const fixtureProps = (...mods: propMod[]) => {
   213          const props = {
   214              testIdRootRefParent: testIdRootRefParent,
   215              disableBackground: false,
   216              center: false,
   217              children: (
   218                  <>
   219                      <div data-testid="test-content-inside"></div>
   220                  </>
   221              ),
   222          };
   223          mods.forEach((m: propMod) => m(props));
   224          return props;
   225      };
   226      const fixtureTestData = () => ({
   227          props: fixtureProps(),
   228          clickViaEvent: [],
   229          sendKey: '',
   230          expectClose: false,
   231          expectRootRefClasses: ['plain-dialog-container'],
   232      });
   233  
   234      const data: TestData[] = [
   235          {
   236              name: 'visible if not clicked',
   237              ...fixtureTestData(),
   238          },
   239          {
   240              name: 'close if clicked outside',
   241              ...fixtureTestData(),
   242              clickViaEvent: ['sibling-outside'],
   243              expectClose: true,
   244          },
   245          {
   246              name: 'still visible if clicked on content',
   247              ...fixtureTestData(),
   248              clickViaEvent: ['content-inside'],
   249              expectClose: false,
   250          },
   251          {
   252              name: 'still visible if clicked on content while centered',
   253              ...fixtureTestData(),
   254              props: fixtureProps((props) => {
   255                  props.center = true;
   256              }),
   257              clickViaEvent: ['content-inside'],
   258              expectClose: false,
   259              expectRootRefClasses: ['confirmation-dialog-container'],
   260          },
   261          {
   262              name: 'still visible if clicked on content while centered with disabled background',
   263              ...fixtureTestData(),
   264              props: fixtureProps((props) => {
   265                  props.center = true;
   266                  props.disableBackground = true;
   267              }),
   268              clickViaEvent: ['content-inside'],
   269              expectClose: false,
   270              expectRootRefClasses: ['confirmation-dialog-container', 'confirmation-dialog-container-open'],
   271          },
   272          {
   273              name: 'close if clicked outside while centered',
   274              ...fixtureTestData(),
   275              props: fixtureProps((props) => {
   276                  props.center = true;
   277              }),
   278              clickViaEvent: ['sibling-outside'],
   279              expectClose: true,
   280          },
   281          {
   282              name: 'different dialog class if centered',
   283              ...fixtureTestData(),
   284              props: fixtureProps((props) => {
   285                  props.center = true;
   286              }),
   287              expectClose: false,
   288              expectRootRefClasses: ['confirmation-dialog-container'],
   289          },
   290          {
   291              name: 'close if escape pressed',
   292              ...fixtureTestData(),
   293              clickViaEvent: ['sibling-outside'],
   294              sendKey: 'Escape',
   295              expectClose: true,
   296          },
   297          {
   298              name: 'do not close on random key press',
   299              ...fixtureTestData(),
   300              sendKey: 'a',
   301              expectClose: false,
   302          },
   303      ];
   304  
   305      const mouseClickEvents = ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup'];
   306  
   307      describe.each(data)('Closes plain dialog on user interaction', (testcase) => {
   308          it(testcase.name, () => {
   309              let calledClose = false;
   310              const onClose = () => {
   311                  calledClose = true;
   312              };
   313              const { container } = getWrapperPlain({
   314                  ...testcase.props,
   315                  onClose: onClose,
   316                  children: (
   317                      <>
   318                          <div data-testid="content-inside"></div>
   319                      </>
   320                  ),
   321              });
   322  
   323              testcase.clickViaEvent.forEach((id) => {
   324                  const elem = getByTestId(container, id);
   325                  mouseClickEvents.forEach((mouseEventType) => {
   326                      elem.dispatchEvent(
   327                          new MouseEvent(mouseEventType, {
   328                              view: window,
   329                              bubbles: true,
   330                              buttons: 1,
   331                          })
   332                      );
   333                  });
   334              });
   335  
   336              if (testcase.sendKey !== '') {
   337                  ['keydown', 'keypress', 'keyup'].forEach((keyEventType) =>
   338                      document.dispatchEvent(new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true }))
   339                  );
   340              }
   341  
   342              expect(calledClose).toBe(testcase.expectClose);
   343  
   344              if (!testcase.expectClose) {
   345                  testcase.expectRootRefClasses.forEach((clas) =>
   346                      expect(getByTestId(container, testIdRootRefParent)).toHaveClass(clas)
   347                  );
   348              }
   349          });
   350      });
   351  });