import Router from '../../../src/index'
import Vue from 'vue'

describe('router.onReady', () => {
  it('should work', done => {
    const calls = []

    const router = new Router({
      mode: 'abstract',
      routes: [
        {
          path: '/a',
          component: {
            name: 'A',
            beforeRouteEnter: (to, from, next) => {
              setTimeout(() => {
                calls.push(2)
                next()
              }, 1)
            }
          }
        }
      ]
    })

    router.beforeEach((to, from, next) => {
      setTimeout(() => {
        calls.push(1)
        next()
      }, 1)
    })

    router.onReady(() => {
      expect(calls).toEqual([1, 2])
      // sync call when already ready
      router.onReady(() => {
        calls.push(3)
      })
      expect(calls).toEqual([1, 2, 3])
      done()
    })

    router.push('/a')
    expect(calls).toEqual([])
  })
})

describe('route matching', () => {
  it('resolves parent params when using current route', () => {
    const router = new Router({
      mode: 'abstract',
      routes: [
        {
          path: '/a/:id',
          component: { name: 'A' },
          children: [{ name: 'b', path: 'b', component: { name: 'B' }}]
        }
      ]
    })

    router.push('/a/1')

    const { route, resolved } = router.resolve({ name: 'b' })
    expect(route.params).toEqual({ id: '1' })
    expect(resolved.params).toEqual({ id: '1' })
  })

  it('can override currentRoute', () => {
    const router = new Router({
      mode: 'abstract',
      routes: [
        {
          path: '/a/:id',
          component: { name: 'A' },
          children: [{ name: 'b', path: 'b', component: { name: 'B' }}]
        }
      ]
    })

    router.push('/a/1')

    const { route, resolved } = router.resolve({ name: 'b' }, { params: { id: '2' }, path: '/a/2' })
    expect(route.params).toEqual({ id: '2' })
    expect(resolved.params).toEqual({ id: '2' })
  })
})

describe('router.addRoutes', () => {
  it('should work', () => {
    const router = new Router({
      mode: 'abstract',
      routes: [
        { path: '/a', component: { name: 'A' }}
      ]
    })

    router.push('/a')
    let components = router.getMatchedComponents()
    expect(components.length).toBe(1)
    expect(components[0].name).toBe('A')

    router.push('/b')
    components = router.getMatchedComponents()
    expect(components.length).toBe(0)

    router.addRoutes([
      { path: '/b', component: { name: 'B' }}
    ])
    components = router.getMatchedComponents()
    expect(components.length).toBe(1)
    expect(components[0].name).toBe('B')

    // make sure it preserves previous routes
    router.push('/a')
    components = router.getMatchedComponents()
    expect(components.length).toBe(1)
    expect(components[0].name).toBe('A')
  })

  it('allows navigating to the same as current location', () => {
    const router = new Router({
      routes: [
        { path: '/', component: {}},
        { path: '*', component: {}}
      ]
    })

    router.push('/not-found')

    expect(router.currentRoute.params).toEqual({ pathMatch: '/not-found' })
    router.addRoutes([{ path: '/not-found', component: {}}])

    // the navigation should have changed locations
    expect(router.currentRoute.params).toEqual({})
  })
})

