Repository: vuejs/vue
Pull request: Update loose equal function, check toString value for primitive type (fix #4514)

fix #4514 

Changed files:
filename: src/shared/util.js
patch: @@ -208,13 +208,15 @@ export function genStaticKeys (modules: Array<ModuleOptions>): string {
  * if they are plain objects, do they have the same shape?
  */
 export function looseEqual (a: mixed, b: mixed): boolean {
-  /* eslint-disable eqeqeq */
-  return a == b || (
-    isObject(a) && isObject(b)
-      ? JSON.stringify(a) === JSON.stringify(b)
-      : false
-  )
-  /* eslint-enable eqeqeq */
+  const isObjectA = isObject(a)
+  const isObjectB = isObject(b)
+  if (isObjectA && isObjectB) {
+    return JSON.stringify(a) === JSON.stringify(b)
+  } else if (!isObjectA && !isObjectB) {
+    return String(a) === String(b)
+  } else {
+    return false
+  }
 }
 
 export function looseIndexOf (arr: Array<mixed>, val: mixed): number {

filename: test/unit/features/directives/model-checkbox.spec.js
patch: @@ -159,6 +159,40 @@ describe('Directive v-model checkbox', () => {
     expect(vm.check).toEqual(false)
   })
 
+  it('should respect different primitive type value', (done) => {
+    const vm = new Vue({
+      data: {
+        test: [0]
+      },
+      template:
+        '<div>' +
+          '<input type="checkbox" value="" v-model="test">' +
+          '<input type="checkbox" value="0" v-model="test">' +
+          '<input type="checkbox" value="1" v-model="test">' +
+        '</div>'
+    }).$mount()
+    var checkboxInput = vm.$el.children
+    expect(checkboxInput[0].checked).toBe(false)
+    expect(checkboxInput[1].checked).toBe(true)
+    expect(checkboxInput[2].checked).toBe(false)
+    vm.test = [1]
+    waitForUpdate(() => {
+      expect(checkboxInput[0].checked).toBe(false)
+      expect(checkboxInput[1].checked).toBe(false)
+      expect(checkboxInput[2].checked).toBe(true)
+      vm.test = ['']
+    }).then(() => {
+      expect(checkboxInput[0].checked).toBe(true)
+      expect(checkboxInput[1].checked).toBe(false)
+      expect(checkboxInput[2].checked).toBe(false)
+      vm.test = ['', 0, 1]
+    }).then(() => {
+      expect(checkboxInput[0].checked).toBe(true)
+      expect(checkboxInput[1].checked).toBe(true)
+      expect(checkboxInput[2].checked).toBe(true)
+    }).then(done)
+  })
+
   it('warn inline checked', () => {
     const vm = new Vue({
       template: `<input type="checkbox" v-model="test" checked>`,

filename: test/unit/features/directives/model-radio.spec.js
patch: @@ -146,6 +146,35 @@ describe('Directive v-model radio', () => {
     expect(vm.test).toBe(2)
   })
 
+  it('should respect different primitive type value', (done) => {
+    const vm = new Vue({
+      data: {
+        test: 1
+      },
+      template:
+        '<div>' +
+          '<input type="radio" value="" v-model="test" name="test">' +
+          '<input type="radio" value="0" v-model="test" name="test">' +
+          '<input type="radio" value="1" v-model="test" name="test">' +
+        '</div>'
+    }).$mount()
+    var radioboxInput = vm.$el.children
+    expect(radioboxInput[0].checked).toBe(false)
+    expect(radioboxInput[1].checked).toBe(false)
+    expect(radioboxInput[2].checked).toBe(true)
+    vm.test = 0
+    waitForUpdate(() => {
+      expect(radioboxInput[0].checked).toBe(false)
+      expect(radioboxInput[1].checked).toBe(true)
+      expect(radioboxInput[2].checked).toBe(false)
+      vm.test = ''
+    }).then(() => {
+      expect(radioboxInput[0].checked).toBe(true)
+      expect(radioboxInput[1].checked).toBe(false)
+      expect(radioboxInput[2].checked).toBe(false)
+    }).then(done)
+  })
+
   it('warn inline checked', () => {
     const vm = new Vue({
       template: `<input v-model="test" type="radio" value="1" checked>`,

filename: test/unit/features/directives/model-select.spec.js
patch: @@ -310,7 +310,7 @@ describe('Directive v-model select', () => {
         '<select v-model.number="test">' +
           '<option value="1">a</option>' +
           '<option :value="2">b</option>' +
-        ' <option :value="3">c</option>' +
+          '<option :value="3">c</option>' +
         '</select>'
     }).$mount()
     document.body.appendChild(vm.$el)
@@ -319,6 +319,35 @@ describe('Directive v-model select', () => {
     expect(vm.test).toBe(1)
   })
 
+  it('should respect different pritive type value', (done) => {
+    const vm = new Vue({
+      data: {
+        test: 0
+      },
+      template:
+        '<select v-model.number="test">' +
+          '<option value="">a</option>' +
+          '<option value="0">b</option>' +
+          '<option value="1">c</option>' +
+        '</select>'
+    }).$mount()
+    var opts = vm.$el.options
+    expect(opts[0].selected).toBe(false)
+    expect(opts[1].selected).toBe(true)
+    expect(opts[2].selected).toBe(false)
+    vm.test = 1
+    waitForUpdate(() => {
+      expect(opts[0].selected).toBe(false)
+      expect(opts[1].selected).toBe(false)
+      expect(opts[2].selected).toBe(true)
+      vm.test = ''
+    }).then(() => {
+      expect(opts[0].selected).toBe(true)
+      expect(opts[1].selected).toBe(false)
+      expect(opts[2].selected).toBe(false)
+    }).then(done)
+  })
+
   it('should warn inline selected', () => {
     const vm = new Vue({
       data: {