import Vue from 'vue'

function checkPrefixedProp(prop) {
  const el = document.createElement('div')
  const upper = prop.charAt(0).toUpperCase() + prop.slice(1)
  if (!(prop in el.style)) {
    const prefixes = ['Webkit', 'Moz', 'ms']
    let i = prefixes.length
    while (i--) {
      if (prefixes[i] + upper in el.style) {
        prop = prefixes[i] + upper
      }
    }
  }
  return prop
}

describe('Directive v-bind:style', () => {
  let vm

  beforeEach(() => {
    vm = new Vue({
      template: '<div :style="styles"></div>',
      data() {
        return {
          styles: {},
          fontSize: 16
        }
      }
    }).$mount()
  })

  it('string', done => {
    vm.styles = 'color:red;'
    waitForUpdate(() => {
      expect(vm.$el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
    }).then(done)
  })

  it('falsy number', done => {
    vm.styles = { opacity: 0 }
    waitForUpdate(() => {
      expect(vm.$el.style.opacity).toBe('0')
    }).then(done)
  })

  it('plain object', done => {
    vm.styles = { color: 'red' }
    waitForUpdate(() => {
      expect(vm.$el.style.cssText.replace(/\s/g, '')).toBe('color:red;')
    }).then(done)
  })

  it('camelCase', done => {
    vm.styles = { marginRight: '10px' }
    waitForUpdate(() => {
      expect(vm.$el.style.marginRight).toBe('10px')
    }).then(done)
  })

  it('remove if falsy value', done => {
    vm.$el.style.color = 'red'
    waitForUpdate(() => {
      vm.styles = { color: null }
    })
      .then(() => {
        expect(vm.$el.style.color).toBe('')
      })
      .then(done)
  })

  it('ignore unsupported property', done => {
    vm.styles = { foo: 'bar' }
    waitForUpdate(() => {
      expect(vm.$el.style.foo).not.toBe('bar')
    }).then(done)
  })

  it('auto prefix', done => {
    const prop = checkPrefixedProp('transform')
    const val = 'scale(0.5)'
    vm.styles = { transform: val }
    waitForUpdate(() => {
      expect(vm.$el.style[prop]).toBe(val)
    }).then(done)
  })

  it('auto-prefixed style value as array', done => {
    vm.styles = { display: ['-webkit-box', '-ms-flexbox', 'flex'] }
    const testEl = document.createElement('div')
    vm.styles.display.forEach(value => {
      testEl.style.display = value
    })
    waitForUpdate(() => {
      expect(vm.$el.style.display).toBe(testEl.style.display)
    }).then(done)
  })

  it('!important', done => {
    vm.styles = { display: 'block !important' }
    waitForUpdate(() => {
      expect(vm.$el.style.getPropertyPriority('display')).toBe('important')
    }).then(done)
  })

  it('camelCase with !important', done => {
    vm.styles = { zIndex: '100 !important' }
    waitForUpdate(() => {
      expect(vm.$el.style.getPropertyPriority('z-index')).toBe('important')
    }).then(done)
  })

  it('object with multiple entries', done => {
    vm.$el.style.color = 'red'
    vm.styles = {
      fontSize: '10px'
    }
    waitForUpdate(() => {
      expect(vm.$el.style.color).toBe('red')
      expect(vm.$el.style.fontSize).toBe('10px')
      expect(vm.$el.style.getPropertyValue('font-size')).toBe('10px')
      vm.styles = {
        color: 'blue',
        padding: null
      }
    })
      .then(() => {
        expect(vm.$el.style.color).toBe('blue')
        expect(vm.$el.style.padding).toBeFalsy()
        expect(vm.$el.style.fontSize).toBeFalsy()
        expect(vm.$el.style.getPropertyValue('font-size')).toBeFalsy()
        // handle falsy value
        vm.styles = null
      })
      .then(() => {
        expect(vm.$el.style.color).toBeFalsy()
        expect(vm.$el.style.padding).toBeFalsy()
        expect(vm.$el.style.fontSize).toBeFalsy()
        expect(vm.$el.style.getPropertyValue('font-size')).toBeFalsy()
      })
      .then(done)
  })

  it('array of objects', done => {
    vm.$el.style.padding = '10px'
    vm.styles = [{ color: 'red' }, { fontSize: '20px' }]

    waitForUpdate(() => {
      expect(vm.$el.style.color).toBe('red')
      expect(vm.$el.style.fontSize).toBe('20px')
      expect(vm.$el.style.padding).toBe('10px')
      vm.styles = [{ color: 'blue' }, { padding: null }]
    })
      .then(() => {
        expect(vm.$el.style.color).toBe('blue')
        expect(vm.$el.style.fontSize).toBeFalsy()
        expect(vm.$el.style.padding).toBeFalsy()
      })
      .then(done)
  })

  it('updates objects deeply', done => {
    vm.styles = { display: 'none' }
    waitForUpdate(() => {
      expect(vm.$el.style.display).toBe('none')
      vm.styles.display = 'block'
    })
      .then(() => {
        expect(vm.$el.style.display).toBe('block')
      })
      .then(done)
  })

  it('background size with only one value', done => {
    vm.styles = { backgroundSize: '100%' }
    waitForUpdate(() => {
      expect(vm.$el.style.cssText.replace(/\s/g, '')).toMatch(
        /background-size:100%(auto)?;/
      )
    }).then(done)
  })

  it('should work with interpolation', done => {
    vm.styles = { fontSize: `${vm.fontSize}px` }
    waitForUpdate(() => {
      expect(vm.$el.style.fontSize).toBe('16px')
    }).then(done)
  })

  const supportCssVariable = () => {
    const el = document.createElement('div')
    el.style.setProperty('--color', 'red')
    return el.style.getPropertyValue('--color') === 'red'
  }

  if (supportCssVariable()) {
    it('CSS variables', done => {
      vm.styles = { '--color': 'red' }
      waitForUpdate(() => {
        expect(vm.$el.style.getPropertyValue('--color')).toBe('red')
      }).then(done)
    })
  }

  it('should merge static style with binding style', () => {
    const vm = new Vue({
      template:
        '<div style="background: url(https://vuejs.org/images/logo.png);color: blue" :style="test"></div>',
      data: {
        test: { color: 'red', fontSize: '12px' }
      }
    }).$mount()
    const style = vm.$el.style
    expect(style.backgroundImage).toMatch('https://vuejs.org/images/logo.png')
    expect(style.color).toBe('red')
    expect(style.fontSize).toBe('12px')
  })

  it('should merge between parent and child', done => {
    const vm = new Vue({
      template:
        '<child style="text-align: left;margin-right:20px" :style="test"></child>',
      data: {
        test: { color: 'red', fontSize: '12px' }
      },
      components: {
        child: {
          template:
            '<div style="margin-right:10px;" :style="{marginLeft: marginLeft}"></div>',
          data: () => ({ marginLeft: '16px' })
        }
      }
    }).$mount()
    const style = vm.$el.style
    const child = vm.$children[0]
    const css = style.cssText.replace(/\s/g, '')
    expect(css).toContain('margin-right:20px;')
    expect(css).toContain('margin-left:16px;')
    expect(css).toContain('text-align:left;')
    expect(css).toContain('color:red;')
    expect(css).toContain('font-size:12px;')
    expect(style.color).toBe('red')
    expect(style.marginRight).toBe('20px')
    vm.test.color = 'blue'
    waitForUpdate(() => {
      expect(style.color).toBe('blue')
      child.marginLeft = '30px'
    })
      .then(() => {
        expect(style.marginLeft).toBe('30px')
        child.fontSize = '30px'
      })
      .then(() => {
        expect(style.fontSize).toBe('12px')
      })
      .then(done)
  })

  it('should not pass to child root element', () => {
    const vm = new Vue({
      template: '<child :style="test"></child>',
      data: {
        test: { color: 'red', fontSize: '12px' }
      },
      components: {
        child: {
          template:
            '<div><nested ref="nested" style="color: blue;text-align:left"></nested></div>',
          components: {
            nested: {
              template: '<div></div>'
            }
          }
        }
      }
    }).$mount()
    const style = vm.$el.style
    expect(style.color).toBe('red')
    expect(style.textAlign).toBe('')
    expect(style.fontSize).toBe('12px')
    expect(vm.$children[0].$refs.nested.$el.style.color).toBe('blue')
  })

  it('should merge between nested components', done => {
    const vm = new Vue({
      template: '<child :style="test"></child>',
      data: {
        test: { color: 'red', fontSize: '12px' }
      },
      components: {
        child: {
          template: '<nested style="color: blue;text-align:left"></nested>',
          components: {
            nested: {
              template:
                '<div style="margin-left: 12px;" :style="nestedStyle"></div>',
              data: () => ({ nestedStyle: { marginLeft: '30px' } })
            }
          }
        }
      }
    }).$mount()
    const style = vm.$el.style
    const child = vm.$children[0].$children[0]
    expect(style.color).toBe('red')
    expect(style.marginLeft).toBe('30px')
    expect(style.textAlign).toBe('left')
    expect(style.fontSize).toBe('12px')
    vm.test.color = 'yellow'
    waitForUpdate(() => {
      child.nestedStyle.marginLeft = '60px'
    })
      .then(() => {
        expect(style.marginLeft).toBe('60px')
        child.nestedStyle = {
          fontSize: '14px',
          marginLeft: '40px'
        }
      })
      .then(() => {
        expect(style.fontSize).toBe('12px')
        expect(style.marginLeft).toBe('40px')
      })
      .then(done)
  })

  it('should not merge for different adjacent elements', done => {
    const vm = new Vue({
      template:
        '<div>' +
        '<section style="color: blue" :style="style" v-if="!bool"></section>' +
        '<div></div>' +
        '<section style="margin-top: 12px" v-if="bool"></section>' +
        '</div>',
      data: {
        bool: false,
        style: {
          fontSize: '12px'
        }
      }
    }).$mount()
    const style = vm.$el.children[0].style
    expect(style.fontSize).toBe('12px')
    expect(style.color).toBe('blue')
    waitForUpdate(() => {
      vm.bool = true
    })
      .then(() => {
        expect(style.color).toBe('')
        expect(style.fontSize).toBe('')
        expect(style.marginTop).toBe('12px')
      })
      .then(done)
  })

  it('should not merge for v-if, v-else-if and v-else elements', done => {
    const vm = new Vue({
      template:
        '<div>' +
        '<section style="color: blue" :style="style" v-if="foo"></section>' +
        '<section style="margin: 12px" v-else-if="bar"></section>' +
        '<section style="padding: 24px" v-else></section>' +
        '<div></div>' +
        '</div>',
      data: {
        foo: true,
        bar: false,
        style: {
          fontSize: '12px'
        }
      }
    }).$mount()
    const style = vm.$el.children[0].style
    expect(style.fontSize).toBe('12px')
    expect(style.color).toBe('blue')
    waitForUpdate(() => {
      vm.foo = false
    })
      .then(() => {
        expect(style.color).toBe('')
        expect(style.fontSize).toBe('')
        expect(style.padding).toBe('24px')
        vm.bar = true
      })
      .then(() => {
        expect(style.color).toBe('')
        expect(style.fontSize).toBe('')
        expect(style.padding).toBe('')
        expect(style.margin).toBe('12px')
      })
      .then(done)
  })

  // #5318
  it('should work for elements passed down as a slot', done => {
    const vm = new Vue({
      template: `<test><div :style="style"/></test>`,
      data: {
        style: { color: 'red' }
      },
      components: {
        test: {
          template: `<div><slot/></div>`
        }
      }
    }).$mount()

    expect(vm.$el.children[0].style.color).toBe('red')
    vm.style.color = 'green'
    waitForUpdate(() => {
      expect(vm.$el.children[0].style.color).toBe('green')
    }).then(done)
  })
})