import { mount, createWrapper, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { nextTick } from 'vue';
import { within } from '@testing-library/dom';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import waitForPromises from 'helpers/wait_for_promises';
import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue';
import { member } from '../mock_data';

const localVue = createLocalVue();
localVue.use(Vuex);

describe('RoleDropdown', () => {
  let wrapper;
  let actions;
  const $toast = {
    show: jest.fn(),
  };

  const createStore = () => {
    actions = {
      updateMemberRole: jest.fn(() => Promise.resolve()),
    };

    return new Vuex.Store({ actions });
  };

  const createComponent = (propsData = {}) => {
    wrapper = mount(RoleDropdown, {
      propsData: {
        member,
        permissions: {},
        ...propsData,
      },
      localVue,
      store: createStore(),
      mocks: {
        $toast,
      },
    });
  };

  const getDropdownMenu = () => within(wrapper.element).getByRole('menu');
  const getByTextInDropdownMenu = (text, options = {}) =>
    createWrapper(within(getDropdownMenu()).getByText(text, options));
  const getDropdownItemByText = text =>
    createWrapper(
      within(getDropdownMenu())
        .getByText(text, { selector: '[role="menuitem"] p' })
        .closest('[role="menuitem"]'),
    );
  const getCheckedDropdownItem = () =>
    wrapper
      .findAll(GlDropdownItem)
      .wrappers.find(dropdownItemWrapper => dropdownItemWrapper.props('isChecked'));

  const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]');
  const findDropdown = () => wrapper.find(GlDropdown);

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

  describe('when dropdown is open', () => {
    beforeEach(done => {
      createComponent();

      findDropdownToggle().trigger('click');
      wrapper.vm.$root.$on('bv::dropdown::shown', () => {
        done();
      });
    });

    it('renders all valid roles', () => {
      Object.keys(member.validRoles).forEach(role => {
        expect(getDropdownItemByText(role).exists()).toBe(true);
      });
    });

    it('renders dropdown header', () => {
      expect(getByTextInDropdownMenu('Change permissions').exists()).toBe(true);
    });

    it('sets dropdown toggle and checks selected role', () => {
      expect(findDropdownToggle().text()).toBe('Owner');
      expect(getCheckedDropdownItem().text()).toBe('Owner');
    });

    describe('when dropdown item is selected', () => {
      it('does nothing if the item selected was already selected', () => {
        getDropdownItemByText('Owner').trigger('click');

        expect(actions.updateMemberRole).not.toHaveBeenCalled();
      });

      it('calls `updateMemberRole` Vuex action', () => {
        getDropdownItemByText('Developer').trigger('click');

        expect(actions.updateMemberRole).toHaveBeenCalledWith(expect.any(Object), {
          memberId: member.id,
          accessLevel: { integerValue: 30, stringValue: 'Developer' },
        });
      });

      it('displays toast when successful', async () => {
        getDropdownItemByText('Developer').trigger('click');

        await waitForPromises();

        expect($toast.show).toHaveBeenCalledWith('Role updated successfully.');
      });

      it('disables dropdown while waiting for `updateMemberRole` to resolve', async () => {
        getDropdownItemByText('Developer').trigger('click');

        await nextTick();

        expect(findDropdown().props('disabled')).toBe(true);

        await waitForPromises();

        expect(findDropdown().props('disabled')).toBe(false);
      });
    });
  });

  it("sets initial dropdown toggle value to member's role", () => {
    createComponent();

    expect(findDropdownToggle().text()).toBe('Owner');
  });

  it('sets the dropdown alignment to right on mobile', async () => {
    jest.spyOn(bp, 'isDesktop').mockReturnValue(false);
    createComponent();

    await nextTick();

    expect(findDropdown().attributes('right')).toBe('true');
  });

  it('sets the dropdown alignment to left on desktop', async () => {
    jest.spyOn(bp, 'isDesktop').mockReturnValue(true);
    createComponent();

    await nextTick();

    expect(findDropdown().attributes('right')).toBeUndefined();
  });
});
