diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4a228e652..11022ce50 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,7 +15,7 @@ assignees: '' **To Reproduce** **Expected behavior** diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 100ee81e4..46c46d4e7 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -106,6 +106,10 @@ export default defineConfig({ } ] }, + { + text: 'FAQ', + link: '/guide/faq/' + }, { text: 'Migrating from Vue 2', link: '/migration/' @@ -214,6 +218,10 @@ export default defineConfig({ } ] }, + { + text: 'FAQ', + link: '/guide/faq/' + }, { text: 'Migrating from Vue 2', link: '/migration/' diff --git a/docs/fr/.vitepress/locale-config.ts b/docs/fr/.vitepress/locale-config.ts index fbecd52eb..55ccb2719 100644 --- a/docs/fr/.vitepress/locale-config.ts +++ b/docs/fr/.vitepress/locale-config.ts @@ -6,6 +6,10 @@ const frLocaleConfig: DefaultTheme.LocaleConfig & Omit: value foo is invalid. +TypeError: Cannot set property prefix of # which has only a getter +``` + +Cet avertissement est affiché si vous utilisez `shallowMount` ou `stubs` avec une propriété dont le nom est celui de l'une des propriétés de [`Element`](https://developer.mozilla.org/fr-FR/docs/Web/API/Element). + +Parmi les noms de propriétés courants partagés avec `Element` figurent : +* `attributes` +* `children` +* `prefix` + +Voir https://developer.mozilla.org/en-US/docs/Web/API/Element + +**Solutions possibles** + +1. Utilisez `mount` au lieu de `shallowMount` pour rendre le composant sans utiliser de `stubs` +2. Ignorez l'avertissement en utilisant un mock pour `console.warn` +3. Renommez la propriété du composant pour éviter les conflits avec les propriétés de `Element` diff --git a/docs/guide/faq/index.md b/docs/guide/faq/index.md new file mode 100644 index 000000000..1c883cab8 --- /dev/null +++ b/docs/guide/faq/index.md @@ -0,0 +1,25 @@ +# FAQ + +[[toc]] + +## Vue warn: Failed setting prop + +``` +[Vue warn]: Failed setting prop "prefix" on : value foo is invalid. +TypeError: Cannot set property prefix of # which has only a getter +``` + +This warning is shown in case you are using `shallowMount` or `stubs` with a property name that is shared with [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element). + +Common property names that are shared with `Element`: +* `attributes` +* `children` +* `prefix` + +See: https://developer.mozilla.org/en-US/docs/Web/API/Element + +**Possible solutions** + +1. Use `mount` instead of `shallowMount` to render without stubs +2. Ignore the warning by mocking `console.warn` +3. Rename the prop to not clash with `Element` properties diff --git a/src/createInstance.ts b/src/createInstance.ts index 468173313..bb79fe94c 100644 --- a/src/createInstance.ts +++ b/src/createInstance.ts @@ -307,6 +307,7 @@ export function createInstance( // stub out Transition and Transition Group by default. transformVNodeArgs( createVNodeTransformer({ + rootComponents, transformers: [ createStubComponentsTransformer({ rootComponents, diff --git a/src/vnodeTransformers/stubComponentsTransformer.ts b/src/vnodeTransformers/stubComponentsTransformer.ts index 24c855c8a..6a5ce6c6b 100644 --- a/src/vnodeTransformers/stubComponentsTransformer.ts +++ b/src/vnodeTransformers/stubComponentsTransformer.ts @@ -1,4 +1,9 @@ -import { isKeepAlive, isTeleport, VTUVNodeTypeTransformer } from './util' +import { + isKeepAlive, + isRootComponent, + isTeleport, + VTUVNodeTypeTransformer +} from './util' import { Transition, TransitionGroup, @@ -177,14 +182,8 @@ export function createStubComponentsTransformer({ }) } - if ( - // Don't stub VTU_ROOT component - !instance || - // Don't stub mounted component on root level - (rootComponents.component === type && !instance?.parent) || - // Don't stub component with compat wrapper - (rootComponents.functional && rootComponents.functional === type) - ) { + // Don't stub root components + if (isRootComponent(rootComponents, type, instance)) { return type } diff --git a/src/vnodeTransformers/util.ts b/src/vnodeTransformers/util.ts index ce71bcc93..950b8b204 100644 --- a/src/vnodeTransformers/util.ts +++ b/src/vnodeTransformers/util.ts @@ -1,6 +1,6 @@ import { isComponent } from '../utils' import { registerStub } from '../stubs' -import { ConcreteComponent, transformVNodeArgs } from 'vue' +import { Component, ConcreteComponent, transformVNodeArgs } from 'vue' type VNodeArgsTransformerFn = NonNullable< Parameters[0] @@ -23,9 +23,30 @@ export type VTUVNodeTypeTransformer = ( export const isTeleport = (type: any): boolean => type.__isTeleport export const isKeepAlive = (type: any): boolean => type.__isKeepAlive +export interface RootComponents { + // Component which has been passed to mount. For functional components it contains a wrapper + component?: Component + // If component is functional then contains the original component otherwise empty + functional?: Component +} +export const isRootComponent = ( + rootComponents: RootComponents, + type: VNodeTransformerInputComponentType, + instance: InstanceArgsType +): boolean => + !!( + !instance || + // Don't stub mounted component on root level + (rootComponents.component === type && !instance?.parent) || + // Don't stub component with compat wrapper + (rootComponents.functional && rootComponents.functional === type) + ) + export const createVNodeTransformer = ({ + rootComponents, transformers }: { + rootComponents: RootComponents transformers: VTUVNodeTypeTransformer[] }): VNodeArgsTransformerFn => { const transformationCache: WeakMap< @@ -40,17 +61,19 @@ export const createVNodeTransformer = ({ return [originalType, props, children, ...restVNodeArgs] } + const componentType: VNodeTransformerInputComponentType = originalType + const cachedTransformation = transformationCache.get(originalType) if ( cachedTransformation && + // Don't use cache for root component, as it could use stubbed recursive component + !isRootComponent(rootComponents, componentType, instance) && !isTeleport(originalType) && !isKeepAlive(originalType) ) { return [cachedTransformation, props, children, ...restVNodeArgs] } - const componentType: VNodeTransformerInputComponentType = originalType - const transformedType = transformers.reduce( (type, transformer) => transformer(type, instance), componentType diff --git a/tests/components/RecursiveComponent.vue b/tests/components/RecursiveComponent.vue index 63b34a344..a0a69d7a0 100644 --- a/tests/components/RecursiveComponent.vue +++ b/tests/components/RecursiveComponent.vue @@ -1,7 +1,15 @@ @@ -9,6 +17,7 @@ import Hello from './Hello.vue' defineProps<{ - first?: boolean + name: string + items?: string[] }>() diff --git a/tests/shallowMount.spec.ts b/tests/shallowMount.spec.ts index 21209f04e..2a2fba099 100644 --- a/tests/shallowMount.spec.ts +++ b/tests/shallowMount.spec.ts @@ -74,19 +74,29 @@ describe('shallowMount', () => { ) }) - it('stub instance of same component', () => { + it('stub instance of same component', async () => { const wrapper = mount(RecursiveComponent, { shallow: true, props: { - first: true + name: '1', + items: ['2'] } }) expect(wrapper.html()).toEqual( '
\n' + + '

1

\n' + ' \n' + - ' \n' + + ' \n' + '
' ) + + expect(wrapper.find('h2').text()).toBe('1') + + await wrapper.setProps({ + name: '3' + }) + + expect(wrapper.find('h2').text()).toBe('3') }) it('correctly renders slot content', () => {