Closed lmiller1990 closed 4 years ago
I was thinking about such helpers too. It is super convenient to have them at your disposal, officially. I also made a similar one for testing stub scoped and named slots.
Maybe we can add all of these to the vtu-extended
package we all have been mentioning here and there :D
We could just include them in core. If people will basically install the package every time, why not? Kind of like flush-promises
- it's in basically every VTU project ever, it almost feels silly not to include it.
Can you share your helpers here?
Should work for named slots too, right?
Would this help with https://github.com/vuejs/vue-test-utils-next/issues/2
I am starting to link the idea of including helpers - if we are going to mention them in the guide, we may as well.
This works for any type of slot, but its only to generate a minimal stub, that renders and provides just enough for you to test your slot content.
It will not help with testing slots properly on the mounted component. That is what I will be doing next day or so. I have started a branch locally with cases :)
I agree, we will have some edge cases that may need some some helpers :)
Great, sounds good. I will work on my suspense helper a bit too.
I wonder, how are we going to test suspense content? Finding things inside Suspense, while its loading/errored? I could not make findComponent
work, I did not test if find
managed to find something.
Seems to work with find. No reason why it wouldn't - you just need flush-promises
to make the async setup
resolve. Is this what you mean?
That's what I am using the tests/features
directory for - just testing various "real world" scenarios that are not specific to any method in VTU. I think we can add Vuetify etc here, too.
So while writing the ScopesSlots PR I realized this is a pretty nifty way of testing scoped slot params. Deff one I would have loved to have available, a while back.
I think its a great addition to the utilities.
const assertScopedSlotData = () => {
let assertion = ref(null)
const slot = (params) => {
assertion.value = params
return ''
}
return [assertion, slot]
}
const [params, slot] = assertScopedSlotData()
const wrapper = mount(ComponentWithSlots, {
slots: {
scoped: slot,
}
})
expect(params.value).toEqual({
boolean: true,
string: 'string',
object: { foo: 'foo' }
})
Interesting!
The more I think about it, the more I like the idea of including these utilities. Let's do it.
Should assertScopedSlotData
return an object you destructure? Either way is fine - just an idea.
Also, how does assertScopedSlotData
get a reference to the current component/wrapper? Do you use it like wrapper.assertScopedSlotData
?
So how I invisioned using it was as just an external function, that you use as I have shown in the example:
const [namedParams, named] = assertScopedSlotData()
const [defaultParams, default] = assertScopedSlotData()
const wrapper = mount(Comp, { slots: { named, default } })
expect(namedParams).toEqual({})
expect(defaultParams).toEqual({})
This way I think its much easier to name the exported objects, then to destructure and object and re-assign names. But then again, in most cases you test only one slot at a time.... But overall I did not intend this to be in the wrapper
as it would have to attach special hidden properties to the instance, which we dont want :) and augment the slot, which I did not want :)
Oh, I see. I was confused by returning return [assertion, slot]
but then renaming the first variable to params
when you call the function.
This is very cool - what an interesting way to use Vue's reactivity. I am starting to see this decoupled reactivity is actually a huge improvement to Vue overall.
Don't have a strong opinion for/against including this. I personally would still make my assertions against the DOM as opposed the the props the slot receives... this still seems like a fancy way to test an implementation detail (what props are passed) instead of a behavior (what the component actually does with the props).
That said, I think you encounter much more complex uses of slots in your day to day usage - would this make an improvement to the code-bases you work on? Do you think other people would also find this useful? If you think so, I don't have a strong opinion against including this - people who want to use it can, people who don't need not.
Lets decide where/how do we add these extra bits? Do we make a vtu-extended
package, that exports helpers and extra VTU convenience methods, like getByTestId()
, name()
or that overview
method, that was added recently?
Or do we keep helpers to VTU and leave community to make their own extra plugins?
I think anything that can be done via a plugin is fine to leave out. These helpers (slots, suspense) can be in core, imo - doesn't hurt.
Did you want to make a branch with your slot helpers? Happy to review and push my suspense helper (can't find it, might just remake it it :) )
Lets decide where/how do we add these extra bits? Do we make a vtu-extended package, that exports helpers and extra VTU convenience methods, like getByTestId(), name() or that overview method, that was added recently?
Could they be implemented as plugins? If so, we could have some "official" plugins, meaning plugins that we've developed or that we consider valuable, so they earn the "official" umbrella.
That said, I think you encounter much more complex uses of slots in your day to day usage - would this make an improvement to the code-bases you work on? Do you think other people would also find this useful?
These are important questions – I'd stick to testing against the DOM so I can't really say anything meaningful here. Maybe we could check against other people that manage tests on large codebases?
If you think so, I don't have a strong opinion against including this - people who want to use it can, people who don't need not.
Agree. Yet we need to be careful – we know people might not need this, but newcomers always have a hard time trying to grasp what they need and what they can ignore for a while. I think I'm just saying that we need to be extra careful when adding new stuff to the API, and make sure docs are in sync 👍
The main one I'm thinking about is a Suspense helper.
Happy to let this sit and add things as people ask for them.
Can we get an official helper function to stub out a component, while still showing its slot content.
For example, I have a component like this:
<div class="parent-component">
<BaseTable :data="data">
<template v-if="thing">
<label for="thing">Thing</label>
<input type="checkbox" id="thing">
</template>
</BaseTable>
</div>
All I care about is testing the logic being passed into the slot. Currently if I did a snapshot of the component 99% of it would be cruft that I don't care about:
<div class="parent-component">
<div class="base-table-wrapper">
<!-- ...80 lines of code... -->
<div class="custom-table-filters-slot">
<label for="thing">Thing</label>
<input type="checkbox" id="thing">
</div>
<!-- ...3000 lines of code, mostly from a 3rd party table component displaying data... -->
</div>
</div>
But if we stubbed out the child component, while still rendering its slots, we'd get a much better snapshot. Something closer to this:
<div class="parent-component">
<div stub="base-table">
<div class="custom-table-filters-slot">
<label for="thing">Thing</label>
<input type="checkbox" id="thing">
</div>
</div>
</div>
This is a huge improvement, I shouldn't have to load 3000 lines of code in a giant DOM tree just to test a checkbox.
See: #69 for how this is done in VTU 1 + Vue 2.
I think we have have something like this @TheJaredWilcurt (docs for VTU Next are still a WIP...) but as a (global) config, we could reuse that logic.
If this was a mounting option, would that solve your problem? This feels like something you would want on a global config, not test by test (correct me if I am wrong, I don't use shallow or snapshots often).
For example:
shallowMount(Foo, {
renderDefaultSlot: true
})
Having both would be good (a global defaulting to true that can be overridden on a per-test basis). But if I could only choose one I'd want the per-test control.
Yes, I agree, we should add this as a global config too.
This (renderStubDefaultSlot mounting option) will go out with #212.
As for helpers, I think we can revisit them when/if there is more demand. So far no-one issues really - we will document how to test things like <Suspense>
etc. If there is more demand, we can consider adding them. Most are trivial to implement anyway. For now I will close this issue.
Hey @lmiller1990 the renderStubDefaultSlot
option is a great idea and would help me out a lot! When do you think it might make it into a release?
Hey @lmiller1990 the
renderStubDefaultSlot
option is a great idea and would help me out a lot! When do you think it might make it into a release?
it's already there :) https://github.com/vuejs/vue-test-utils-next/pull/102
@afontcu Brilliant, thanks!
Is there a plan to extend the render to named slots rather than just default? I have a similarly scenario to @TheJaredWilcurt were I use named slots and the default slot to compose a component together. Whilst doing a full render would solve this I really only want to focus on the component functionality at this level. Assuming other unit tests cover other components.
What about checking if the setup function is async like (not sure if there is a better way without calling the function first):
Component.setup.constructor.name === 'AsyncFunction'
And then return something like the mountSuspense
promise @lmiller1990 posted earlier so that the mount
function can be optionally awaited when the component has a an async setup?
It seems to me that a lot of people will run into this. As far as i know there is no off-the-shelf way to test this right now while this a common task.
@rikbrowning Might be worth making a new issue if you have a feature proposal. Not 100% clear on what you want, but I think I understand and I think the reason we don't have that is a technical blocker (could be wrong, would need to see a more fleshed out example of your proposal).
@sand4rt I think including some helpers is a good idea, if you want to make an issue with a proposal we could go over it. If there's no technical blocker/edge cases, we could add it. What I've generally been doing is just something like this which seems okay. What do you think?
@lmiller1990 I put together the feature I am talking about in this commit https://github.com/rikbrowning/vue-test-utils-next/commit/d9309d48264031ec83a9211c8baced8f5e87a088 The feature would also fix https://github.com/vuejs/vue-test-utils-next/issues/773
My two cents goes towards including a helper function in this library. I've been struggling to get this to work with components that need props and react to changes to those props.
I think I finally have a solution, but I'm still not sure if it's the right way. You probably know better.
This is my current solution in case it could help someone:
/**
* Creates a Wrapper that contains the mounted and rendered Vue component.
*
* @param component The asynchronous component to mount using suspense.
* @param options The mount options.
*
* @returns The mounted component wrapper.
*/
async function mountWithSuspense<Component extends ComponentPublicInstance, Props>(
component: new () => Component,
options: MountingOptions<Props>
): Promise<VueWrapper<ComponentPublicInstance>> {
const wrapper = defineComponent({
'components': { [component.name]: component },
'props': Object.keys(options.props ?? {}),
'template': `<suspense><${component.name} v-bind="$props" /></suspense>`
});
const result = mount(wrapper, options);
await flushPromises();
return result;
}
Neat helper. Personally I like to keep the scope of this library small, and encourage building helpers/integrations, mainly to keep maintenance easy. No doubt this helper is great for you, but someone else might need to tweak it - then we end up with a helper with many different options.
We could have. a page in the docs with some snippets/helpers - kind of like recipes - what do you think?
Alternatively, if you have a blog, you could write about how it works and we could backlink to you from the docs - good traffic/publicity for your content.
I think it's worth considering.
<suspense>
is very intrusive. Once a component is asynchronous, the rest of that component's clients must react to this in some way. Most of the time, to me, it seems like the best policy is NOT to 'suspense' until you really need to. That has been long chains of components.
We have a lot of packages that depend on each other, and trying to encapsulate this in some sort of package hasn't been easy. For example, recently, it had a problem with the config
object being different between packages due to bundle issues and version differences.
This makes me think that it's actually a variation of mount
, like shallowMount
for when you're aware of the need for suspense
.
It would be even better if this could be autodetected by mount
.
@alejandroclaro first things first, thank you for your contribution here; it's been extraordinarily helpful. That said, I'm running into a problem trying to access elements of the component itself after it has mounted. With a synchronous component I can simply call wrapper.vm.<methodName>
or wrapper.vm.<dataElement>
to get access to relevant functionality in the component, but using this method it doesn't work.
I've provided a minimal example here: https://stackblitz.com/~/github.com/jeffpohlmeyer/vue-testing-async where I try to call the helloWorld
method on the component but it doesn't recognize it as a function. You can see the error if you open a new terminal and just run vitest
.
Can this issue be reopened?
I tried to find a solution for testing a simple Component with async setup code and was unable to find a solution. In my opinion async/await should be easy to use.
@alejandroclaro first things first, thank you for your contribution here; it's been extraordinarily helpful. That said, I'm running into a problem trying to access elements of the component itself after it has mounted. With a synchronous component I can simply call
wrapper.vm.<methodName>
orwrapper.vm.<dataElement>
to get access to relevant functionality in the component, but using this method it doesn't work.I've provided a minimal example here: https://stackblitz.com/~/github.com/jeffpohlmeyer/vue-testing-async where I try to call the
helloWorld
method on the component but it doesn't recognize it as a function. You can see the error if you open a new terminal and just runvitest
.
I have found a workaround to access wrapper.vm
, hope it helps
wrapper.getComponent(<component>).vm.<dataElement>
@Bo-Yang-PDL try to do it like this:
async function mountWithSuspense<Component extends ComponentPublicInstance, Props>(
component: new () => Component,
options: MountingOptions<Props>
): Promise<VueWrapper<ComponentPublicInstance>> {
const wrapper = defineComponent({
components: { [component.name]: component },
props: Object.keys(options.props ?? {}),
template: `<suspense><${component.name} v-bind="$props" /></suspense>`
})
const result = mount(wrapper, options)
await flushPromises()
const childWrapper = result.findComponent(component)
return childWrapper
}
105 had some great discussion around helpers - the example raised there was for components with
async setup
functions (used in<Suspense>
). Testing those alone won't work, since if you have aasync setup
, Vue expected a<Suspense>
wrapper.We could provide a helper (this works)
Some thoughts:
flushPromises
for the user. Any possible side-effects/unexpected behavior?shallowMountSuspense
? Someone will almost certainly ask for this.shallowMount
as a stand-alone function (which I think we should) and instead have ashallow: true
mounting option, this would not be a problem.