import Vue from 'vue'
import { isFunction } from 'core/util'

function assertClass(assertions, done) {
  const vm = new Vue({
    template: '<div class="foo" :class="value"></div>',
    data: { value: '' }
  }).$mount()
  const chain = waitForUpdate()
  assertions.forEach(([value, expected], i) => {
    chain
      .then(() => {
        if (isFunction(value)) {
          value(vm.value)
        } else {
          vm.value = value
        }
      })
      .then(() => {
        expect(vm.$el.className).toBe(expected)
        // NOTE THIS WAS MAKING
        // if (i >= assertions.length - 1) {
        //   done()
        // }
      })
  })
  chain.then(done)
}

describe('Directive v-bind:class', () => {
  it('plain string', done => {
    assertClass(
      [
        ['bar', 'foo bar'],
        ['baz qux', 'foo baz qux'],
        ['qux', 'foo qux'],
        [undefined, 'foo']
      ],
      done
    )
  })

  it('object value', done => {
    assertClass(
      [
        [{ bar: true, baz: false }, 'foo bar'],
        [{ baz: true }, 'foo baz'],
        [null, 'foo'],
        [{ 'bar baz': true, qux: false }, 'foo bar baz'],
        [{ qux: true }, 'foo qux']
      ],
      done
    )
  })

  it('array value', done => {
    assertClass(
      [
        [['bar', 'baz'], 'foo bar baz'],
        [['qux', 'baz'], 'foo qux baz'],
        [['w', 'x y z'], 'foo w x y z'],
        [undefined, 'foo'],
        [['bar'], 'foo bar'],
        [val => val.push('baz'), 'foo bar baz']
      ],
      done
    )
  })

  it('array of mixed values', done => {
    assertClass(
      [
        [['x', { y: true, z: true }], 'foo x y z'],
        [['x', { y: true, z: false }], 'foo x y'],
        [['f', { z: true }], 'foo f z'],
        [['l', 'f', { n: true, z: true }], 'foo l f n z'],
        [['x', {}], 'foo x'],
        [undefined, 'foo']
      ],
      done
    )
  })

  it('class merge between parent and child', done => {
    const vm = new Vue({
      template: '<child class="a" :class="value"></child>',
      data: { value: 'b' },
      components: {
        child: {
          template: '<div class="c" :class="value"></div>',
          data: () => ({ value: 'd' })
        }
      }
    }).$mount()
    const child = vm.$children[0]
    expect(vm.$el.className).toBe('c a d b')
    vm.value = 'e'
    waitForUpdate(() => {
      expect(vm.$el.className).toBe('c a d e')
    })
      .then(() => {
        child.value = 'f'
      })
      .then(() => {
        expect(vm.$el.className).toBe('c a f e')
      })
      .then(() => {
        vm.value = { foo: true }
        child.value = ['bar', 'baz']
      })
      .then(() => {
        expect(vm.$el.className).toBe('c a bar baz foo')
      })
      .then(done)
  })

  it('class merge between multiple nested components sharing same element', done => {
    const vm = new Vue({
      template: `
        <component1 :class="componentClass1">
          <component2 :class="componentClass2">
            <component3 :class="componentClass3">
              some text
            </component3>
          </component2>
        </component1>
      `,
      data: {
        componentClass1: 'componentClass1',
        componentClass2: 'componentClass2',
        componentClass3: 'componentClass3'
      },
      components: {
        component1: {
          render() {
            return this.$slots.default[0]
          }
        },
        component2: {
          render() {
            return this.$slots.default[0]
          }
        },
        component3: {
          template: '<div class="staticClass"><slot></slot></div>'
        }
      }
    }).$mount()
    expect(vm.$el.className).toBe(
      'staticClass componentClass3 componentClass2 componentClass1'
    )
    vm.componentClass1 = 'c1'
    waitForUpdate(() => {
      expect(vm.$el.className).toBe(
        'staticClass componentClass3 componentClass2 c1'
      )
      vm.componentClass2 = 'c2'
    })
      .then(() => {
        expect(vm.$el.className).toBe('staticClass componentClass3 c2 c1')
        vm.componentClass3 = 'c3'
      })
      .then(() => {
        expect(vm.$el.className).toBe('staticClass c3 c2 c1')
      })
      .then(done)
  })

  it('deep update', done => {
    const vm = new Vue({
      template: '<div :class="test"></div>',
      data: {
        test: { a: true, b: false }
      }
    }).$mount()
    expect(vm.$el.className).toBe('a')
    vm.test.b = true
    waitForUpdate(() => {
      expect(vm.$el.className).toBe('a b')
    }).then(done)
  })

  // css static classes should only contain a single space in between,
  // as all the text inside of classes is shipped as a JS string
  // and this could lead to useless spacing in static classes
  it('condenses whitespace in staticClass', done => {
    const vm = new Vue({
      template:
        '<div class=" test1\ntest2\ttest3 test4   test5 \n \n \ntest6\t"></div>'
    }).$mount()
    expect(vm.$el.className).toBe('test1 test2 test3 test4 test5 test6')
    done()
  })

  it('condenses whitespace in staticClass merge in a component', done => {
    const vm = new Vue({
      template: `
        <component1 class="\n\t staticClass \t\n" :class="componentClass1">
        </component1>
      `,
      data: {
        componentClass1: 'componentClass1'
      },
      components: {
        component1: {
          template: '<div class="\n\t test \t\n"></div>'
        }
      }
    }).$mount()
    expect(vm.$el.className).toBe('test staticClass componentClass1')
    vm.componentClass1 = 'c1'
    waitForUpdate(() => {
      expect(vm.$el.className).toBe('test staticClass c1')
    }).then(done)
  })

  // a vdom patch edge case where the user has several un-keyed elements of the
  // same tag next to each other, and toggling them.
  it('properly remove staticClass for toggling un-keyed children', done => {
    const vm = new Vue({
      template: `
        <div>
          <div v-if="ok" class="a"></div>
          <div v-if="!ok"></div>
        </div>
      `,
      data: {
        ok: true
      }
    }).$mount()
    expect(vm.$el.children[0].className).toBe('a')
    vm.ok = false
    waitForUpdate(() => {
      expect(vm.$el.children[0].className).toBe('')
    }).then(done)
  })
})