-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vue 3: Async component with suspense wrapper #230
Comments
Related to: vuejs/test-utils#108 |
Hi! this is most likely an upstream issue in vue-test-utils-next (as seen in the issue you linked a few days ago) |
@afontcu After some debugging i found out that the wrapper on line 27 needs to be resolved first, e.g. with a Here is a test that demonstrates this. Are you sure this should be fixed in @vue/vue-test-utils? |
Hi! Thank you for taking the time to looking into this! Looks like you're right about the problem, too. Have you tried stubbing Thanks! |
Thanks for your reply! Could you elaborate on what you mean by stubbing |
After many problems with JSDOM/HappyDOM and issues like this i decided not to use VTL anymore. Playwright component testing has a almost identical API, is fast as well and runs in a real browser.. |
Any updates on this? We have embraced Suspense and async components very heavily in our project and this is really holding us back in writing tests. I've noticed it's the last part of making this library fully Vue 3 compatible. |
After a lot of debugging and trial and error finally got something that works with the current code base and isn't async. If you do pass an async component to this function then you just have to do some Note below I left in where I setup quasar, pinia, and vue router mocks/stubs. Also the test runner I use is vitest import { createTestingPinia } from '@pinia/testing';
import getQuasarOptions from '@rtvision/configs/quasar';
import { render } from '@testing-library/vue';
import { config as vueTestUtilsConfig, RouterLinkStub } from '@vue/test-utils';
import { Quasar } from 'quasar';
import { vi } from 'vitest';
import { defineComponent, reactive } from 'vue';
// Note need the wrapping div around suspense otherwise won't load correctly
// due to this line https://github.com/sand4rt/suspense-test/blob/master/tests/unit/renderAsync.js#L36
const SUSPENSE_TEST_TEMPLATE = `
<div id="TestRoot">
<suspense>
<async-component v-bind="$attrs" v-on="emitListeners">
<template v-for="(_, slot) of $slots" :key="slot" #[slot]="scope">
<slot key="" :name="slot" v-bind="scope" />
</template>
</async-component>
<template #fallback>
Suspense Fallback
</template>
</suspense>
</div>
`;
function getSuspenseWrapper(component) {
return defineComponent({
setup(_props, {
emit
}) {
const emitListeners = reactive({});
if ('emits' in component && Array.isArray(component.emits)) {
for (const emitName of component.emits) {
emitListeners[emitName] = (...args) => {
emit(emitName, ...args);
};
}
}
return {
emitListeners
};
},
emits: 'emits' in component && Array.isArray(component.emits) ? component.emits : [],
components: {
AsyncComponent: component
},
inheritAttrs: false,
template: SUSPENSE_TEST_TEMPLATE
});
}
/**
* @param initialState Used to set initial set of pinia stores
**/
export function mountComponent(
component,
{ props, initialState, slots } = {}
) {
return render(getSuspenseWrapper(component), {
props,
slots,
global: {
plugins: [
[Quasar, getQuasarOptions()],
createTestingPinia({
createSpy: vi.fn,
initialState
})
],
provide: {
...vueTestUtilsConfig.global.provide
},
components: {
AsyncComponent: component
},
stubs: {
icon: true,
RouterLink: RouterLinkStub
}
}
});
} Example of usage with async component // CommonTable.spec.ts
async function mountCommonTable(props, slots = {}) {
const tableWrapper = mountComponent(CommonTable, { props, slots });
await tableWrapper.findByRole('table');
return tableWrapper;
} |
This will probably not cover all the cases, but by copying together code, this seems to work: helper functions: const scheduler = typeof setImmediate === 'function' ? setImmediate : setTimeout
export function flushPromises(): Promise<void> {
return new Promise((resolve) => {
scheduler(resolve, 0)
})
}
export function wrapInSuspense(
component: ReturnType<typeof defineComponent>,
{ props }: { props: object },
): ReturnType<typeof defineComponent> {
return defineComponent({
render() {
return h(
'div',
{ id: 'root' },
h(Suspense, null, {
default() {
return h(component, props)
},
fallback: h('div', 'fallback'),
}),
)
},
})
} tests: render(wrapInSuspense(MyAsyncComponent, { props: { } }))
await flushPromises()
expect(screen.getByText('text in component')).toBeVisible() |
@dwin0 Works fine for me, thank you |
Struggling to test a async component. I created a suspense wrapper component with
defineComponent
which i think should work but it doesn't:Also created a repository for reproducing the issue.
The text was updated successfully, but these errors were encountered: