import { shallowMount } from '@vue/test-utils';
import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue';
import { VULNERABILITY_STATES } from 'ee/vulnerabilities/constants';

const vulnerabilityStateEntries = Object.entries(VULNERABILITY_STATES);

describe('Vulnerability state dropdown component', () => {
  let wrapper;

  const createWrapper = (state = vulnerabilityStateEntries[0][0]) => {
    // Create a dropdown that by default has the first vulnerability state selected.
    wrapper = shallowMount(VulnerabilityStateDropdown, {
      propsData: { state },
    });

    // Mock out this function, it's called by some methods in the component.
    wrapper.vm.$refs.dropdown.hide = jest.fn();
  };

  // A selected item has a selected icon as its child. We don't use .classes('.selected') here
  // because it only works with .find(), whereas item.contains() works with .find() and .findAll().
  const isSelected = items => items.contains('.selected-icon');
  const isDisabled = item => item.attributes('disabled') === 'true';
  const firstUnselectedItem = () => wrapper.find('.dropdown-item:not(.selected)');
  const selectedItem = () => wrapper.find('.dropdown-item.selected');
  const saveButton = () => wrapper.find({ ref: 'save-button' });
  const cancelButton = () => wrapper.find({ ref: 'cancel-button' });
  const innerDropdown = () => wrapper.find({ ref: 'dropdown' });

  afterEach(() => wrapper.destroy());

  test.each(vulnerabilityStateEntries)(
    'dropdown is created with the passed-in state already selected',
    (stateString, stateObject) => {
      createWrapper(stateString);
      const dropdownItem = wrapper.find(`.dropdown-item.${stateObject.action}`);

      // Check that the dropdown item is selected.
      expect(isSelected(dropdownItem)).toBe(true);
    },
  );

  it('if an unknown state is passed in, nothing will be selected by default', () => {
    createWrapper('some unknown state');
    const dropdownItems = wrapper.findAll('.dropdown-item');

    expect(isSelected(dropdownItems)).toBe(false);
  });

  test.each(vulnerabilityStateEntries)(
    `when the %s dropdown item is clicked, it's the only one that's selected`,
    (stateString, stateObject) => {
      // Start off with an unknown state so we can click through each item and see it change.
      createWrapper('some unknown state');
      const dropdownItem = wrapper.find(`.dropdown-item.${stateObject.action}`);

      dropdownItem.trigger('click');

      return wrapper.vm.$nextTick().then(() => {
        // Check that the clicked item is selected.
        expect(isSelected(dropdownItem)).toBe(true);
        // Check that the other items aren't selected.
        const otherItems = wrapper.find(`.dropdown-item:not(.${stateObject.action})`);
        expect(isSelected(otherItems)).toBe(false);
      });
    },
  );

  it('the save button should be enabled/disabled based on if the selected item has changed or not', () => {
    createWrapper();
    const originalItem = selectedItem();

    expect(isDisabled(saveButton())).toBe(true); // Check that the save button starts off as disabled.
    firstUnselectedItem().trigger('click'); // Click on an unselected item.

    return wrapper.vm
      .$nextTick()
      .then(() => {
        expect(isDisabled(saveButton())).toBe(false); // Check that the save button has been enabled.
        originalItem.trigger('click'); // Re-select the original item.
        return wrapper.vm.$nextTick();
      })
      .then(() => {
        expect(isDisabled(saveButton())).toBe(true); // Check that the save button has once again been disabled.
      });
  });

  it('clicking on the save button will close the dropdown and fire a change event', () => {
    createWrapper();

    expect(isDisabled(saveButton())).toBe(true); // Check that the save button starts off disabled.
    firstUnselectedItem().trigger('click'); // Click on an unselected item.

    return wrapper.vm.$nextTick().then(() => {
      saveButton().vm.$emit('click'); // Click on the save button.
      const changeEvent = wrapper.emitted('change');
      expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalledTimes(1); // Check that the dropdown hide function was called.
      expect(changeEvent).toHaveLength(1); // Check that a change event was emitted.
      expect(changeEvent[0][0]).toBeTruthy(); // Check that the change event has been emitted with something as its first parameter.
    });
  });

  it('clicking on the cancel button will close the dropdown without emitting any events', () => {
    createWrapper();

    expect(isDisabled(saveButton())).toBe(true); // Check that the save button starts out disabled.
    firstUnselectedItem().trigger('click'); // Click on an unselected item.

    return wrapper.vm.$nextTick().then(() => {
      expect(isDisabled(saveButton())).toBe(false); // Check that the save button is enabled.
      cancelButton().vm.$emit('click'); // Click on the cancel button.
      expect(Object.keys(wrapper.emitted())).toHaveLength(0); // Check that no events have been emitted.
      expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalledTimes(1); // Check that hide was called on the inner dropdown.
    });
  });

  it('when the dropdown is closed, the selected item resets back to the initial item', () => {
    createWrapper();
    const initialSelectedItem = selectedItem();

    firstUnselectedItem().trigger('click'); // Click on an unselected item.

    return wrapper.vm
      .$nextTick()
      .then(() => {
        expect(selectedItem().element).not.toBe(initialSelectedItem.element); // Check that the selected item actually changed.
        innerDropdown().vm.$emit('hide'); // Emit the dropdown hide event.
        return wrapper.vm.$nextTick();
      })
      .then(() => {
        expect(selectedItem().element).toBe(initialSelectedItem.element); // Check that the selected item has been reset back to the initial item.
      });
  });
});
