github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/SideBar/SideBar.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, renderHook } from '@testing-library/react';
    17  import { TopAppBar } from '../TopAppBar/TopAppBar';
    18  import { MemoryRouter } from 'react-router-dom';
    19  import { BatchAction, LockBehavior } from '../../../api/api';
    20  import {
    21      addAction,
    22      deleteAction,
    23      useActions,
    24      updateActions,
    25      deleteAllActions,
    26      appendAction,
    27      DisplayLock,
    28  } from '../../utils/store';
    29  import { ActionDetails, ActionTypes, getActionDetails, SideBar } from './SideBar';
    30  import { elementQuerySelectorSafe } from '../../../setupTests';
    31  
    32  describe('Show and Hide Sidebar', () => {
    33      interface dataT {
    34          name: string;
    35          expect: (container: HTMLElement) => HTMLElement | void;
    36      }
    37  
    38      const data: dataT[] = [
    39          {
    40              name: 'Sidebar is hidden',
    41              expect: (container) =>
    42                  expect(container.getElementsByClassName('mdc-drawer-sidebar--hidden')[0]).toBeTruthy(),
    43          },
    44          {
    45              name: 'Sidebar is displayed',
    46              expect: (container) => {
    47                  const result = elementQuerySelectorSafe(container, '.mdc-show-button');
    48                  act(() => {
    49                      result.click();
    50                  });
    51                  expect(container.getElementsByClassName('mdc-drawer-sidebar--displayed')[0]).toBeTruthy();
    52              },
    53          },
    54      ];
    55  
    56      const getNode = (overrides?: {}): JSX.Element => {
    57          // given
    58          const defaultProps: any = {
    59              children: null,
    60          };
    61          return (
    62              <MemoryRouter>
    63                  <TopAppBar {...defaultProps} {...overrides} />{' '}
    64              </MemoryRouter>
    65          );
    66      };
    67      const getWrapper = (overrides?: {}) => render(getNode(overrides));
    68  
    69      describe.each(data)(`SideBar functionality`, (testcase) => {
    70          it(testcase.name, () => {
    71              // when
    72              const { container } = getWrapper({});
    73              // then
    74              testcase.expect(container);
    75          });
    76      });
    77  });
    78  
    79  describe('Sidebar shows list of actions', () => {
    80      interface dataT {
    81          name: string;
    82          actions: BatchAction[];
    83          expectedNumOfActions: number;
    84      }
    85  
    86      const data: dataT[] = [
    87          {
    88              name: '2 results',
    89              actions: [
    90                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
    91                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
    92              ],
    93              expectedNumOfActions: 2,
    94          },
    95          {
    96              name: '1 results, repeated',
    97              actions: [
    98                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
    99                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   100              ],
   101              expectedNumOfActions: 1,
   102          },
   103          {
   104              name: '3 results',
   105              actions: [
   106                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   107                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   108                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   109              ],
   110              expectedNumOfActions: 3,
   111          },
   112          {
   113              name: '2 results, repeated',
   114              actions: [
   115                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   116                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   117                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   118              ],
   119              expectedNumOfActions: 2,
   120          },
   121          {
   122              name: '0 results',
   123              actions: [],
   124              expectedNumOfActions: 0,
   125          },
   126      ];
   127  
   128      const getNode = (overrides?: {}): JSX.Element | any => {
   129          // given
   130          const defaultProps: any = {
   131              children: null,
   132          };
   133          return (
   134              <MemoryRouter>
   135                  <TopAppBar {...defaultProps} {...overrides} />{' '}
   136              </MemoryRouter>
   137          );
   138      };
   139      const getWrapper = (overrides?: {}) => render(getNode(overrides));
   140  
   141      describe.each(data)('', (testcase) => {
   142          it(testcase.name, () => {
   143              // given
   144              updateActions(testcase.actions);
   145              // when
   146              const { container } = getWrapper({});
   147              const result = elementQuerySelectorSafe(container, '.mdc-show-button');
   148              act(() => {
   149                  result.click();
   150              });
   151              // then
   152              expect(container.getElementsByClassName('mdc-drawer-sidebar-list')[0].children).toHaveLength(
   153                  testcase.expectedNumOfActions
   154              );
   155          });
   156      });
   157  });
   158  
   159  describe('Sidebar test deletebutton', () => {
   160      interface dataT {
   161          name: string;
   162          actions: BatchAction[];
   163          expectedNumOfActions: number;
   164      }
   165  
   166      const data: dataT[] = [
   167          {
   168              name: '2 results',
   169              actions: [
   170                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   171                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   172              ],
   173              expectedNumOfActions: 1,
   174          },
   175          {
   176              name: '3 results',
   177              actions: [
   178                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   179                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   180                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   181              ],
   182              expectedNumOfActions: 2,
   183          },
   184          {
   185              name: '0 results',
   186              actions: [],
   187              expectedNumOfActions: 0,
   188          },
   189      ];
   190  
   191      const getNode = (overrides?: {}): JSX.Element | any => {
   192          // given
   193          const defaultProps: any = {
   194              children: null,
   195          };
   196          return (
   197              <MemoryRouter>
   198                  <TopAppBar {...defaultProps} {...overrides} />{' '}
   199              </MemoryRouter>
   200          );
   201      };
   202      const getWrapper = (overrides?: {}) => render(getNode(overrides));
   203  
   204      describe.each(data)('', (testcase) => {
   205          it(testcase.name, () => {
   206              // given
   207              updateActions(testcase.actions);
   208              // when
   209              const { container } = getWrapper({});
   210              const result = elementQuerySelectorSafe(container, '.mdc-show-button');
   211              act(() => {
   212                  result.click();
   213              });
   214              const svg = container.getElementsByClassName('mdc-drawer-sidebar-list-item-delete-icon')[0];
   215              if (svg) {
   216                  const button = svg.parentElement;
   217                  if (button) button.click();
   218              }
   219              // then
   220              expect(container.getElementsByClassName('mdc-drawer-sidebar-list')[0].children).toHaveLength(
   221                  testcase.expectedNumOfActions
   222              );
   223          });
   224      });
   225  });
   226  
   227  describe('Action Store functionality', () => {
   228      interface dataT {
   229          name: string;
   230          actions: BatchAction[];
   231          deleteActions?: BatchAction[];
   232          expectedActions: BatchAction[];
   233      }
   234  
   235      const dataGetSet: dataT[] = [
   236          {
   237              name: '1 action',
   238              actions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   239              expectedActions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   240          },
   241          {
   242              name: 'Empty action store',
   243              actions: [],
   244              expectedActions: [],
   245          },
   246          {
   247              name: '2 different type of actions',
   248              actions: [
   249                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   250                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   251              ],
   252              expectedActions: [
   253                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   254                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   255              ],
   256          },
   257          {
   258              name: '2 actions of the same type',
   259              actions: [
   260                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   261                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   262              ],
   263              expectedActions: [
   264                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   265                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   266              ],
   267          },
   268      ];
   269  
   270      describe.each(dataGetSet)('Test getting actions from the store and setting the store from an array', (testcase) => {
   271          it(testcase.name, () => {
   272              // given
   273              updateActions(testcase.actions);
   274              // when
   275              const actions = renderHook(() => useActions()).result.current;
   276              // then
   277              expect(actions).toStrictEqual(testcase.expectedActions);
   278          });
   279      });
   280  
   281      const dataAdding: dataT[] = [
   282          {
   283              name: '1 action',
   284              actions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   285              expectedActions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   286          },
   287          {
   288              name: 'Empty action store',
   289              actions: [],
   290              expectedActions: [],
   291          },
   292          {
   293              name: '2 different type of actions',
   294              actions: [
   295                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   296                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   297              ],
   298              expectedActions: [
   299                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   300                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   301              ],
   302          },
   303          {
   304              name: '2 actions of the same type',
   305              actions: [
   306                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   307                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   308              ],
   309              expectedActions: [
   310                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   311                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   312              ],
   313          },
   314      ];
   315  
   316      describe.each(dataAdding)('Test adding actions to the store', (testcase) => {
   317          it(testcase.name, () => {
   318              // given
   319              deleteAllActions();
   320              testcase.actions.forEach((action) => {
   321                  addAction(action);
   322              });
   323              // when
   324              const actions = renderHook(() => useActions()).result.current;
   325              // then
   326              expect(actions).toStrictEqual(testcase.expectedActions);
   327          });
   328      });
   329  
   330      const dataDeleting: dataT[] = [
   331          {
   332              name: 'delete 1 action - 0 remain',
   333              actions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   334              deleteActions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   335              expectedActions: [],
   336          },
   337          {
   338              name: 'delete 1 action (different action type, same app) - 1 remains',
   339              actions: [
   340                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   341                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   342              ],
   343              deleteActions: [{ action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } }],
   344              expectedActions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   345          },
   346          {
   347              name: 'delete 1 action (same action type, different app) - 1 remains',
   348              actions: [
   349                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   350                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   351              ],
   352              deleteActions: [{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }],
   353              expectedActions: [{ action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } }],
   354          },
   355          {
   356              name: 'delete 1 action from empty array - 0 remain',
   357              actions: [],
   358              deleteActions: [{ action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } }],
   359              expectedActions: [],
   360          },
   361          {
   362              name: 'delete 2 actions - 1 remain',
   363              actions: [
   364                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   365                  { action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } },
   366                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   367              ],
   368              deleteActions: [
   369                  { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   370                  { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   371              ],
   372              expectedActions: [{ action: { $case: 'undeploy', undeploy: { application: 'auth-service' } } }],
   373          },
   374      ];
   375  
   376      describe.each(dataDeleting)('Test deleting actions', (testcase) => {
   377          it(testcase.name, () => {
   378              // given
   379              updateActions(testcase.actions);
   380              // when
   381              testcase.deleteActions?.map((action) => deleteAction(action));
   382              const actions = renderHook(() => useActions()).result.current;
   383              // then
   384              expect(actions).toStrictEqual(testcase.expectedActions);
   385          });
   386      });
   387  });
   388  
   389  describe('Action details', () => {
   390      interface dataT {
   391          name: string;
   392          action: BatchAction;
   393          envLocks?: DisplayLock[];
   394          appLocks?: DisplayLock[];
   395          expectedDetails: ActionDetails;
   396      }
   397      const data: dataT[] = [
   398          {
   399              name: 'test createEnvironmentLock action',
   400              action: {
   401                  action: {
   402                      $case: 'createEnvironmentLock',
   403                      createEnvironmentLock: { environment: 'foo', lockId: 'ui-v2-1337', message: 'bar' },
   404                  },
   405              },
   406              expectedDetails: {
   407                  type: ActionTypes.CreateEnvironmentLock,
   408                  name: 'Create Env Lock',
   409                  dialogTitle: 'Are you sure you want to add this environment lock?',
   410                  tooltip:
   411                      'An environment lock will prevent automated process from changing the deployed version - note that kuberpult users can still deploy despite locks.',
   412                  summary: 'Create new environment lock on foo',
   413                  environment: 'foo',
   414              },
   415          },
   416          {
   417              name: 'test deleteEnvironmentLock action',
   418              action: {
   419                  action: {
   420                      $case: 'deleteEnvironmentLock',
   421                      deleteEnvironmentLock: { environment: 'foo', lockId: 'ui-v2-1337' },
   422                  },
   423              },
   424              envLocks: [
   425                  {
   426                      lockId: 'ui-v2-1337',
   427                      environment: 'foo',
   428                      message: 'bar',
   429                  },
   430              ],
   431              expectedDetails: {
   432                  type: ActionTypes.DeleteEnvironmentLock,
   433                  name: 'Delete Env Lock',
   434                  dialogTitle: 'Are you sure you want to delete this environment lock?',
   435                  summary: 'Delete environment lock on foo with the message: "bar"',
   436                  tooltip: 'This will only remove the lock, it will not automatically deploy anything.',
   437                  environment: 'foo',
   438                  lockId: 'ui-v2-1337',
   439                  lockMessage: 'bar',
   440              },
   441          },
   442          {
   443              name: 'test createEnvironmentApplicationLock action',
   444              action: {
   445                  action: {
   446                      $case: 'createEnvironmentApplicationLock',
   447                      createEnvironmentApplicationLock: {
   448                          environment: 'foo',
   449                          application: 'bread',
   450                          lockId: 'ui-v2-1337',
   451                          message: 'bar',
   452                      },
   453                  },
   454              },
   455              expectedDetails: {
   456                  type: ActionTypes.CreateApplicationLock,
   457                  name: 'Create App Lock',
   458                  dialogTitle: 'Are you sure you want to add this application lock?',
   459                  summary: 'Create new application lock for "bread" on foo',
   460                  tooltip:
   461                      'An app lock will prevent automated process from changing the deployed version - note that kuberpult users can still deploy despite locks.',
   462                  environment: 'foo',
   463                  application: 'bread',
   464              },
   465          },
   466          {
   467              name: 'test deleteEnvironmentApplicationLock action',
   468              action: {
   469                  action: {
   470                      $case: 'deleteEnvironmentApplicationLock',
   471                      deleteEnvironmentApplicationLock: { environment: 'foo', application: 'bar', lockId: 'ui-v2-1337' },
   472                  },
   473              },
   474              appLocks: [
   475                  {
   476                      lockId: 'ui-v2-1337',
   477                      environment: 'foo',
   478                      message: 'bar',
   479                      application: 'bar',
   480                  },
   481              ],
   482              expectedDetails: {
   483                  type: ActionTypes.DeleteApplicationLock,
   484                  name: 'Delete App Lock',
   485                  dialogTitle: 'Are you sure you want to delete this application lock?',
   486                  summary: 'Delete application lock for "bar" on foo with the message: "bar"',
   487                  tooltip: 'This will only remove the lock, it will not automatically deploy anything.',
   488                  environment: 'foo',
   489                  application: 'bar',
   490                  lockId: 'ui-v2-1337',
   491                  lockMessage: 'bar',
   492              },
   493          },
   494          {
   495              name: 'test deploy action',
   496              action: {
   497                  action: {
   498                      $case: 'deploy',
   499                      deploy: {
   500                          environment: 'foo',
   501                          application: 'bread',
   502                          version: 1337,
   503                          ignoreAllLocks: false,
   504                          lockBehavior: LockBehavior.IGNORE,
   505                      },
   506                  },
   507              },
   508              expectedDetails: {
   509                  type: ActionTypes.Deploy,
   510                  name: 'Deploy',
   511                  dialogTitle: 'Please be aware:',
   512                  summary: 'Deploy version 1337 of "bread" to foo',
   513                  tooltip: '',
   514                  environment: 'foo',
   515                  application: 'bread',
   516                  version: 1337,
   517              },
   518          },
   519          {
   520              name: 'test prepareUndeploy action',
   521              action: {
   522                  action: {
   523                      $case: 'prepareUndeploy',
   524                      prepareUndeploy: {
   525                          application: 'foo',
   526                      },
   527                  },
   528              },
   529              expectedDetails: {
   530                  type: ActionTypes.PrepareUndeploy,
   531                  name: 'Prepare Undeploy',
   532                  dialogTitle: 'Are you sure you want to start undeploy?',
   533                  tooltip:
   534                      'The new version will go through the same cycle as any other versions' +
   535                      ' (e.g. development->staging->production). ' +
   536                      'The behavior is similar to any other version that is created normally.',
   537                  summary: 'Prepare undeploy version for Application "foo"',
   538                  application: 'foo',
   539              },
   540          },
   541          {
   542              name: 'test undeploy action',
   543              action: {
   544                  action: {
   545                      $case: 'undeploy',
   546                      undeploy: {
   547                          application: 'foo',
   548                      },
   549                  },
   550              },
   551              expectedDetails: {
   552                  type: ActionTypes.Undeploy,
   553                  name: 'Undeploy',
   554                  dialogTitle: 'Are you sure you want to undeploy this application?',
   555                  tooltip: 'This application will be deleted permanently',
   556                  summary: 'Undeploy and delete Application "foo"',
   557                  application: 'foo',
   558              },
   559          },
   560          {
   561              name: 'test delete env from app action',
   562              action: {
   563                  action: {
   564                      $case: 'deleteEnvFromApp',
   565                      deleteEnvFromApp: {
   566                          environment: 'dev',
   567                          application: 'foo',
   568                      },
   569                  },
   570              },
   571              expectedDetails: {
   572                  type: ActionTypes.DeleteEnvFromApp,
   573                  name: 'Delete an Environment from App',
   574                  dialogTitle: 'Are you sure you want to delete environments from this application?',
   575                  tooltip: 'These environments will be deleted permanently from this application',
   576                  summary: 'Delete environment "dev" from application "foo"',
   577                  application: 'foo',
   578              },
   579          },
   580          {
   581              name: 'test releaseTrain action',
   582              action: {
   583                  action: {
   584                      $case: 'releaseTrain',
   585                      releaseTrain: {
   586                          target: 'dev',
   587                          team: '',
   588                          commitHash: '',
   589                      },
   590                  },
   591              },
   592              expectedDetails: {
   593                  type: ActionTypes.ReleaseTrain,
   594                  name: 'Release Train',
   595                  dialogTitle: 'Are you sure you want to run a Release Train',
   596                  summary: 'Run release train to environment dev',
   597                  tooltip: '',
   598                  environment: 'dev',
   599              },
   600          },
   601      ];
   602  
   603      describe.each(data)('Test getActionDetails function', (testcase) => {
   604          it(testcase.name, () => {
   605              const envLocks = testcase.envLocks || [];
   606              const appLocks = testcase.appLocks || [];
   607              const obtainedDetails = renderHook(() => getActionDetails(testcase.action, appLocks, envLocks)).result
   608                  .current;
   609              expect(obtainedDetails).toStrictEqual(testcase.expectedDetails);
   610          });
   611      });
   612  
   613      describe('Sidebar shows the number of planned actions', () => {
   614          interface dataT {
   615              name: string;
   616              actions: BatchAction[];
   617              expectedTitle: string;
   618          }
   619  
   620          const data: dataT[] = [
   621              {
   622                  name: '2 results',
   623                  actions: [
   624                      { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   625                      { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   626                  ],
   627                  expectedTitle: 'Planned Actions (2)',
   628              },
   629              {
   630                  name: '1 results, repeated',
   631                  actions: [
   632                      { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   633                      { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   634                  ],
   635                  expectedTitle: 'Planned Actions (1)',
   636              },
   637              {
   638                  name: '0 results',
   639                  actions: [],
   640                  expectedTitle: 'Planned Actions',
   641              },
   642          ];
   643  
   644          const getNode = (overrides?: {}): JSX.Element | any => {
   645              // given
   646              const defaultProps: any = {
   647                  children: null,
   648              };
   649              return (
   650                  <MemoryRouter>
   651                      <SideBar {...defaultProps} {...overrides} />
   652                  </MemoryRouter>
   653              );
   654          };
   655          const getWrapper = (overrides?: {}) => render(getNode(overrides));
   656  
   657          describe.each(data)('', (testcase) => {
   658              it(testcase.name, () => {
   659                  updateActions(testcase.actions);
   660                  const { container } = getWrapper({});
   661                  expect(container.getElementsByClassName('mdc-drawer-sidebar-header-title')[0].textContent).toBe(
   662                      testcase.expectedTitle
   663                  );
   664              });
   665          });
   666      });
   667      describe('Sidebar shows updates number of planned actions', () => {
   668          interface dataT {
   669              name: string;
   670              actions: BatchAction[];
   671              expectedTitle: string;
   672          }
   673  
   674          const data: dataT[] = [
   675              {
   676                  name: 'add 2 actions',
   677                  actions: [
   678                      { action: { $case: 'undeploy', undeploy: { application: 'nmww' } } },
   679                      { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } },
   680                  ],
   681                  expectedTitle: 'Planned Actions (3)',
   682              },
   683              {
   684                  name: 'Add another action',
   685                  actions: [
   686                      {
   687                          action: {
   688                              $case: 'deploy',
   689                              deploy: {
   690                                  environment: 'foo',
   691                                  application: 'bread',
   692                                  version: 1337,
   693                                  ignoreAllLocks: false,
   694                                  lockBehavior: LockBehavior.IGNORE,
   695                              },
   696                          },
   697                      },
   698                  ],
   699                  expectedTitle: 'Planned Actions (4)',
   700              },
   701              {
   702                  name: 'Add 2 more actions actions',
   703                  actions: [
   704                      { action: { $case: 'undeploy', undeploy: { application: 'test2' } } },
   705                      { action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'test2' } } },
   706                  ],
   707                  expectedTitle: 'Planned Actions (6)',
   708              },
   709          ];
   710  
   711          const getNode = (overrides?: {}): JSX.Element | any => {
   712              // given
   713              const defaultProps: any = {
   714                  children: null,
   715              };
   716              return (
   717                  <MemoryRouter>
   718                      <SideBar {...defaultProps} {...overrides} />
   719                  </MemoryRouter>
   720              );
   721          };
   722          const getWrapper = (overrides?: {}) => render(getNode(overrides));
   723          it('Create an action initially', () => {
   724              updateActions([{ action: { $case: 'undeploy', undeploy: { application: 'test' } } }]);
   725              const { container } = getWrapper({});
   726              expect(container.getElementsByClassName('mdc-drawer-sidebar-header-title')[0].textContent).toBe(
   727                  'Planned Actions (1)'
   728              );
   729          });
   730          describe.each(data)('', (testcase) => {
   731              it(testcase.name, () => {
   732                  appendAction(testcase.actions);
   733                  const { container } = getWrapper({});
   734                  expect(container.getElementsByClassName('mdc-drawer-sidebar-header-title')[0].textContent).toBe(
   735                      testcase.expectedTitle
   736                  );
   737              });
   738          });
   739          describe('Deleting an action from the cart', () => {
   740              it('Test deleting an an action', () => {
   741                  updateActions([{ action: { $case: 'undeploy', undeploy: { application: 'nmww' } } }]);
   742                  appendAction([{ action: { $case: 'prepareUndeploy', prepareUndeploy: { application: 'nmww' } } }]);
   743                  // Here we expect the value to be Planned Actions (1)
   744                  const expected = 'Planned Actions (1)';
   745                  const { container } = getWrapper({});
   746                  const svg = container.getElementsByClassName('mdc-drawer-sidebar-list-item-delete-icon')[0];
   747                  if (svg) {
   748                      const button = svg.parentElement;
   749                      if (button) button.click();
   750                  }
   751                  expect(container.getElementsByClassName('mdc-drawer-sidebar-header-title')[0].textContent).toBe(
   752                      expected
   753                  );
   754              });
   755          });
   756      });
   757  });