Repository: vuejs/vue
Pull request: fix mixin issue(fix vuejs/vue-class-component#87)

<!--
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)
- [] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

**Other information:**
This patch fixes vuejs/vue-class-component#87 issue.
This includes following changes:
* Fixing the problem that Mixing feature drops original lifecycle hooks.
* Its test code.

In my environment, some e2e tests failed. But these tests also fail without my modifications.

Changed files:
filename: src/core/instance/init.js
patch: @@ -115,24 +115,27 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
 function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
   let modified
   const latest = Ctor.options
+  const extended = Ctor.extendOptions
   const sealed = Ctor.sealedOptions
   for (const key in latest) {
     if (latest[key] !== sealed[key]) {
       if (!modified) modified = {}
-      modified[key] = dedupe(latest[key], sealed[key])
+      modified[key] = dedupe(latest[key], extended[key], sealed[key])
     }
   }
   return modified
 }
 
-function dedupe (latest, sealed) {
+function dedupe (latest, extended, sealed) {
   // compare latest and sealed to ensure lifecycle hooks won't be duplicated
   // between merges
   if (Array.isArray(latest)) {
     const res = []
     sealed = Array.isArray(sealed) ? sealed : [sealed]
+    extended = Array.isArray(extended) ? extended : [extended]
     for (let i = 0; i < latest.length; i++) {
-      if (sealed.indexOf(latest[i]) < 0) {
+      // push original options and not sealed options to exclude duplicated options
+      if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
         res.push(latest[i])
       }
     }

filename: test/unit/features/global-api/mixin.spec.js
patch: @@ -141,4 +141,25 @@ describe('Global API: mixin', () => {
     })
     expect(spy).toHaveBeenCalledWith('hello')
   })
+
+  // vue-class-component#87
+  it('should not drop original lifecycle hooks', () => {
+    const base = jasmine.createSpy('base')
+
+    const Base = Vue.extend({
+      beforeCreate: base
+    })
+
+    const injected = jasmine.createSpy('injected')
+
+    // inject a function
+    Base.options.beforeCreate = Base.options.beforeCreate.concat(injected)
+
+    Vue.mixin({})
+
+    new Base({})
+
+    expect(base).toHaveBeenCalled()
+    expect(injected).toHaveBeenCalled()
+  })
 })