text: const {CompositeDisposable} = require('atom')
  const TooltipManager = require('../src/tooltip-manager')
  const Tooltip = require('../src/tooltip')
  const _ = require('underscore-plus')
  
  describe('TooltipManager', () => {
    let manager, element
  
    const ctrlX = _.humanizeKeystroke('ctrl-x')
    const ctrlY = _.humanizeKeystroke('ctrl-y')
  
    const hover = function (element, fn) {
      mouseEnter(element)
      advanceClock(manager.hoverDefaults.delay.show)
      fn()
      mouseLeave(element)
      advanceClock(manager.hoverDefaults.delay.hide)
    }
  
    beforeEach(function () {
      manager = new TooltipManager({keymapManager: atom.keymaps, viewRegistry: atom.views})
      element = createElement('foo')
    })
  
    describe('::add(target, options)', () => {
      describe("when the trigger is 'hover' (the default)", () => {
        it('creates a tooltip when hovering over the target element', () => {
          manager.add(element, {title: 'Title'})
          hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
        })
  
        it('displays tooltips immediately when hovering over new elements once a tooltip has been displayed once', () => {
          const disposables = new CompositeDisposable()
          const element1 = createElement('foo')
          disposables.add(manager.add(element1, {title: 'Title'}))
          const element2 = createElement('bar')
          disposables.add(manager.add(element2, {title: 'Title'}))
          const element3 = createElement('baz')
          disposables.add(manager.add(element3, {title: 'Title'}))
  
          hover(element1, () => {})
          expect(document.body.querySelector('.tooltip')).toBeNull()
  
          mouseEnter(element2)
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          mouseLeave(element2)
          advanceClock(manager.hoverDefaults.delay.hide)
          expect(document.body.querySelector('.tooltip')).toBeNull()
  
          advanceClock(Tooltip.FOLLOW_THROUGH_DURATION)
          mouseEnter(element3)
          expect(document.body.querySelector('.tooltip')).toBeNull()
          advanceClock(manager.hoverDefaults.delay.show)
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
  
          disposables.dispose()
        })
      })
  
      describe("when the trigger is 'manual'", () =>
        it('creates a tooltip immediately and only hides it on dispose', () => {
          const disposable = manager.add(element, {title: 'Title', trigger: 'manual'})
          expect(document.body.querySelector('.tooltip')).toHaveText('Title')
          disposable.dispose()
          expect(document.body.querySelector('.tooltip')).toBeNull()
        })
      )
  
      describe("when the trigger is 'click'", () =>
        it('shows and hides the tooltip when the target element is clicked', () => {
          manager.add(element, {title: 'Title', trigger: 'click'})
          expect(document.body.querySelector('.tooltip')).toBeNull()
          element.click()
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          element.click()
          expect(document.body.querySelector('.tooltip')).toBeNull()
  
          // Hide the tooltip when clicking anywhere but inside the tooltip element
          element.click()
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          document.body.querySelector('.tooltip').click()
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          document.body.querySelector('.tooltip').firstChild.click()
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          document.body.click()
          expect(document.body.querySelector('.tooltip')).toBeNull()
  
          // Tooltip can show again after hiding due to clicking outside of the tooltip
          element.click()
          expect(document.body.querySelector('.tooltip')).not.toBeNull()
          element.click()
          expect(document.body.querySelector('.tooltip')).toBeNull()
        })
      )
  
      it('allows a custom item to be specified for the content of the tooltip', () => {
        const tooltipElement = document.createElement('div')
        manager.add(element, {item: {element: tooltipElement}})
        hover(element, () => expect(tooltipElement.closest('.tooltip')).not.toBeNull())
      })
  
      it('allows a custom class to be specified for the tooltip', () => {
        manager.add(element, {title: 'Title', class: 'custom-tooltip-class'})
        hover(element, () => expect(document.body.querySelector('.tooltip').classList.contains('custom-tooltip-class')).toBe(true))
      })
  
      it('allows jQuery elements to be passed as the target', () => {
        const element2 = document.createElement('div')
        jasmine.attachToDOM(element2)
  
        const fakeJqueryWrapper = {
          0: element,
          1: element2,
          length: 2,
          jquery: 'any-version'
        }
        const disposable = manager.add(fakeJqueryWrapper, {title: 'Title'})
  
        hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
        expect(document.body.querySelector('.tooltip')).toBeNull()
        hover(element2, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
        expect(document.body.querySelector('.tooltip')).toBeNull()
  
        disposable.dispose()
  
        hover(element, () => expect(document.body.querySelector('.tooltip')).toBeNull())
        hover(element2, () => expect(document.body.querySelector('.tooltip')).toBeNull())
      })
  
      describe('when a keyBindingCommand is specified', () => {
        describe('when a title is specified', () =>
          it('appends the key binding corresponding to the command to the title', () => {
            atom.keymaps.add('test', {
              '.foo': { 'ctrl-x ctrl-y': 'test-command'
              },
              '.bar': { 'ctrl-x ctrl-z': 'test-command'
              }
            }
            )
  
            manager.add(element, {title: 'Title', keyBindingCommand: 'test-command'})
  
            hover(element, function () {
              const tooltipElement = document.body.querySelector('.tooltip')
              expect(tooltipElement).toHaveText(`Title ${ctrlX} ${ctrlY}`)
            })
          })
        )
  
        describe('when no title is specified', () =>
          it('shows the key binding corresponding to the command alone', () => {
            atom.keymaps.add('test', {'.foo': {'ctrl-x ctrl-y': 'test-command'}})
  
            manager.add(element, {keyBindingCommand: 'test-command'})
  
            hover(element, function () {
              const tooltipElement = document.body.querySelector('.tooltip')
              expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
            })
          })
        )
  
        describe('when a keyBindingTarget is specified', () => {
          it('looks up the key binding relative to the target', () => {
            atom.keymaps.add('test', {
              '.bar': { 'ctrl-x ctrl-z': 'test-command'
              },
              '.foo': { 'ctrl-x ctrl-y': 'test-command'
              }
            }
            )
  
            manager.add(element, {keyBindingCommand: 'test-command', keyBindingTarget: element})
  
            hover(element, function () {
              const tooltipElement = document.body.querySelector('.tooltip')
              expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
            })
          })
  
          it('does not display the keybinding if there is nothing mapped to the specified keyBindingCommand', () => {
            manager.add(element, {title: 'A Title', keyBindingCommand: 'test-command', keyBindingTarget: element})
  
            hover(element, function () {
              const tooltipElement = document.body.querySelector('.tooltip')
              expect(tooltipElement.textContent).toBe('A Title')
            })
          })
        })
      })
  
      describe('when .dispose() is called on the returned disposable', () =>
        it('no longer displays the tooltip on hover', () => {
          const disposable = manager.add(element, {title: 'Title'})
  
          hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
  
          disposable.dispose()
  
          hover(element, () => expect(document.body.querySelector('.tooltip')).toBeNull())
        })
      )
  
      describe('when the window is resized', () =>
        it('hides the tooltips', () => {
          const disposable = manager.add(element, {title: 'Title'})
          hover(element, function () {
            expect(document.body.querySelector('.tooltip')).not.toBeNull()
            window.dispatchEvent(new CustomEvent('resize'))
            expect(document.body.querySelector('.tooltip')).toBeNull()
            disposable.dispose()
          })
        })
      )
  
      describe('findTooltips', () => {
        it('adds and remove tooltips correctly', () => {
          expect(manager.findTooltips(element).length).toBe(0)
          const disposable1 = manager.add(element, {title: 'elem1'})
          expect(manager.findTooltips(element).length).toBe(1)
          const disposable2 = manager.add(element, {title: 'elem2'})
          expect(manager.findTooltips(element).length).toBe(2)
          disposable1.dispose()
          expect(manager.findTooltips(element).length).toBe(1)
          disposable2.dispose()
          expect(manager.findTooltips(element).length).toBe(0)
        })
  
        it('lets us hide tooltips programmatically', () => {
          const disposable = manager.add(element, {title: 'Title'})
          hover(element, function () {
            expect(document.body.querySelector('.tooltip')).not.toBeNull()
            manager.findTooltips(element)[0].hide()
            expect(document.body.querySelector('.tooltip')).toBeNull()
            disposable.dispose()
          })
        })
      })
    })
  })
  
  function createElement (className) {
    const el = document.createElement('div')
    el.classList.add(className)
    jasmine.attachToDOM(el)
    return el
  }
  
  function mouseEnter (element) {
    element.dispatchEvent(new CustomEvent('mouseenter', {bubbles: false}))
    element.dispatchEvent(new CustomEvent('mouseover', {bubbles: true}))
  }
  
  function mouseLeave (element) {
    element.dispatchEvent(new CustomEvent('mouseleave', {bubbles: false}))
    element.dispatchEvent(new CustomEvent('mouseout', {bubbles: true}))
  }
  