describe('router.push/replace', () => {
  let calls = []
  let router, spy1, spy2

  const Foo = {
    beforeRouteEnter (to, from, next) {
      calls.push(3)
      setTimeout(() => {
        calls.push(4)
        next()
      }, 1)
    }
  }

  beforeEach(() => {
    calls = []
    spy1 = jasmine.createSpy('complete')
    spy2 = jasmine.createSpy('abort')

    router = new Router({
      routes: [
        { path: '/foo', component: Foo }
      ]
    })

    router.beforeEach((to, from, next) => {
      calls.push(1)
      setTimeout(() => {
        calls.push(2)
        next()
      }, 1)
    })
  })
  describe('callbacks', () => {
    it('push does not return a Promise when a callback is passed', done => {
      expect(router.push('/foo', done)).toEqual(undefined)
    })

    it('push complete', done => {
      router.push('/foo', () => {
        expect(calls).toEqual([1, 2, 3, 4])
        done()
      })
    })

    it('push abort', done => {
      router.push('/foo', spy1, spy2)
      router.push('/bar', () => {
        expect(calls).toEqual([1, 1, 2, 2])
        expect(spy1).not.toHaveBeenCalled()
        expect(spy2).toHaveBeenCalled()
        done()
      })
    })

    it('replace does not return a Promise when a callback is passed', done => {
      expect(router.replace('/foo', done)).toEqual(undefined)
    })

    it('replace complete', done => {
      router.replace('/foo', () => {
        expect(calls).toEqual([1, 2, 3, 4])
        done()
      })
    })

    it('replace abort', done => {
      router.replace('/foo', spy1, spy2)
      router.replace('/bar', () => {
        expect(calls).toEqual([1, 1, 2, 2])
        expect(spy1).not.toHaveBeenCalled()
        expect(spy2).toHaveBeenCalled()
        done()
      })
    })
  })

  describe('promises', () => {
    it('push complete', done => {
      router.push('/foo')
        .then(spy1)
        .finally(() => {
          expect(calls).toEqual([1, 2, 3, 4])
          expect(spy1).toHaveBeenCalledWith(router.currentRoute)
          done()
        })
    })

    it('push abort', done => {
      router.push('/foo').catch(spy2)
      router.push('/bar').finally(() => {
        expect(calls).toEqual([1, 1, 2, 2])
        expect(spy1).not.toHaveBeenCalled()
        expect(spy2).toHaveBeenCalled()
        done()
      })
    })

    it('replace complete', done => {
      router.replace('/foo')
        .then(spy1)
        .finally(() => {
          expect(calls).toEqual([1, 2, 3, 4])
          expect(spy1).toHaveBeenCalledWith(router.currentRoute)
          done()
        })
    })

    it('replace abort', done => {
      router.replace('/foo').catch(spy2)
      router.replace('/bar').finally(() => {
        expect(calls).toEqual([1, 1, 2, 2])
        expect(spy1).not.toHaveBeenCalled()
        expect(spy2).toHaveBeenCalled()
        done()
      })
    })
  })
})

describe('router app destroy handling', () => {
  Vue.use(Router)

  let router, app1, app2, app3

  beforeEach(() => {
    router = new Router({
      mode: 'abstract',
      routes: [
        { path: '/', component: { name: 'A' }}
      ]
    })

    // Add main app
    app1 = new Vue({
      router,
      render (h) { return h('div') }
    })

    // Add 2nd app
    app2 = new Vue({
      router,
      render (h) { return h('div') }
    })

    // Add 3rd app
    app3 = new Vue({
      router,
      render (h) { return h('div') }
    })
  })

  it('all apps point to the same router instance', () => {
    expect(app1.$router).toBe(app2.$router)
    expect(app2.$router).toBe(app3.$router)
  })

  it('should have all 3 registered apps', () => {
    expect(app1.$router.app).toBe(app1)
    expect(app1.$router.apps.length).toBe(3)
    expect(app1.$router.apps[0]).toBe(app1)
    expect(app1.$router.apps[1]).toBe(app2)
    expect(app1.$router.apps[2]).toBe(app3)
  })

  it('should remove 2nd destroyed app from this.apps', () => {
    app2.$destroy()
    expect(app1.$router.app).toBe(app1)
    expect(app1.$router.apps.length).toBe(2)
    expect(app1.$router.apps[0]).toBe(app1)
    expect(app1.$router.apps[1]).toBe(app3)
  })

  it('should remove 1st destroyed app and replace current app', () => {
    app1.$destroy()
    expect(app3.$router.app).toBe(app2)
    expect(app3.$router.apps.length).toBe(2)
    expect(app3.$router.apps[0]).toBe(app2)
    expect(app1.$router.apps[1]).toBe(app3)
  })

  it('should remove all apps', () => {
    app1.$destroy()
    app3.$destroy()
    app2.$destroy()
    expect(app3.$router.app).toBe(null)
    expect(app3.$router.apps.length).toBe(0)
  })

  it('should keep current app if already defined', () => {
    const app4 = new Vue({
      router,
      render (h) { return h('div') }
    })
    expect(app4.$router.app).toBe(app1)
    expect(app4.$router.apps.length).toBe(4)
    expect(app4.$router.apps[3]).toBe(app4)
  })

  it('should replace current app if none is assigned when creating the app', () => {
    app1.$destroy()
    app3.$destroy()
    app2.$destroy()
    const app4 = new Vue({
      router,
      render (h) { return h('div') }
    })
    expect(router.app).toBe(app4)
    expect(app4.$router).toBe(router)
    expect(app4.$router.apps.length).toBe(1)
    expect(app4.$router.apps[0]).toBe(app4)
  })
})
