Repository: vuejs/vue
Pull request: fix(model): correctly set activeindex of select element in a new v-model

https://github.com/vuejs/vue/issues/6903

<!--
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/platforms/web/runtime/directives/model.js
patch: @@ -6,6 +6,8 @@
 import { isTextInputType } from 'web/util/element'
 import { looseEqual, looseIndexOf } from 'shared/util'
 import { warn, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'
+import { mergeVNodeHook } from 'core/vdom/helpers/index'
+import { emptyNode } from 'core/vdom/patch'
 
 /* istanbul ignore if */
 if (isIE9) {
@@ -18,10 +20,17 @@ if (isIE9) {
   })
 }
 
-export default {
-  inserted (el, binding, vnode) {
+const directive = {
+  inserted (el, binding, vnode, oldVnode) {
     if (vnode.tag === 'select') {
-      setSelected(el, binding, vnode.context)
+      // #6903
+      if (oldVnode !== emptyNode && !hasDirective(oldVnode, 'model')) {
+        mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', () => {
+          directive.componentUpdated(el, binding, vnode)
+        })
+      } else {
+        setSelected(el, binding, vnode.context)
+      }
       el._vOptions = [].map.call(el.options, getValue)
     } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
       el._vModifiers = binding.modifiers
@@ -136,3 +145,11 @@ function trigger (el, type) {
   e.initEvent(type, true, true)
   el.dispatchEvent(e)
 }
+
+function hasDirective (vnode, dirname) {
+  return vnode.data &&
+    vnode.data.directives &&
+    vnode.data.directives.some(dir => dir.name === dirname)
+}
+
+export default directive

filename: test/unit/features/directives/model-select.spec.js
patch: @@ -546,4 +546,46 @@ describe('Directive v-model select', () => {
       expect(spy).not.toHaveBeenCalled()
     }).then(done)
   })
+
+  // #6903
+  describe('should correctly handle v-model when the vnodes are the same', () => {
+    function makeInstance (foo) {
+      return new Vue({
+        data: {
+          foo: foo,
+          options: ['b', 'c', 'd'],
+          value: 'c'
+        },
+        template:
+          '<div>' +
+            '<select v-if="foo" data-attr>' +
+              '<option selected>a</option>' +
+            '</select>' +
+            '<select v-else v-model="value">' +
+              '<option v-for="option in options" :value="option">{{ option }}</option>' +
+            '</select>' +
+          '</div>'
+      }).$mount()
+    }
+
+    it('register v-model', done => {
+      const vm = makeInstance(true)
+
+      expect(vm.$el.firstChild.selectedIndex).toBe(0)
+      vm.foo = false
+      waitForUpdate(() => {
+        expect(vm.$el.firstChild.selectedIndex).toBe(1)
+      }).then(done)
+    })
+
+    it('remove v-model', done => {
+      const vm = makeInstance(false)
+
+      expect(vm.$el.firstChild.selectedIndex).toBe(1)
+      vm.foo = true
+      waitForUpdate(() => {
+        expect(vm.$el.firstChild.selectedIndex).toBe(0)
+      }).then(done)
+    })
+  })
 })