]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): properly merge props and emits options from mixins (#8052)
authorHimself65 <himself65@outlook.com>
Mon, 10 Apr 2023 07:06:21 +0000 (02:06 -0500)
committerGitHub <noreply@github.com>
Mon, 10 Apr 2023 07:06:21 +0000 (15:06 +0800)
close #7989

packages/runtime-core/__tests__/componentEmits.spec.ts
packages/runtime-core/src/apiSetupHelpers.ts
packages/runtime-core/src/componentOptions.ts

index 0e5f1e3983d66ebd53bbee7911079c57399eb6e3..d8774b078b69be6086b5b5996e96acceeb09bcba 100644 (file)
@@ -8,7 +8,8 @@ import {
   h,
   nodeOps,
   toHandlers,
-  nextTick
+  nextTick,
+  ComponentPublicInstance
 } from '@vue/runtime-test'
 import { isEmitListener } from '../src/componentEmits'
 
@@ -454,4 +455,55 @@ describe('component: emit', () => {
     await nextTick()
     expect(fn).not.toHaveBeenCalled()
   })
+
+  test('merge string array emits', async () => {
+    const ComponentA = defineComponent({
+      emits: ['one', 'two']
+    })
+    const ComponentB = defineComponent({
+      emits: ['three']
+    })
+    const renderFn = vi.fn(function (this: ComponentPublicInstance) {
+      expect(this.$options.emits).toEqual(['one', 'two', 'three'])
+      return h('div')
+    })
+    const ComponentC = defineComponent({
+      render: renderFn,
+      mixins: [ComponentA, ComponentB]
+    })
+    const el = nodeOps.createElement('div')
+    expect(renderFn).toHaveBeenCalledTimes(0)
+    render(h(ComponentC), el)
+    expect(renderFn).toHaveBeenCalledTimes(1)
+  })
+
+  test('merge object emits', async () => {
+    const twoFn = vi.fn((v: unknown) => !v)
+    const ComponentA = defineComponent({
+      emits: {
+        one: null,
+        two: twoFn
+      }
+    })
+    const ComponentB = defineComponent({
+      emits: ['three']
+    })
+    const renderFn = vi.fn(function (this: ComponentPublicInstance) {
+      expect(this.$options.emits).toEqual({
+        one: null,
+        two: twoFn,
+        three: null
+      })
+      expect(this.$options.emits.two).toBe(twoFn)
+      return h('div')
+    })
+    const ComponentC = defineComponent({
+      render: renderFn,
+      mixins: [ComponentA, ComponentB]
+    })
+    const el = nodeOps.createElement('div')
+    expect(renderFn).toHaveBeenCalledTimes(0)
+    render(h(ComponentC), el)
+    expect(renderFn).toHaveBeenCalledTimes(1)
+  })
 })
index e0fe434210f04d365daa62fba7a7b4a4a13e2a06..de7426ad32507d083c5f2d7f0622951df5df22aa 100644 (file)
@@ -396,10 +396,15 @@ function getContext(): SetupContext {
   return i.setupContext || (i.setupContext = createSetupContext(i))
 }
 
-function normalizePropsOrEmits(props: ComponentPropsOptions | EmitsOptions) {
+/**
+ * @internal
+ */
+export function normalizePropsOrEmits(
+  props: ComponentPropsOptions | EmitsOptions
+) {
   return isArray(props)
     ? props.reduce(
-        (normalized, p) => ((normalized[p] = {}), normalized),
+        (normalized, p) => ((normalized[p] = null), normalized),
         {} as ComponentObjectPropsOptions | ObjectEmitsOptions
       )
     : props
index 481c2adb67e227da0829cf0a09b6f9d7c53e2859..bba5ade9ad41eb1fc07907788a15ab4863422421 100644 (file)
@@ -51,7 +51,8 @@ import {
 import {
   ComponentObjectPropsOptions,
   ExtractPropTypes,
-  ExtractDefaultPropTypes
+  ExtractDefaultPropTypes,
+  ComponentPropsOptions
 } from './componentProps'
 import { EmitsOptions, EmitsToProps } from './componentEmits'
 import { Directive } from './directives'
@@ -75,6 +76,7 @@ import {
 import { OptionMergeFunction } from './apiCreateApp'
 import { LifecycleHooks } from './enums'
 import { SlotsType } from './componentSlots'
+import { normalizePropsOrEmits } from './apiSetupHelpers'
 
 /**
  * Interface for declaring custom options.
@@ -1069,8 +1071,8 @@ export function mergeOptions(
 
 export const internalOptionMergeStrats: Record<string, Function> = {
   data: mergeDataFn,
-  props: mergeObjectOptions, // TODO
-  emits: mergeObjectOptions, // TODO
+  props: mergeEmitsOrPropsOptions,
+  emits: mergeEmitsOrPropsOptions,
   // objects
   methods: mergeObjectOptions,
   computed: mergeObjectOptions,
@@ -1147,7 +1149,33 @@ function mergeAsArray<T = Function>(to: T[] | T | undefined, from: T | T[]) {
 }
 
 function mergeObjectOptions(to: Object | undefined, from: Object | undefined) {
-  return to ? extend(extend(Object.create(null), to), from) : from
+  return to ? extend(Object.create(null), to, from) : from
+}
+
+function mergeEmitsOrPropsOptions(
+  to: EmitsOptions | undefined,
+  from: EmitsOptions | undefined
+): EmitsOptions | undefined
+function mergeEmitsOrPropsOptions(
+  to: ComponentPropsOptions | undefined,
+  from: ComponentPropsOptions | undefined
+): ComponentPropsOptions | undefined
+function mergeEmitsOrPropsOptions(
+  to: ComponentPropsOptions | EmitsOptions | undefined,
+  from: ComponentPropsOptions | EmitsOptions | undefined
+) {
+  if (to) {
+    if (isArray(to) && isArray(from)) {
+      return [...new Set([...to, ...from])]
+    }
+    return extend(
+      Object.create(null),
+      normalizePropsOrEmits(to),
+      normalizePropsOrEmits(from ?? {})
+    )
+  } else {
+    return from
+  }
 }
 
 function mergeWatchOptions(