huridocs/uwazi

View on GitHub
app/react/Forms/components/specs/MultiSelect.spec.js

Summary

Maintainability
D
1 day
Test Coverage
import React from 'react';
import { shallow } from 'enzyme';

import { MultiSelect } from '../MultiSelect';

describe('MultiSelect', () => {
  let component;
  let instance;
  let props;

  beforeEach(() => {
    props = {
      label: 'input label',
      value: [],
      options: [
        { label: 'Option1', value: 'option1', results: 5 },
        { label: 'Option2', value: 'option2', results: 4 },
        {
          label: 'Sub Group',
          value: 'Group',
          results: 3,
          options: [
            { label: 'Group option1', value: 'group-option1', results: 2 },
            { label: 'Group option2', value: 'group-option2', results: 1 },
          ],
        },
      ],
      onChange: jasmine.createSpy('onChange'),
      onFilter: jasmine.createSpy('onFilter').and.callFake(async () => Promise.resolve()),
    };
  });

  const render = () => {
    component = shallow(<MultiSelect {...props} />);
    instance = component.instance();
  };

  it.each`
    index | value              | state
    ${0}  | ${'option1'}       | ${0}
    ${1}  | ${'option2'}       | ${0}
    ${2}  | ${undefined}       | ${0}
    ${3}  | ${'group-option1'} | ${0}
    ${4}  | ${'group-option2'} | ${0}
  `('should render the checkboxes empty: $value', ({ index, value, state }) => {
    render();
    const optionElements = component.find('input[type="checkbox"]');
    expect(optionElements.length).toBe(5);
    expect(optionElements.at(index).props().value).toBe(value);
    expect(optionElements.at(index).prop('data-state')).toBe(state);
  });

  it.each`
    index | value              | state
    ${0}  | ${undefined}       | ${1}
    ${1}  | ${'group-option1'} | ${0}
    ${2}  | ${'group-option2'} | ${2}
    ${3}  | ${'option1'}       | ${0}
    ${4}  | ${'option2'}       | ${0}
  `('should render the checkboxes partial group: $value', ({ index, value, state }) => {
    props.value = ['group-option2'];
    render();
    const optionElements = component.find('input[type="checkbox"]');
    expect(optionElements.length).toBe(5);
    expect(optionElements.at(index).props().value).toBe(value);
    expect(optionElements.at(index).prop('data-state')).toBe(state);
  });

  it.each`
    index | value              | state
    ${0}  | ${undefined}       | ${3}
    ${1}  | ${'group-option1'} | ${0}
    ${2}  | ${'group-option2'} | ${0}
    ${3}  | ${'option1'}       | ${0}
    ${4}  | ${'option2'}       | ${0}
  `(
    'should render correctly when the selection is a proper group: $value',
    ({ index, value, state }) => {
      props.value = ['Group'];
      render();
      const optionElements = component.find('input[type="checkbox"]');
      expect(optionElements.length).toBe(5);
      expect(optionElements.at(index).props().value).toBe(value);
      expect(optionElements.at(index).prop('data-state')).toBe(state);
    }
  );

  it('should not render aggregations on the groups when not defined', () => {
    delete props.options[2].results;
    render();
    const groupAggregation = component.find(
      '.multiselect-group .multiselectItem .multiselectItem-results'
    );
    expect(groupAggregation.at(0)).toMatchSnapshot();
  });

  it('should display a not found message when there are no options', () => {
    props.options = [];
    props.sourceName = 'My List';
    render();
    expect(component).toMatchSnapshot();
  });

  it('should use title property if present', () => {
    props.options[1].title = 'this should be the title';
    render();
    expect(component.find('li.multiselectItem').map(li => li.prop('title'))).toEqual([
      'Option1',
      'this should be the title',
      'Group option1',
      'Group option2',
    ]);
  });

  it('should not render an empty group', () => {
    props.options.push({ label: 'Empty Group', value: 'empty', options: [] });
    render();
    expect(component).toMatchSnapshot();
  });

  describe('when checking an option', () => {
    it('should call onChange with the new value', () => {
      render();
      component.find('input[type="checkbox"]').at(0).simulate('change');
      expect(props.onChange).toHaveBeenCalledWith(['option1']);
    });

    it('it should handle multiple options selected', () => {
      props.value = ['option1'];
      render();
      component.find('input[type="checkbox"]').at(1).simulate('change');
      expect(props.onChange).toHaveBeenCalledWith(['option1', 'option2']);
    });

    it('it should remove options that were selected', () => {
      props.value = ['option1', 'option2'];
      render();
      component.find('input[type="checkbox"]').at(0).simulate('change');
      expect(props.onChange).toHaveBeenCalledWith(['option2']);
    });
  });

  describe('checking a group', () => {
    describe('when allowSelectGroup is set', () => {
      it('should select the group', () => {
        props.allowSelectGroup = true;
        render();
        component
          .find('.group-checkbox')
          .first()
          .simulate('change', { target: { checked: true, dataset: { state: '0' } } });
        expect(props.onChange).toHaveBeenCalledWith(['group-option1', 'group-option2']);
        component
          .find('.group-checkbox')
          .first()
          .simulate('change', { target: { checked: false, dataset: { state: '2' } } });
        expect(props.onChange).toHaveBeenCalledWith(['Group']);
        component
          .find('.group-checkbox')
          .first()
          .simulate('change', { target: { checked: false, dataset: { state: '3' } } });
        expect(props.onChange).toHaveBeenCalledWith([]);
      });
    });
    describe('when allowSelectGroup is NOT set', () => {
      it('should select all options of that group', () => {
        render();
        component
          .find('.group-checkbox')
          .first()
          .simulate('change', { target: { checked: true, dataset: { state: '0' } } });
        expect(props.onChange).toHaveBeenCalledWith(['group-option1', 'group-option2']);
        component
          .find('.group-checkbox')
          .first()
          .simulate('change', { target: { checked: false, dataset: { state: '2' } } });
        expect(props.onChange).toHaveBeenCalledWith([]);
      });
    });
  });

  describe('toggleOptions', () => {
    it('should toggle a flag in the state to show or not group sub options', () => {
      render();
      component
        .find('.multiselectItem-action')
        .at(2)
        .simulate('click', { preventDefault: () => {} });
      expect(component.state().ui).toEqual({ Group: true });
    });
  });

  describe('filtering', () => {
    beforeEach(() => {
      props = {
        label: 'input label',
        value: [],
        options: [
          { label: 'Option1', value: 'option1', results: 5 },
          { label: 'Option2', value: 'option2', results: 4 },
          {
            label: 'Sub Group',
            value: 'Group',
            results: 3,
            options: [
              { label: 'Group option', value: 'group-option1', results: 2 },
              { label: 'Group option2', value: 'group-option2', results: 1 },
            ],
          },
        ],
        onChange: jasmine.createSpy('onChange'),
      };
    });
    it('should render only options matching the filter', () => {
      render();
      component.setState({ filter: '1' });
      const optionElements = component.find('input[type="checkbox"]');
      expect(optionElements.length).toBe(1);
      expect(optionElements.first().props().value).toBe('option1');
    });
    it('should render group if children match filter', () => {
      render();
      component.setState({ filter: '2' });
      const optionElements = component.find('input[type="checkbox"]');
      expect(optionElements.length).toBe(4);
      expect(optionElements.first().props().value).toBe('option2');
      expect(optionElements.at(2).props().value).toBe('group-option1');
      expect(optionElements.at(3).props().value).toBe('group-option2');
    });
  });

  describe('different key name for label and value', () => {
    beforeEach(() => {
      props = {
        label: 'input label',
        value: [],
        options: [
          { name: 'Option1', id: 'option1', results: 4 },
          { name: 'Option3', id: 'option3', results: 3 },
          { name: 'Option2', id: 'option2', results: 2 },
        ],
        optionsValue: 'id',
        optionsLabel: 'name',
        onChange: jasmine.createSpy('onChange'),
      };
    });

    it('should render the options', () => {
      render();
      const optionElements = component.find('input[type="checkbox"]');

      expect(optionElements.length).toBe(3);
      expect(optionElements.first().props().value).toBe('option1');
      expect(optionElements.last().props().value).toBe('option2');
    });

    it('should render the options by the result and then label if list is sorted', () => {
      props.sort = true;
      render();
      const optionElements = component.find('input[type="checkbox"]');

      expect(optionElements.length).toBe(3);
      expect(optionElements.first().props().value).toBe('option1');
      expect(optionElements.last().props().value).toBe('option3');
    });
  });

  describe('sorting', () => {
    beforeEach(() => {
      props = {
        label: 'input label',
        value: [],
        options: [
          { label: 'D', value: 'option1', results: 2 },
          { label: 'A', value: 'option2', results: 1 },
          { label: 'C', value: 'option3', results: 4 },
        ],
        onChange: jasmine.createSpy('onChange'),
      };
    });
    describe('when prop.sort is false', () => {
      it('should sort by descending aggregated results count', () => {
        render();
        const options = component.find('input[type="checkbox"]');
        expect(options.length).toBe(3);
        expect(options.first().props().value).toBe('option3');
        expect(options.last().props().value).toBe('option2');
      });
      it('should use alphabetical order as tie breaker if items have the same count', () => {
        props.options[0].results = 4;
        props.options.push({ label: 'B', value: 'option4', results: 1 });
        render();
        const options = component.find('input[type="checkbox"]');
        expect(options.length).toBe(4);
        expect(options.first().props().value).toBe('option3');
        expect(options.at(1).props().value).toBe('option1');
        expect(options.at(2).props().value).toBe('option2');
        expect(options.last().props().value).toBe('option4');
      });
      it('should not sort if options do not have aggregate results', () => {
        props.options = [
          { label: 'D', value: 'option1' },
          { label: 'A', value: 'option2' },
          { label: 'C', value: 'option3' },
        ];
        render();
        const options = component.find('input[type="checkbox"]');
        expect(options.length).toBe(3);
        expect(options.first().props().value).toBe('option1');
        expect(options.at(1).props().value).toBe('option2');
        expect(options.last().props().value).toBe('option3');
      });
    });

    describe('when prop.sort is true', () => {
      it('should sort by alphabetical order', () => {
        props.sort = true;
        render();
        const options = component.find('input[type="checkbox"]');
        expect(options.length).toBe(3);
        expect(options.first().props().value).toBe('option2');
        expect(options.at(1).props().value).toBe('option3');
        expect(options.last().props().value).toBe('option1');
      });
      it('should sort by descending results if not showing all options', () => {
        props.showAll = false;
        props.optionsToShow = 2;

        render();
        const options = component.find('input[type="checkbox"]');
        expect(options.length).toBe(2);
        expect(options.first().props().value).toBe('option3');
        expect(options.at(1).props().value).toBe('option1');
      });
    });
  });

  describe('filter()', () => {
    it('should set the input value filter', () => {
      instance.filter({ target: { value: 'something' } });
      expect(instance.state.filter).toBe('something');
    });
  });

  describe('resetFilter()', () => {
    it('should reset the filter', () => {
      render();
      instance.resetFilter();
      expect(instance.state.filter).toBe('');
      expect(props.onFilter).toHaveBeenCalledWith('');
    });
  });

  describe('component update props', () => {
    it('should set the filter in the state', () => {
      component.setProps({ filter: 'Only this' });
      expect(instance.state.filter).toBe('Only this');
    });
  });

  describe('showAll()', () => {
    it('should toggle state flag showAll', () => {
      instance.showAll({ preventDefault: () => {} });
      expect(instance.state.showAll).toBe(true);

      instance.showAll({ preventDefault: () => {} });
      expect(instance.state.showAll).toBe(false);
    });
  });
});