Skip to content

Commit

Permalink
fix(defineModel): support kebab-case/camelCase mismatches (#9950)
Browse files Browse the repository at this point in the history
  • Loading branch information
skirtles-code committed Jan 3, 2024
1 parent f300a40 commit 10ccb9b
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 2 deletions.
78 changes: 78 additions & 0 deletions packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,84 @@ describe('SFC <script setup> helpers', () => {
expect(serializeInner(root)).toBe('bar')
})

test('kebab-case v-model (should not be local)', async () => {
let foo: any

const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})

const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// v-model:foo-bar compiles to foo-bar and onUpdate:fooBar
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:fooBar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')

expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')

await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})

test('kebab-case update listener (should not be local)', async () => {
let foo: any

const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})

const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// The template compiler won't create hyphenated listeners, but it could have been passed manually
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:foo-bar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')

expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')

await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})

test('default value', async () => {
let count: any
const inc = () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/runtime-core/src/apiSetupHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
camelize,
extend,
hasChanged,
hyphenate,
isArray,
isFunction,
isPromise,
Expand Down Expand Up @@ -382,6 +383,7 @@ export function useModel(
}

const camelizedName = camelize(name)
const hyphenatedName = hyphenate(name)

const res = customRef((track, trigger) => {
let localValue: any
Expand All @@ -403,9 +405,12 @@ export function useModel(
!(
rawProps &&
// check if parent has passed v-model
(name in rawProps || camelizedName in rawProps) &&
(name in rawProps ||
camelizedName in rawProps ||
hyphenatedName in rawProps) &&
(`onUpdate:${name}` in rawProps ||
`onUpdate:${camelizedName}` in rawProps)
`onUpdate:${camelizedName}` in rawProps ||
`onUpdate:${hyphenatedName}` in rawProps)
) &&
hasChanged(value, localValue)
) {
Expand Down

0 comments on commit 10ccb9b

Please sign in to comment.