huridocs/uwazi

View on GitHub
app/react/V2/Components/UI/specs/Table.cy.tsx

Summary

Maintainability
A
0 mins
Test Coverage
/* eslint-disable max-statements */
import React from 'react';
import 'cypress-axe';
import { mount } from '@cypress/react18';
import { composeStories } from '@storybook/react';
import { map } from 'lodash';
import * as stories from 'app/stories/Table.stories';
import { tableWithDisabled } from '../Table/specs/fixtures';

const { Basic, Nested, Custom } = composeStories(stories);

describe('Table', () => {
  const data = Basic.args.tableData || [];
  const dataWithNested = Nested.args.tableData || [];

  const checkRowContent = (rowNumber: number, cellsContent: string[]) => {
    cellsContent.forEach(
      (content, index) =>
        content &&
        cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content)
    );
  };

  it('should be accessible', () => {
    cy.injectAxe();
    mount(<Basic />);
    cy.checkA11y();
  });

  beforeEach(() => {
    Basic.args.defaultSorting = undefined;
    Basic.args.enableSelections = false;
    Basic.args.dnd = { enable: false, disableEditingGroups: false };
  });

  it('Should return a table with the columns and row specified', () => {
    mount(<Basic />);
    const toStrings = (cells: JQuery<HTMLElement>) => map(cells, 'textContent');
    cy.get('tr th').then(toStrings).should('eql', ['Title', 'Description', 'Date added']);

    checkRowContent(1, ['Entity 2', data[0].description, '2']);
    checkRowContent(2, ['Entity 1', data[1].description, '1']);
    checkRowContent(3, ['Entity 4', data[2].description, '4']);
    checkRowContent(4, ['Entity 3', data[3].description, '3']);
    checkRowContent(5, ['Entity 5', data[4].description, '5']);
  });

  it('should render the data in a custom component and styles', () => {
    mount(<Custom />);
    cy.get('table').within(() => {
      cy.contains('th', 'Description').should(
        'have.attr',
        'class',
        'p-4 text-sm text-gray-500 uppercase border-b bg-blue-700 text-white'
      );
      cy.contains('td', 'Entity 2').should(
        'have.attr',
        'class',
        'relative px-4 py-2 bg-gray-100 text-red-700 '
      );
      cy.contains('td', 'Entity 1').should(
        'have.attr',
        'class',
        'relative px-4 py-2 bg-gray-100 text-red-700 '
      );
      cy.get('div[class="text-white bg-orange-500"]').should('have.length', 5);
      cy.get('button').should('have.length', 5);
    });
  });

  it('should trigger the custom action for the buttons', () => {
    Custom.args.actionFn = cy.spy().as('actionSpy');
    mount(<Custom />);

    cy.contains('tr', 'Entity 1').within(() => {
      cy.contains('button', 'Action').realClick();
    });

    cy.get('@actionSpy').should('have.been.calledOnceWith', 'A1');
  });

  describe('Sorting', () => {
    it('Should be sortable by title', () => {
      mount(<Basic />);

      cy.get('th').contains('Title').realClick();
      cy.contains('button', 'Save changes').realClick();

      checkRowContent(1, ['Entity 1', data[1].description, '1']);
      checkRowContent(2, ['Entity 2', data[0].description, '2']);
      checkRowContent(3, ['Entity 3', data[3].description, '3']);
      checkRowContent(4, ['Entity 4', data[2].description, '4']);
      checkRowContent(5, ['Entity 5', data[4].description, '5']);
    });

    it('should return to the default sorting', () => {
      mount(<Basic />);
      cy.get('th').contains('Title').realClick().realClick().realClick();

      checkRowContent(1, ['Entity 2', data[0].description, '2']);
      checkRowContent(2, ['Entity 1', data[1].description, '1']);
      checkRowContent(3, ['Entity 4', data[2].description, '4']);
      checkRowContent(4, ['Entity 3', data[3].description, '3']);
      checkRowContent(5, ['Entity 5', data[4].description, '5']);
    });

    it('should keep selections when sorting', () => {
      Basic.args.enableSelections = true;
      mount(<Basic />);

      cy.get('tbody').within(() => {
        cy.get('input[type="checkbox"]').eq(0).check();
        cy.get('input[type="checkbox"]').eq(2).check();
      });

      cy.get('th').contains('Title').realClick().realClick();
      cy.contains('button', 'Save changes').realClick();

      cy.get('[data-testid="selected-items"]').within(() => {
        cy.contains('Entity 2');
        cy.contains('Entity 4');
      });
    });

    it('should sort items in groups', () => {
      mount(<Nested />);
      cy.contains('tr', 'Group 1').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      checkRowContent(1, [
        'Drag row',
        'Select',
        'Group',
        'Group 1',
        dataWithNested[0].description,
        '10',
      ]);
      checkRowContent(2, [
        'Drag row',
        'Select',
        '',
        'Sub 1-1',
        dataWithNested[0].subRows[0].description,
        '5',
      ]);
      checkRowContent(3, [
        'Drag row',
        'Select',
        '',
        'Sub 1-2',
        dataWithNested[0].subRows[1].description,
        '7',
      ]);

      cy.get('th').contains('Title').realClick().realClick();

      checkRowContent(6, [
        'Drag row',
        'Select',
        'Group',
        'Group 1',
        dataWithNested[0].description,
        '10',
      ]);
      checkRowContent(7, [
        'Drag row',
        'Select',
        '',
        'Sub 1-2',
        dataWithNested[0].subRows[1].description,
        '7',
      ]);
      checkRowContent(8, [
        'Drag row',
        'Select',
        '',
        'Sub 1-1',
        dataWithNested[0].subRows[0].description,
        '5',
      ]);
    });

    it('should disable sorting when defined in the columns', () => {
      mount(<Basic />);
      cy.get('tr th').contains('Title').children().should('have.length', 1);
      cy.get('tr th').contains('Description').children().should('have.length', 0);
      cy.get('tr th').contains('Date added').children().should('have.length', 1);

      cy.get('th').contains('Description').realClick();
      checkRowContent(1, ['Entity 2', data[0].description, '2']);
    });

    it('Should sort the rows with the sorting state specified', () => {
      Basic.args.defaultSorting = [{ id: 'created', desc: false }];
      mount(<Basic />);

      checkRowContent(1, ['Entity 1', data[1].description, '1']);
      checkRowContent(2, ['Entity 2', data[0].description, '2']);
      checkRowContent(3, ['Entity 3', data[3].description, '3']);
    });

    it('should reset sorting state when using dnd after sorting', () => {
      Basic.args.dnd = { enable: true };
      mount(<Nested />);

      cy.contains('tr', 'Group 1').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.get('th').contains('Title').realClick().realClick();
      cy.contains('Sorted by title');

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(6),
        cy.get('button[aria-roledescription="sortable"]').eq(2)
      );

      cy.contains('No sorting');
    });

    it('it should sort items within groups', () => {
      Nested.args.dnd = { enable: false };
      Nested.args.enableSelections = false;
      mount(<Nested />);

      cy.contains('tr', 'Group 1').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      checkRowContent(1, ['Open group', 'Group 1', dataWithNested[0].description, '10']);
      checkRowContent(2, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']);
      checkRowContent(3, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']);

      cy.contains('th', 'Title').realClick().realClick();

      checkRowContent(6, ['Open group', 'Group 1', dataWithNested[0].description, '10']);
      checkRowContent(7, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']);
      checkRowContent(8, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']);
    });

    it('should allow manually controlling the sorting', () => {
      const setSortingSpy = cy.stub().as('setSortingSpy');
      Basic.args.sortingFn = setSortingSpy;

      mount(<Basic />);
      checkRowContent(1, ['Entity 2', data[0].description, '2']);
      cy.get('th').contains('Title').realClick();
      checkRowContent(1, ['Entity 2', data[0].description, '2']);

      cy.get('@setSortingSpy').should('have.been.calledTwice');
      cy.get('@setSortingSpy').should('have.been.calledWith', []);
      cy.get('@setSortingSpy').should('have.been.calledWith', [{ id: 'title', desc: false }]);
    });
  });

  describe('Selections', () => {
    beforeEach(() => {
      Basic.args.enableSelections = true;
      Basic.args.dnd = { enable: true };
      mount(<Basic />);
    });

    it('should select and unselect some items selections', () => {
      cy.contains('Select all').realClick();
      cy.get('tbody').within(() => {
        cy.get('input[type="checkbox"]').eq(0).uncheck();
        cy.get('input[type="checkbox"]').eq(2).uncheck();
      });
      cy.contains('button', 'Save changes').realClick();

      cy.get('[data-testid="selected-items"]').within(() => {
        cy.contains('Entity 1');
        cy.contains('Entity 2').should('not.exist');
        cy.contains('Entity 3');
        cy.contains('Entity 4').should('not.exist');
        cy.contains('Entity 5');
      });
    });

    it('should reset selections when adding a new entry to the table', () => {
      cy.contains('Select all').realClick();
      cy.contains('button', 'Save changes').realClick();
      cy.get('[data-testid="selected-items"]').within(() => {
        cy.contains('Entity 1');
        cy.contains('Entity 2');
        cy.contains('Entity 3');
        cy.contains('Entity 4');
        cy.contains('Entity 5');
      });
      cy.get('#checkbox-header').should('be.checked');

      cy.contains('button', 'Add new item').realClick();
      cy.get('#checkbox-header').should('not.be.checked');
    });

    it('should reset selections when removing an item from the table', () => {
      cy.contains('Select all').realClick();
      cy.contains('button', 'Save changes').realClick();
      cy.get('#checkbox-header').should('be.checked');
      cy.contains('button', 'Remove last item').realClick();
      cy.get('#checkbox-header').should('not.be.checked');
    });

    it('should not select items with disabled row selection', () => {
      Nested.args.enableSelections = true;
      Nested.args.dnd = { enable: true };
      Nested.args.tableData = tableWithDisabled;
      mount(<Nested />);
      cy.contains('Select all').realClick();
      cy.contains('button', 'Save changes').realClick();
      cy.get('[data-testid="selected-items"]').within(() => {
        cy.contains('Group 1');
        cy.contains('Group 2');
        cy.contains('Item 1').should('not.exist');
        cy.contains('Item 2');
      });
      cy.get('[data-testid="selected-subrows"]').within(() => {
        cy.contains('Sub 1-1').should('not.exist');
        cy.contains('Sub 1-2');
        cy.contains('Sub 1-3');
      });
    });

    it('should change parent status based on selected children', () => {
      Nested.args.enableSelections = true;
      Nested.args.dnd = { enable: true };
      Nested.args.tableData = tableWithDisabled;
      mount(<Nested />);

      cy.contains('tr', 'Group 1').within(() => {
        cy.contains('button', 'Open group').realClick();
        cy.get('input').realClick().should('be.checked');
      });
      cy.contains('tr', 'Sub 1-1').within(() => {
        cy.get('input').should('not.be.checked');
      });
      cy.contains('tr', 'Sub 1-2').within(() => {
        cy.get('input').should('be.checked');
        cy.get('input').realClick();
      });
      cy.contains('tr', 'Sub 1-3').within(() => {
        cy.get('input').should('be.checked');
        cy.get('input').realClick();
      });
      cy.contains('tr', 'Group 1').within(() => {
        cy.get('input').should('be.checked');
      });
    });
  });

  describe('DnD', () => {
    beforeEach(() => {
      Basic.args.dnd = { enable: true };
      Basic.args.enableSelections = true;
      Nested.args.tableData = dataWithNested;
      mount(<Basic />);
      cy.get('[data-testid="sorted-items"]').within(() => {
        cy.contains('Entity 2 Entity 1 Entity 4 Entity 3 Entity 5');
      });
    });

    it('should sort rows by dragging', () => {
      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(1),
        cy.get('button[aria-roledescription="sortable"]').eq(0)
      );

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(3),
        cy.get('button[aria-roledescription="sortable"]').eq(2)
      );

      checkRowContent(1, ['Drag row', 'Select', 'Entity 1', data[1].description, '1']);
      checkRowContent(2, ['Drag row', 'Select', 'Entity 2', data[0].description, '2']);
      checkRowContent(3, ['Drag row', 'Select', 'Entity 3', data[3].description, '3']);
      checkRowContent(4, ['Drag row', 'Select', 'Entity 4', data[2].description, '4']);
      checkRowContent(5, ['Drag row', 'Select', 'Entity 5', data[4].description, '5']);

      cy.contains('button', 'Save changes').realClick();
      cy.get('[data-testid="sorted-items"]').within(() => {
        cy.contains('Entity 1 Entity 2 Entity 3 Entity 4 Entity 5');
      });
    });

    it('should keep selections while dragging', () => {
      cy.get('tbody').within(() => {
        cy.get('input[type="checkbox"]').eq(0).check();
        cy.get('input[type="checkbox"]').eq(2).check();
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(1),
        cy.get('button[aria-roledescription="sortable"]').eq(0)
      );

      cy.contains('button', 'Save changes').realClick();

      cy.get('[data-testid="selected-items"]').within(() => {
        cy.contains('Entity 1').should('not.exist');
        cy.contains('Entity 2');
        cy.contains('Entity 4');
        cy.contains('Entity 3').should('not.exist');
        cy.contains('Entity 5').should('not.exist');
      });

      cy.get('[data-testid="sorted-items"]').within(() => {
        cy.contains('Entity 1 Entity 2 Entity 4 Entity 3 Entity 5');
      });
    });
  });

  describe('Nested data', () => {
    beforeEach(() => {
      Nested.args.dnd = { enable: true };
      Nested.args.enableSelections = true;
      mount(<Nested />);
    });

    it('should check the content', () => {
      cy.get('[data-testid="sorted-items"]').within(() => {
        cy.contains('Group 1 Group 2 Group 3 Group 4 Item 1 Item 2');
      });
      checkRowContent(1, [
        'Drag row',
        'Select',
        'Group',
        'Group 1',
        dataWithNested[0].description,
        '10',
      ]);
      checkRowContent(2, [
        'Drag row',
        'Select',
        'Group',
        'Group 2',
        dataWithNested[1].description,
        '20',
      ]);
      checkRowContent(3, [
        'Drag row',
        'Select',
        'Group',
        'Group 3',
        dataWithNested[2].description,
        '30',
      ]);
      checkRowContent(4, [
        'Drag row',
        'Select',
        'Group',
        'Group 4',
        dataWithNested[3].description,
        '40',
      ]);
      checkRowContent(5, [
        'Drag row',
        'Select',
        undefined,
        'Item 1',
        dataWithNested[4].description,
        '50',
      ]);
      checkRowContent(6, [
        'Drag row',
        'Select',
        undefined,
        'Item 2',
        dataWithNested[5].description,
        '60',
      ]);
    });

    it('should expand groups and check for accessibility', () => {
      cy.get('tbody').within(() => {
        cy.contains('tr', 'Group 1').within(() => {
          cy.contains('button', 'Open group').realClick();
        });
        cy.contains('tr', 'Group 2').within(() => {
          cy.contains('button', 'Open group').realClick();
        });
        cy.contains('tr', 'Group 3').within(() => {
          cy.contains('button', 'Open group').realClick();
        });
        cy.contains('td', 'Sub 1-1');
        cy.contains('td', 'Sub 1-2');
        cy.contains('td', 'Sub 2-1');
        cy.contains('td', 'Sub 2-2');
        cy.contains('td', 'Sub 3-1');
        cy.contains('td', 'Sub 1-2');
      });

      cy.checkA11y();
    });

    it('should sort children element with dnd', () => {
      cy.get('tbody').within(() => {
        cy.contains('tr', 'Group 1').within(() => {
          cy.contains('Open group').realClick();
        });
        cy.contains('tr', 'Group 3').within(() => {
          cy.contains('Open group').realClick();
        });
        cy.contains('td', 'Sub 1-1');
        cy.contains('td', 'Sub 1-2');
        cy.contains('td', 'Sub 3-1');
        cy.contains('td', 'Sub 3-2');
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(5),
        cy.get('button[aria-roledescription="sortable"]').eq(1)
      );

      checkRowContent(1, [
        'Drag row',
        'Select',
        'Open group',
        'Group 1',
        dataWithNested[0].description,
        '10',
      ]);
      checkRowContent(2, [
        'Drag row',
        'Select',
        undefined,
        'Sub 3-1',
        dataWithNested[2].subRows[0].description,
        '12',
      ]);

      cy.contains('button', 'Save changes').realClick();

      cy.get('[data-testid="sorted-subrows"] > .flex > :nth-child(1)').contains(
        '|Group 1 - Sub 3-1|'
      );
    });

    it('should add an item to an empty group', () => {
      cy.contains('tr', 'Group 4').within(() => {
        cy.contains('button', 'Open group').realClick();
      });
      cy.contains('tr', 'Group 3').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(3),
        cy.get('td').contains('Empty group. Drop here to add')
      );

      checkRowContent(5, [
        'Drag row',
        'Select',
        'Open group',
        'Group 4',
        dataWithNested[3].description,
        '40',
      ]);
      checkRowContent(6, [
        'Drag row',
        'Select',
        undefined,
        'Sub 3-1',
        dataWithNested[2].subRows[0].description,
        '12',
      ]);
    });

    it('should empty a group by dragging all items out of it', () => {
      cy.contains('tr', 'Group 1').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.contains('tr', 'Empty group. Drop here to add').should('not.exist');

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(1),
        cy.get('button[aria-roledescription="sortable"]').eq(0)
      );
      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(2),
        cy.get('button[aria-roledescription="sortable"]').eq(0)
      );

      cy.contains('tr', 'Empty group. Drop here to add').should('exist');

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(7),
        cy.get('td').contains('Empty group. Drop here to add')
      );

      checkRowContent(3, [
        'Drag row',
        'Select',
        'Open group',
        'Group 1',
        dataWithNested[0].description,
        '10',
      ]);
      checkRowContent(4, [
        'Drag row',
        'Select',
        undefined,
        'Item 2',
        dataWithNested[5].description,
        '60',
      ]);

      cy.contains('tr', 'Empty group. Drop here to add').should('not.exist');
    });

    it('should not loose selections when dragging into a dropzone', () => {
      cy.contains('tr', 'Group 4').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.contains('tr', 'Item 2').within(() => {
        cy.get('input[type="checkbox"]').check();
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(5),
        cy.get('td').contains('Empty group. Drop here to add')
      );

      cy.contains('button', 'Save changes').realClick();

      cy.get('[data-testid="selected-subrows"]').within(() => {
        cy.contains('Item 2');
      });
    });

    it('should disable editing groups with dnd but allow sorting them internally', () => {
      Nested.args.dnd = { enable: true, disableEditingGroups: true };
      mount(<Nested />);

      cy.contains('tr', 'Group 4').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(5),
        cy.get('td').contains('Empty group. Drop here to add')
      );

      checkRowContent(5, ['Empty group. Drop here to add']);

      cy.contains('tr', 'Group 2').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(7),
        cy.get('td').contains('Sub 2-1')
      );

      checkRowContent(2, ['Drag row', 'Select', 'Group', 'Group 2']);
      checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-1']);
      checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-2']);
      checkRowContent(9, ['Drag row', 'Select', '', 'Item 2']);

      cy.realDragAndDrop(
        cy.get('button[aria-roledescription="sortable"]').eq(2),
        cy.get('td').contains('Sub 2-2')
      );

      checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-2']);
      checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-1']);
    });

    it('should render the correct text for empty groups based on dnd status', () => {
      Nested.args.dnd = { enable: false };
      Nested.args.enableSelections = false;
      mount(<Nested />);

      cy.contains('tr', 'Group 4').within(() => {
        cy.contains('button', 'Open group').realClick();
      });

      cy.contains('This group is empty');
    });
  });
});