]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): render correct initial selected state for select with v-model (#7432)
authoredison <daiwei521@126.com>
Tue, 11 Jul 2023 09:13:18 +0000 (17:13 +0800)
committerGitHub <noreply@github.com>
Tue, 11 Jul 2023 09:13:18 +0000 (17:13 +0800)
close #7392

packages/compiler-ssr/__tests__/ssrVModel.spec.ts
packages/compiler-ssr/src/transforms/ssrVModel.ts
packages/server-renderer/__tests__/ssrDirectives.spec.ts

index 3411aa291c9c5ef0e68cb39b8cb060d786a74046..5bccbcb788c13a5fa38a94358dc1d698488e4cd2 100644 (file)
@@ -33,6 +33,44 @@ describe('ssr: v-model', () => {
       `)
   })
 
+  test('<select v-model>', () => {
+    expect(
+      compileWithWrapper(
+        `<select v-model="model"><option value="1"></option></select>`
+      ).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><select><option value=\\"1\\"\${
+          (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+            ? _ssrLooseContain(_ctx.model, \\"1\\")
+            : _ssrLooseEqual(_ctx.model, \\"1\\"))) ? \\" selected\\" : \\"\\"
+        }></option></select></div>\`)
+      }"
+    `)
+
+    expect(
+      compileWithWrapper(
+        `<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`
+      ).code
+    ).toMatchInlineSnapshot(`
+      "const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+
+      return function ssrRender(_ctx, _push, _parent, _attrs) {
+        _push(\`<div\${
+          _ssrRenderAttrs(_attrs)
+        }><select multiple><option value=\\"1\\" selected></option><option value=\\"2\\"\${
+          (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+            ? _ssrLooseContain(_ctx.model, \\"2\\")
+            : _ssrLooseEqual(_ctx.model, \\"2\\"))) ? \\" selected\\" : \\"\\"
+        }></option></select></div>\`)
+      }"
+    `)
+  })
+
   test('<input type="radio">', () => {
     expect(
       compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
index 589ef6a8650f32e2160cf012267b387ae59579e6..bd587edcb9cab664041c74f37ad4fbfd57a73686 100644 (file)
@@ -18,7 +18,8 @@ import {
 import {
   SSR_LOOSE_EQUAL,
   SSR_LOOSE_CONTAIN,
-  SSR_RENDER_DYNAMIC_MODEL
+  SSR_RENDER_DYNAMIC_MODEL,
+  SSR_INCLUDE_BOOLEAN_ATTR
 } from '../runtimeHelpers'
 import { DirectiveTransformResult } from 'packages/compiler-core/src/transform'
 
@@ -129,8 +130,34 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
       checkDuplicatedValue()
       node.children = [createInterpolation(model, model.loc)]
     } else if (node.tag === 'select') {
-      // NOOP
-      // select relies on client-side directive to set initial selected state.
+      node.children.forEach(option => {
+        if (option.type === NodeTypes.ELEMENT) {
+          const plainNode = option as PlainElementNode
+          if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
+            const value = findValueBinding(plainNode)
+            plainNode.ssrCodegenNode!.elements.push(
+              createConditionalExpression(
+                createCallExpression(context.helper(SSR_INCLUDE_BOOLEAN_ATTR), [
+                  createConditionalExpression(
+                    createCallExpression(`Array.isArray`, [model]),
+                    createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
+                      model,
+                      value
+                    ]),
+                    createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
+                      model,
+                      value
+                    ])
+                  )
+                ]),
+                createSimpleExpression(' selected', true),
+                createSimpleExpression('', true),
+                false /* no newline */
+              )
+            )
+          }
+        }
+      })
     } else {
       context.onError(
         createDOMCompilerError(
index 102e95d5b27bdbd16a8272b56737d82d3ee557b9..e52ef2db69f38d0366ae0478c55a890860bac139 100644 (file)
@@ -107,6 +107,30 @@ describe('ssr: directives', () => {
       ).toBe(`<input type="radio">`)
     })
 
+    test('select', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            data: () => ({ model: 1 }),
+            template: `<select v-model="model"><option value="0"></option><option value="1"></option></select>`
+          })
+        )
+      ).toBe(
+        `<select><option value="0"></option><option value="1" selected></option></select>`
+      )
+
+      expect(
+        await renderToString(
+          createApp({
+            data: () => ({ model: [0, 1] }),
+            template: `<select multiple v-model="model"><option value="0"></option><option value="1"></option></select>`
+          })
+        )
+      ).toBe(
+        `<select multiple><option value="0" selected></option><option value="1" selected></option></select>`
+      )
+    })
+
     test('checkbox', async () => {
       expect(
         await renderToString(