Skip to content

Commit

Permalink
fix(ssr): render correct initial selected state for select with v-mod…
Browse files Browse the repository at this point in the history
…el (#7432)

close #7392
  • Loading branch information
edison1105 authored Jul 11, 2023
1 parent 3decc57 commit 201c46d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 3 deletions.
38 changes: 38 additions & 0 deletions packages/compiler-ssr/__tests__/ssrVModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 30 additions & 3 deletions packages/compiler-ssr/src/transforms/ssrVModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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(
Expand Down
24 changes: 24 additions & 0 deletions packages/server-renderer/__tests__/ssrDirectives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 201c46d

Please sign in to comment.