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

const vulnerabilityStateEntries = Object.entries(VULNERABILITY_STATE_OBJECTS);

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

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

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

  // isSelected is designed to work with both single VueWrapper or WrapperArray
  const isSelected = (items) =>
    Boolean((items.wrappers ?? [items]).find((w) => w.find('.selected-icon').exists()));
  const isDisabled = (item) => item.attributes('disabled') === 'true';
  const dropdownItems = () => wrapper.findAll('.dropdown-item');
  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' });

  const dropdownItemFor = (stateEntry) =>
    dropdownItems().wrappers.find((x) => {
      const text = x.text();
      return text.includes(stateEntry.displayName) && text.includes(stateEntry.description);
    });

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

  describe('tests that need to manually create the wrapper', () => {
    it.each(vulnerabilityStateEntries)(
      'dropdown is created with the %s state already selected',
      (stateString, stateObject) => {
        createWrapper(stateString);

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

    it('if an unknown state is passed in, nothing will be selected by default', () => {
      createWrapper('some unknown state');
      expect(isSelected(dropdownItems())).toBe(false);
    });

    it.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 = dropdownItemFor(stateObject);

        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.findAll('.dropdown-item:not(.selected)');
          expect(isSelected(otherItems)).toBe(false);
        });
      },
    );
  });

  describe('tests that use the default wrapper', () => {
    beforeEach(() => createWrapper());

    it('the save button should be enabled/disabled based on if the selected item has changed or not', () => {
      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', () => {
      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', () => {
      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.
        });
    });

    it('when the parent component changes the state, the dropdown will update its selected and initial item', () => {
      const [stateString, stateObject] = vulnerabilityStateEntries[1];

      wrapper.setProps({ initialState: stateString }); // Change the state.

      return wrapper.vm.$nextTick().then(() => {
        expect(innerDropdown().props('text')).toBe(stateString); // Check that the dropdown button's value matches the initial state.
        expect(selectedItem().text()).toMatch(new RegExp(`^${stateObject.action}`, 'i')); // Check that the selected item is the initial state.
        expect(isDisabled(saveButton())).toBe(true); // Check that the save button is disabled.
      });
    });
  });
});
