]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): support client-compiled v-model with dynamic type during ssr (#5787)
authorRoan Kattouw <roan.kattouw@gmail.com>
Tue, 17 May 2022 09:52:44 +0000 (02:52 -0700)
committerGitHub <noreply@github.com>
Tue, 17 May 2022 09:52:44 +0000 (05:52 -0400)
fix #5786

packages/runtime-dom/src/directives/vModel.ts
packages/server-renderer/__tests__/ssrDirectives.spec.ts

index 1a14decd06ac9a51e71b64f3cc22f3419365a585..722b4d9b44cd596a41b3cadc093c9c6dea262a2d 100644 (file)
@@ -269,33 +269,35 @@ export const vModelDynamic: ObjectDirective<
   }
 }
 
-function callModelHook(
-  el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
-  binding: DirectiveBinding,
-  vnode: VNode,
-  prevVNode: VNode | null,
-  hook: keyof ObjectDirective
-) {
-  let modelToUse: ObjectDirective
-  switch (el.tagName) {
+function resolveDynamicModel(tagName: string, type: string | undefined) {
+  switch (tagName) {
     case 'SELECT':
-      modelToUse = vModelSelect
-      break
+      return vModelSelect
     case 'TEXTAREA':
-      modelToUse = vModelText
-      break
+      return vModelText
     default:
-      switch (vnode.props && vnode.props.type) {
+      switch (type) {
         case 'checkbox':
-          modelToUse = vModelCheckbox
-          break
+          return vModelCheckbox
         case 'radio':
-          modelToUse = vModelRadio
-          break
+          return vModelRadio
         default:
-          modelToUse = vModelText
+          return vModelText
       }
   }
+}
+
+function callModelHook(
+  el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
+  binding: DirectiveBinding,
+  vnode: VNode,
+  prevVNode: VNode | null,
+  hook: keyof ObjectDirective
+) {
+  const modelToUse = resolveDynamicModel(
+    el.tagName,
+    vnode.props && vnode.props.type
+  )
   const fn = modelToUse[hook] as DirectiveHook
   fn && fn(el, binding, vnode, prevVNode)
 }
@@ -324,4 +326,18 @@ export function initVModelForSSR() {
       return { checked: true }
     }
   }
+
+  vModelDynamic.getSSRProps = (binding, vnode) => {
+    if (typeof vnode.type !== 'string') {
+      return
+    }
+    const modelToUse = resolveDynamicModel(
+      // resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase
+      vnode.type.toUpperCase(),
+      vnode.props && vnode.props.type
+    )
+    if (modelToUse.getSSRProps) {
+      return modelToUse.getSSRProps(binding, vnode)
+    }
+  }
 }
index 3e8bd2e0f67905989bc941e992f266d68c208c9a..74b01204d3183a79a463a2b5e0251c4f52311481 100644 (file)
@@ -11,6 +11,7 @@ import {
   vModelText,
   vModelRadio,
   vModelCheckbox,
+  vModelDynamic,
   resolveDirective
 } from 'vue'
 import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'
@@ -376,6 +377,100 @@ describe('ssr: directives', () => {
     })
   })
 
+  describe('vnode v-model dynamic', () => {
+    test('text', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(h('input'), [[vModelDynamic, 'hello']])
+            }
+          })
+        )
+      ).toBe(`<input value="hello">`)
+    })
+
+    test('radio', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(
+                h('input', { type: 'radio', value: 'hello' }),
+                [[vModelDynamic, 'hello']]
+              )
+            }
+          })
+        )
+      ).toBe(`<input type="radio" value="hello" checked>`)
+
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(
+                h('input', { type: 'radio', value: 'hello' }),
+                [[vModelDynamic, 'foo']]
+              )
+            }
+          })
+        )
+      ).toBe(`<input type="radio" value="hello">`)
+    })
+
+    test('checkbox', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(h('input', { type: 'checkbox' }), [
+                [vModelDynamic, true]
+              ])
+            }
+          })
+        )
+      ).toBe(`<input type="checkbox" checked>`)
+
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(h('input', { type: 'checkbox' }), [
+                [vModelDynamic, false]
+              ])
+            }
+          })
+        )
+      ).toBe(`<input type="checkbox">`)
+
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(
+                h('input', { type: 'checkbox', value: 'foo' }),
+                [[vModelDynamic, ['foo']]]
+              )
+            }
+          })
+        )
+      ).toBe(`<input type="checkbox" value="foo" checked>`)
+
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return withDirectives(
+                h('input', { type: 'checkbox', value: 'foo' }),
+                [[vModelDynamic, []]]
+              )
+            }
+          })
+        )
+      ).toBe(`<input type="checkbox" value="foo">`)
+    })
+  })
+
   test('custom directive w/ getSSRProps (vdom)', async () => {
     expect(
       await renderToString(