Skip to content

Commit 10ccb9b

Browse files
fix(defineModel): support kebab-case/camelCase mismatches (#9950)
1 parent f300a40 commit 10ccb9b

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

packages/runtime-core/__tests__/apiSetupHelpers.spec.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,84 @@ describe('SFC <script setup> helpers', () => {
314314
expect(serializeInner(root)).toBe('bar')
315315
})
316316

317+
test('kebab-case v-model (should not be local)', async () => {
318+
let foo: any
319+
320+
const compRender = vi.fn()
321+
const Comp = defineComponent({
322+
props: ['fooBar'],
323+
emits: ['update:fooBar'],
324+
setup(props) {
325+
foo = useModel(props, 'fooBar')
326+
return () => {
327+
compRender()
328+
return foo.value
329+
}
330+
},
331+
})
332+
333+
const updateFooBar = vi.fn()
334+
const root = nodeOps.createElement('div')
335+
// v-model:foo-bar compiles to foo-bar and onUpdate:fooBar
336+
render(
337+
h(Comp, { 'foo-bar': 'initial', 'onUpdate:fooBar': updateFooBar }),
338+
root,
339+
)
340+
expect(compRender).toBeCalledTimes(1)
341+
expect(serializeInner(root)).toBe('initial')
342+
343+
expect(foo.value).toBe('initial')
344+
foo.value = 'bar'
345+
// should not be using local mode, so nothing should actually change
346+
expect(foo.value).toBe('initial')
347+
348+
await nextTick()
349+
expect(compRender).toBeCalledTimes(1)
350+
expect(updateFooBar).toBeCalledTimes(1)
351+
expect(updateFooBar).toHaveBeenCalledWith('bar')
352+
expect(foo.value).toBe('initial')
353+
expect(serializeInner(root)).toBe('initial')
354+
})
355+
356+
test('kebab-case update listener (should not be local)', async () => {
357+
let foo: any
358+
359+
const compRender = vi.fn()
360+
const Comp = defineComponent({
361+
props: ['fooBar'],
362+
emits: ['update:fooBar'],
363+
setup(props) {
364+
foo = useModel(props, 'fooBar')
365+
return () => {
366+
compRender()
367+
return foo.value
368+
}
369+
},
370+
})
371+
372+
const updateFooBar = vi.fn()
373+
const root = nodeOps.createElement('div')
374+
// The template compiler won't create hyphenated listeners, but it could have been passed manually
375+
render(
376+
h(Comp, { 'foo-bar': 'initial', 'onUpdate:foo-bar': updateFooBar }),
377+
root,
378+
)
379+
expect(compRender).toBeCalledTimes(1)
380+
expect(serializeInner(root)).toBe('initial')
381+
382+
expect(foo.value).toBe('initial')
383+
foo.value = 'bar'
384+
// should not be using local mode, so nothing should actually change
385+
expect(foo.value).toBe('initial')
386+
387+
await nextTick()
388+
expect(compRender).toBeCalledTimes(1)
389+
expect(updateFooBar).toBeCalledTimes(1)
390+
expect(updateFooBar).toHaveBeenCalledWith('bar')
391+
expect(foo.value).toBe('initial')
392+
expect(serializeInner(root)).toBe('initial')
393+
})
394+
317395
test('default value', async () => {
318396
let count: any
319397
const inc = () => {

packages/runtime-core/src/apiSetupHelpers.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
camelize,
77
extend,
88
hasChanged,
9+
hyphenate,
910
isArray,
1011
isFunction,
1112
isPromise,
@@ -382,6 +383,7 @@ export function useModel(
382383
}
383384

384385
const camelizedName = camelize(name)
386+
const hyphenatedName = hyphenate(name)
385387

386388
const res = customRef((track, trigger) => {
387389
let localValue: any
@@ -403,9 +405,12 @@ export function useModel(
403405
!(
404406
rawProps &&
405407
// check if parent has passed v-model
406-
(name in rawProps || camelizedName in rawProps) &&
408+
(name in rawProps ||
409+
camelizedName in rawProps ||
410+
hyphenatedName in rawProps) &&
407411
(`onUpdate:${name}` in rawProps ||
408-
`onUpdate:${camelizedName}` in rawProps)
412+
`onUpdate:${camelizedName}` in rawProps ||
413+
`onUpdate:${hyphenatedName}` in rawProps)
409414
) &&
410415
hasChanged(value, localValue)
411416
) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy