Skip to content

Commit 5e07023

Browse files
committed
fix(runtime-core): Select elements can preserve data types when stringified
1 parent c6e5bda commit 5e07023

File tree

5 files changed

+110
-2
lines changed

5 files changed

+110
-2
lines changed

packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`stringify static html Select elements can preserve data types when stringified 1`] = `
4+
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
5+
6+
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<option value=\\"string\\" v-stringify-type='string'>string</option><option value=\\"false\\" v-stringify-type='boolean'>boolean</option><option value=\\"1\\" v-stringify-type='number'>number 1</option><option value=\\"2\\" v-stringify-type='number'>number 2</option><option v-stringify-type='null'>null</option>", 5)
7+
8+
return function render(_ctx, _cache) {
9+
return _hoisted_1
10+
}"
11+
`;
12+
313
exports[`stringify static html should bail on bindings that are hoisted but not stringifiable 1`] = `
414
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
515
@@ -33,6 +43,16 @@ return function render(_ctx, _cache) {
3343
}"
3444
`;
3545
46+
exports[`stringify static html stringify #6568 1`] = `
47+
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
48+
49+
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<option value=\\"string\\" v-stringify-type='string'>string</option><option value=\\"false\\" v-stringify-type='boolean'>boolean</option><option value=\\"1\\" v-stringify-type='number'>number 1</option><option value=\\"2\\" v-stringify-type='number'>number 2</option><option v-stringify-type='null'>null</option>", 5)
50+
51+
return function render(_ctx, _cache) {
52+
return _hoisted_1
53+
}"
54+
`;
55+
3656
exports[`stringify static html stringify v-html 1`] = `
3757
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
3858

packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,4 +477,17 @@ describe('stringify static html', () => {
477477
expect(code).toMatch(`<code>&lt;span&gt;show-it &lt;/span&gt;</code>`)
478478
expect(code).toMatchSnapshot()
479479
})
480+
481+
// #6568
482+
test('Select elements can preserve data types when stringified', () => {
483+
const { code } = compileWithStringify(`
484+
<option :value="'string'">string</option>
485+
<option :value="false">boolean</option>
486+
<option :value="1">number 1</option>
487+
<option :value="2">number 2</option>
488+
<option :value="null">null</option>
489+
`)
490+
491+
expect(code).toMatchSnapshot()
492+
})
480493
})

packages/compiler-dom/src/transforms/stringifyStatic.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,12 @@ function stringifyElement(
318318
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
319319
evaluated
320320
)}"`
321+
if (typeof evaluated !== 'object' && node.tag === 'option') {
322+
res += ` v-stringify-type='${typeof evaluated}'`
323+
}
324+
}
325+
if (evaluated === null && node.tag === 'option') {
326+
res += ` v-stringify-type='${'null'}'`
321327
}
322328
} else if (p.name === 'html') {
323329
// #5439 v-html with constant value

packages/runtime-dom/__tests__/directives/vModel.spec.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
vModelDynamic,
77
withDirectives,
88
VNode,
9-
ref
9+
ref,
10+
createStaticVNode
1011
} from '@vue/runtime-dom'
1112

1213
const triggerEvent = (type: string, el: Element) => {
@@ -1172,4 +1173,61 @@ describe('vModel', () => {
11721173
await nextTick()
11731174
expect(data.value).toEqual('使用拼音输入')
11741175
})
1176+
1177+
it(`After the select tag is stringified,
1178+
v-model can get the correct type`, async () => {
1179+
const hoist = createStaticVNode(
1180+
"<option value=\"string\" v-stringify-type='string'>string</option><option value=\"false\" v-stringify-type='boolean'>bool</option><option value=\"1\" v-stringify-type='number'>number</option><option value=\"2\" v-stringify-type='number'>number</option><option v-stringify-type='null'>null</option>",
1181+
5
1182+
)
1183+
const component = defineComponent({
1184+
data() {
1185+
return { value: '' }
1186+
},
1187+
render() {
1188+
return [
1189+
withVModel(
1190+
h(
1191+
'select',
1192+
{
1193+
value: null,
1194+
'onUpdate:modelValue': setValue.bind(this)
1195+
},
1196+
[hoist]
1197+
),
1198+
this.value
1199+
)
1200+
]
1201+
}
1202+
})
1203+
render(h(component), root)
1204+
1205+
await nextTick()
1206+
const input = root.querySelector('select')
1207+
const optionList = root.querySelectorAll('option')
1208+
const data = root._vnode.component.data
1209+
1210+
optionList[0].selected = true
1211+
triggerEvent('change', input)
1212+
await nextTick()
1213+
expect(data.value).toBe('string')
1214+
1215+
optionList[0].selected = false
1216+
optionList[1].selected = true
1217+
triggerEvent('change', input)
1218+
await nextTick()
1219+
expect(data.value).toBe(false)
1220+
1221+
optionList[1].selected = false
1222+
optionList[2].selected = true
1223+
triggerEvent('change', input)
1224+
await nextTick()
1225+
expect(data.value).toBe(1)
1226+
1227+
optionList[2].selected = false
1228+
optionList[4].selected = true
1229+
triggerEvent('change', input)
1230+
await nextTick()
1231+
expect(data.value).toBe(null)
1232+
})
11751233
})

packages/runtime-dom/src/directives/vModel.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,18 @@ function setSelected(el: HTMLSelectElement, value: any) {
243243

244244
// retrieve raw value set via :value bindings
245245
function getValue(el: HTMLOptionElement | HTMLInputElement) {
246-
return '_value' in el ? (el as any)._value : el.value
246+
let value = '_value' in el ? (el as any)._value : el.value
247+
let stringifyType = el.getAttribute('v-stringify-type')
248+
if (stringifyType === 'number') {
249+
return Number(value)
250+
}
251+
if (stringifyType === 'boolean') {
252+
return value !== 'false'
253+
}
254+
if (stringifyType === 'null') {
255+
return null
256+
}
257+
return value
247258
}
248259

249260
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings

0 commit comments

Comments
 (0)