Repository: vuejs/vue
Pull request: Added check in merge strat for watches if child is already an array (fix #5652)

When performing multiple extends/mixins watches are converted to arrays of handlers.
If the parent does not watch an element, but the children do, it will not run the parent.concat code and instead always wrap the child.
If the child is already an array it does not need to be wrapped again.
Fixing #5652

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**


Changed files:
filename: src/core/util/options.js
patch: @@ -172,7 +172,7 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
     }
     ret[key] = parent
       ? parent.concat(child)
-      : [child]
+      : Array.isArray(child) ? child : [child]
   }
   return ret
 }

filename: test/unit/features/options/extends.spec.js
patch: @@ -47,3 +47,44 @@ describe('Options extends', () => {
     expect(vm.c).toBe(3)
   })
 })
+
+describe('Options extends with Object.prototype.watch', () => {
+  beforeAll(function () {
+    if (!Object.prototype.watch) {
+      // eslint-disable-next-line no-extend-native
+      Object.prototype.watch = {
+        remove: true
+      }
+    }
+  })
+  afterAll(function () {
+    if (Object.prototype.watch && Object.prototype.watch.remove) {
+      delete Object.prototype.watch
+    }
+  })
+  it('should work with global mixins', done => {
+    Vue.use({
+      install: function () {
+        Vue.mixin({})
+      }
+    })
+    const spy = jasmine.createSpy('watch')
+    const A = Vue.extend({
+      data: function () {
+        return { a: 1 }
+      },
+      watch: {
+        a: spy
+      },
+      created: function () {
+        this.a = 2
+      }
+    })
+    new Vue({
+      extends: A
+    })
+    waitForUpdate(() => {
+      expect(spy).toHaveBeenCalledWith(2, 1)
+    }).then(done)
+  })
+})

filename: test/unit/features/options/watch.spec.js
patch: @@ -104,4 +104,43 @@ describe('Options watch', () => {
       expect(spy).toHaveBeenCalledWith(vm.a, oldA)
     }).then(done)
   })
+
+  it('correctly merges multiple extends', done => {
+    var spy2 = jasmine.createSpy('A')
+    var spy3 = jasmine.createSpy('B')
+    var A = Vue.extend({
+      data: function () {
+        return {
+          a: 0,
+          b: 0
+        }
+      },
+      watch: {
+        b: spy
+      }
+    })
+
+    var B = Vue.extend({
+      extends: A,
+      watch: {
+        a: spy2
+      }
+    })
+
+    var C = Vue.extend({
+      extends: B,
+      watch: {
+        a: spy3
+      }
+    })
+
+    var vm = new C()
+    vm.a = 1
+
+    waitForUpdate(() => {
+      expect(spy).not.toHaveBeenCalled()
+      expect(spy2).toHaveBeenCalledWith(1, 0)
+      expect(spy3).toHaveBeenCalledWith(1, 0)
+    }).then(done)
+  })
 })