import { shallowMount } from '@vue/test-utils';
import { GlTooltip } from '@gitlab/ui';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Tooltips from '~/tooltips/components/tooltips.vue';

describe('tooltips/components/tooltips.vue', () => {
  const { trigger: triggerMutate, observersCount } = useMockMutationObserver();
  let wrapper;

  const buildWrapper = () => {
    wrapper = shallowMount(Tooltips);
  };

  const createTooltipTarget = (attributes = {}) => {
    const target = document.createElement('button');
    const defaults = {
      title: 'default title',
      ...attributes,
    };

    Object.keys(defaults).forEach(name => {
      target.setAttribute(name, defaults[name]);
    });

    document.body.appendChild(target);

    return target;
  };

  const allTooltips = () => wrapper.findAll(GlTooltip);

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

  describe('addTooltips', () => {
    let target;

    beforeEach(() => {
      buildWrapper();

      target = createTooltipTarget();
    });

    it('attaches tooltips to the targets specified', async () => {
      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlTooltip).props('target')).toBe(target);
    });

    it('does not attach a tooltip twice to the same element', async () => {
      wrapper.vm.addTooltips([target]);
      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      expect(wrapper.findAll(GlTooltip)).toHaveLength(1);
    });

    it('sets tooltip content from title attribute', async () => {
      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlTooltip).text()).toBe(target.getAttribute('title'));
    });

    it('supports HTML content', async () => {
      target = createTooltipTarget({
        title: 'content with <b>HTML</b>',
        'data-html': true,
      });
      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlTooltip).html()).toContain(target.getAttribute('title'));
    });

    it('sets the configuration values passed in the config object', async () => {
      const config = { show: true };
      target = createTooltipTarget();
      wrapper.vm.addTooltips([target], config);
      await wrapper.vm.$nextTick();
      expect(wrapper.find(GlTooltip).props()).toMatchObject(config);
    });

    it.each`
      attribute           | value                 | prop
      ${'data-placement'} | ${'bottom'}           | ${'placement'}
      ${'data-container'} | ${'custom-container'} | ${'container'}
      ${'data-boundary'}  | ${'viewport'}         | ${'boundary'}
      ${'data-triggers'}  | ${'manual'}           | ${'triggers'}
    `(
      'sets $prop to $value when $attribute is set in target',
      async ({ attribute, value, prop }) => {
        target = createTooltipTarget({ [attribute]: value });
        wrapper.vm.addTooltips([target]);

        await wrapper.vm.$nextTick();

        expect(wrapper.find(GlTooltip).props(prop)).toBe(value);
      },
    );
  });

  describe('dispose', () => {
    beforeEach(() => {
      buildWrapper();
    });

    it('removes all tooltips when elements is nil', async () => {
      wrapper.vm.addTooltips([createTooltipTarget(), createTooltipTarget()]);
      await wrapper.vm.$nextTick();

      wrapper.vm.dispose();
      await wrapper.vm.$nextTick();

      expect(allTooltips()).toHaveLength(0);
    });

    it('removes the tooltips that target the elements specified', async () => {
      const target = createTooltipTarget();

      wrapper.vm.addTooltips([target, createTooltipTarget()]);
      await wrapper.vm.$nextTick();

      wrapper.vm.dispose(target);
      await wrapper.vm.$nextTick();

      expect(allTooltips()).toHaveLength(1);
    });
  });

  describe('observe', () => {
    beforeEach(() => {
      buildWrapper();
    });

    it('removes tooltip when target is removed from the document', async () => {
      const target = createTooltipTarget();

      wrapper.vm.addTooltips([target, createTooltipTarget()]);
      await wrapper.vm.$nextTick();

      triggerMutate(document.body, {
        entry: { removedNodes: [target] },
        options: { childList: true },
      });
      await wrapper.vm.$nextTick();

      expect(allTooltips()).toHaveLength(1);
    });
  });

  describe('triggerEvent', () => {
    it('triggers a bootstrap-vue tooltip global event for the tooltip specified', async () => {
      const target = createTooltipTarget();
      const event = 'hide';

      buildWrapper();

      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      wrapper.vm.triggerEvent(target, event);

      expect(wrapper.find(GlTooltip).emitted(event)).toHaveLength(1);
    });
  });

  describe('fixTitle', () => {
    it('updates tooltip content with the latest value the target title property', async () => {
      const target = createTooltipTarget();
      const currentTitle = 'title';
      const newTitle = 'new title';

      target.setAttribute('title', currentTitle);

      buildWrapper();

      wrapper.vm.addTooltips([target]);

      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlTooltip).text()).toBe(currentTitle);

      target.setAttribute('title', newTitle);
      wrapper.vm.fixTitle(target);

      await wrapper.vm.$nextTick();

      expect(wrapper.find(GlTooltip).text()).toBe(newTitle);
    });
  });

  it('disconnects mutation observer on beforeDestroy', () => {
    buildWrapper();
    wrapper.vm.addTooltips([createTooltipTarget()]);

    expect(observersCount()).toBe(1);

    wrapper.destroy();
    expect(observersCount()).toBe(0);
  });
});
