import Backdrop from '../../../src/util/backdrop'
import { getTransitionDurationFromElement } from '../../../src/util/index'
import { clearFixture, getFixture } from '../../helpers/fixture'

const CLASS_BACKDROP = '.modal-backdrop'
const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_SHOW = 'show'

describe('Backdrop', () => {
  let fixtureEl

  beforeAll(() => {
    fixtureEl = getFixture()
  })

  afterEach(() => {
    clearFixture()
    const list = document.querySelectorAll(CLASS_BACKDROP)

    for (const el of list) {
      el.remove()
    }
  })

  describe('show', () => {
    it('should append the backdrop html once on show and include the "show" class if it is "shown"', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: true,
          isAnimated: false
        })
        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)

        expect(getElements()).toHaveSize(0)

        instance.show()
        instance.show(() => {
          expect(getElements()).toHaveSize(1)
          for (const el of getElements()) {
            expect(el).toHaveClass(CLASS_NAME_SHOW)
          }

          resolve()
        })
      })
    })

    it('should not append the backdrop html if it is not "shown"', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: false,
          isAnimated: true
        })
        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)

        expect(getElements()).toHaveSize(0)
        instance.show(() => {
          expect(getElements()).toHaveSize(0)
          resolve()
        })
      })
    })

    it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: true,
          isAnimated: true
        })
        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)

        expect(getElements()).toHaveSize(0)

        instance.show(() => {
          expect(getElements()).toHaveSize(1)
          for (const el of getElements()) {
            expect(el).toHaveClass(CLASS_NAME_FADE)
          }

          resolve()
        })
      })
    })
  })

  describe('hide', () => {
    it('should remove the backdrop html', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: true,
          isAnimated: true
        })

        const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)

        expect(getElements()).toHaveSize(0)
        instance.show(() => {
          expect(getElements()).toHaveSize(1)
          instance.hide(() => {
            expect(getElements()).toHaveSize(0)
            resolve()
          })
        })
      })
    })

    it('should remove the "show" class', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: true,
          isAnimated: true
        })
        const elem = instance._getElement()

        instance.show()
        instance.hide(() => {
          expect(elem).not.toHaveClass(CLASS_NAME_SHOW)
          resolve()
        })
      })
    })

    it('should not try to remove Node on remove method if it is not "shown"', () => {
      return new Promise(resolve => {
        const instance = new Backdrop({
          isVisible: false,
          isAnimated: true
        })
        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
        const spy = spyOn(instance, 'dispose').and.callThrough()

        expect(getElements()).toHaveSize(0)
        expect(instance._isAppended).toBeFalse()
        instance.show(() => {
          instance.hide(() => {
            expect(getElements()).toHaveSize(0)
            expect(spy).not.toHaveBeenCalled()
            expect(instance._isAppended).toBeFalse()
            resolve()
          })
        })
      })
    })

    it('should not error if the backdrop no longer has a parent', () => {
      return new Promise(resolve => {
        fixtureEl.innerHTML = '<div id="wrapper"></div>'

        const wrapper = fixtureEl.querySelector('#wrapper')
        const instance = new Backdrop({
          isVisible: true,
          isAnimated: true,
          rootElement: wrapper
        })

        const getElements = () => document.querySelectorAll(CLASS_BACKDROP)

        instance.show(() => {
          wrapper.remove()
          instance.hide(() => {
            expect(getElements()).toHaveSize(0)
            resolve()
          })
        })
      })
    })
  })

  describe('click callback', () => {
    it('should execute callback on click', () => {
      return new Promise(resolve => {
        const spy = jasmine.createSpy('spy')

        const instance = new Backdrop({
          isVisible: true,
          isAnimated: false,
          clickCallback: () => spy()
        })
        const endTest = () => {
          setTimeout(() => {
            expect(spy).toHaveBeenCalled()
            resolve()
          }, 10)
        }

        instance.show(() => {
          const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
          document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent)
          endTest()
        })
      })
    })

    describe('animation callbacks', () => {
      it('should show and hide backdrop after counting transition duration if it is animated', () => {
        return new Promise(resolve => {
          const instance = new Backdrop({
            isVisible: true,
            isAnimated: true
          })
          const spy2 = jasmine.createSpy('spy2')

          const execDone = () => {
            setTimeout(() => {
              expect(spy2).toHaveBeenCalledTimes(2)
              resolve()
            }, 10)
          }

          instance.show(spy2)
          instance.hide(() => {
            spy2()
            execDone()
          })
          expect(spy2).not.toHaveBeenCalled()
        })
      })

      it('should show and hide backdrop without a delay if it is not animated', () => {
        return new Promise(resolve => {
          const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
          const instance = new Backdrop({
            isVisible: true,
            isAnimated: false
          })
          const spy2 = jasmine.createSpy('spy2')

          instance.show(spy2)
          instance.hide(spy2)

          setTimeout(() => {
            expect(spy2).toHaveBeenCalled()
            expect(spy).not.toHaveBeenCalled()
            resolve()
          }, 10)
        })
      })

      it('should not call delay callbacks if it is not "shown"', () => {
        return new Promise(resolve => {
          const instance = new Backdrop({
            isVisible: false,
            isAnimated: true
          })
          const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)

          instance.show()
          instance.hide(() => {
            expect(spy).not.toHaveBeenCalled()
            resolve()
          })
        })
      })
    })

    describe('Config', () => {
      describe('rootElement initialization', () => {
        it('should be appended on "document.body" by default', () => {
          return new Promise(resolve => {
            const instance = new Backdrop({
              isVisible: true
            })
            const getElement = () => document.querySelector(CLASS_BACKDROP)
            instance.show(() => {
              expect(getElement().parentElement).toEqual(document.body)
              resolve()
            })
          })
        })

        it('should find the rootElement if passed as a string', () => {
          return new Promise(resolve => {
            const instance = new Backdrop({
              isVisible: true,
              rootElement: 'body'
            })
            const getElement = () => document.querySelector(CLASS_BACKDROP)
            instance.show(() => {
              expect(getElement().parentElement).toEqual(document.body)
              resolve()
            })
          })
        })

        it('should be appended on any element given by the proper config', () => {
          return new Promise(resolve => {
            fixtureEl.innerHTML = '<div id="wrapper"></div>'

            const wrapper = fixtureEl.querySelector('#wrapper')
            const instance = new Backdrop({
              isVisible: true,
              rootElement: wrapper
            })
            const getElement = () => document.querySelector(CLASS_BACKDROP)
            instance.show(() => {
              expect(getElement().parentElement).toEqual(wrapper)
              resolve()
            })
          })
        })
      })

      describe('ClassName', () => {
        it('should allow configuring className', () => {
          return new Promise(resolve => {
            const instance = new Backdrop({
              isVisible: true,
              className: 'foo'
            })
            const getElement = () => document.querySelector('.foo')
            instance.show(() => {
              expect(getElement()).toEqual(instance._getElement())
              instance.dispose()
              resolve()
            })
          })
        })
      })
    })
  })
})
