]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(defineModel): support kebab-case/camelCase mismatches (#9950)
authorskirtle <65301168+skirtles-code@users.noreply.github.com>
Wed, 3 Jan 2024 10:18:35 +0000 (10:18 +0000)
committerGitHub <noreply@github.com>
Wed, 3 Jan 2024 10:18:35 +0000 (18:18 +0800)
packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
packages/runtime-core/src/apiSetupHelpers.ts

index b50da90d4266816df9b8d4a98acefadbd96b026a..04df0ae593ab5aa1ad4e8c9563845e22d5a7d8d9 100644 (file)
@@ -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 = () => {
index ce6050d4b9fb1164c1bb109d5bbd64c1a53a0392..3815c7b143e4fc52339f43ad862b06f0cd710540 100644 (file)
@@ -6,6 +6,7 @@ import {
   camelize,
   extend,
   hasChanged,
+  hyphenate,
   isArray,
   isFunction,
   isPromise,
@@ -382,6 +383,7 @@ export function useModel(
   }
 
   const camelizedName = camelize(name)
+  const hyphenatedName = hyphenate(name)
 
   const res = customRef((track, trigger) => {
     let localValue: any
@@ -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)
         ) {