-
Notifications
You must be signed in to change notification settings - Fork 264
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
WIP: findComponent edge cases #184
Conversation
Hmm... You’re right. It is curious that it only works for SFCs 🤔. I agree
that a library limitation will be preferred to writing workarounds. Perhaps
we put a warning
…On Sun, Aug 23, 2020 at 6:32 AM Lachlan ***@***.***> wrote:
Hi all! This is a bug write-up and I would like some feedback from
everyone. Sorry for this wall of text! I feel like this is important and I
don't want to be the only one calling the shots.
TL;DR is findComponent seems impossible to support in the same way it
works in VTU V1. I could be wrong but either way here is my findings and
some options.
The current implementation very buggy (to the point it isn't really
useful). The reason this is important is people will be looking to upgrade
to Vue 3 soon. If they cannot upgrade their tests, they will not want to
upgrade their product, either. We need to make sure there is an obvious way
to do move to Vue 3, and in the current state of things there is *not*.
See this issue for some context: #180
<#180>
I rewrote the findVNodes function to not mutate at all (use recursion
instead) in an effort to understand how it works. You can see that in this
PR. I learned a lot. Basically all components have the concept of a
subTree, which stores the components VDOM state. We recurse down each
level via subTree or children. It works for some cases, but not for
others. Here are the cases I cannot find a solution for:
- functional components. No instance, so you cannot findComponent
them, or at least when you do find them you cannot get the instance, which
means no VueWrapper. I found a fix for this in this PR. But, ince
there is no instance, you cannot access .vm and all the stuff you
would expect (like props etc). We could return a DOMWrapper, but this
feels wrong, since people will expect findComponent to find a
component. The whole point of splitting find and findComponent` was to
emphasis the difference in the two.
- slots and inline templates. These is the real blocker here! You can
see how I access the default slot here (this PR):
if (isObject(vnode.component.subTree.children) &&
'default' in vnode.component.subTree.children &&
isFunction(vnode.component.subTree.children['default'])
) {
// hohoho, we got it!
const defaultSlotContent = vnode.component.subTree.children['default']()
}
Basically in a regular stateful component, you can do something like this:
vnode.component.subTree.children // and so on, you get one or more VNodes. Continue traversing the VDOM!
In the case of slots and inline templates, though, you end up with
something like this:
vnode.component.subTree.children //=> { default: [Function: renderFnWithContext] }
If you call default(), it does not include things like subTree in the
same manner as you would expect, so you cannot continue to recurse down the
tree. You can eventually find the component you want with some effort, but
it doesn't appear to have a PublicComponentInstance (so no vm), so we
cannot create a VueWrapper with all the APIs the user would expect.
Components created with template (not render or .vue files) suffer this
same problem - you need to call render function in the same manner, so you
do not get a subTree like you'd expect.
On the flip side, all of these issues seem to go away when we use SFC (via
vue-jest)! So I guess pre-compiling everything solves the problems,
somehow. 🤷♂️ Interesting.
Here are my proposed solutions:
- add an additional wrapper. This would something like
FunctionalWrapper that returns the non-instance with some limited
methods we can support (eg, html() and attributes for static HTML
attributes) and consider logging a warning. This seems bad, since you can't
do very much and its not obvious what you are going to get from
getComponent unless you know Vue and VTU inside out.
- dive deeper and use @vue/compiler-sfc to compile all non-SFC
components. It has several useful functions (like parseTemplate and
parseScript) that we can use. This seems like a lot of work, but it
might be worth it. This is how vue-jest works and pre-compiled SFCs
seem not to have the same caveats. This would be a long term goal, at least
if I need to write the code (I also need to focus on other bugs, vue-jest,
VTU v1...).
- just don't support findComponent for inline components. We would log
something like "If you want to use findComponent, please make a .vue
file. If a problem is too hard for to solve, I either ask someone smarter,
just change the problem, or provide a work around. I think most people are
using SFCs... but maybe not. I don't like this, but I don't hate it. I'd
prefer to support 80% of the use cases with 100% solution than 100% of the
use cases with an 80% solution.
- (*my favorite solution*) *until* we figure out how to solve the
problems for non SFC components, put a warning that says "findComponent
currently only supports SFCs. We are working on supporting all manner of
components". The reason I like this is so I don't have to keep telling
people about work-arounds and closing issues when people point out it's not
working. I'd much prefer a library with limitations that works really well,
than one without limitations that doesn't.
Tagging all the people who have expressed interested in contributing to
and improving VTU in the last few weeks. Please give me your input!
@afontcu <https://github.com/afontcu> @dobromir-hristov
<https://github.com/dobromir-hristov> @JeremyWuuuuu
<https://github.com/JeremyWuuuuu> @JessicaSachs
<https://github.com/JessicaSachs> @cexbrayat
<https://github.com/cexbrayat> @Jokcy <https://github.com/Jokcy>
@angelogulina <https://github.com/AngeloGulina> @AtofStryker
<https://github.com/AtofStryker>
------------------------------
You can view, comment on, or merge this pull request online at:
#184
Commit Summary
- wip: update find component to handle edge cases
- tests: add some SFC tests
File Changes
-
*M*
src/utils.ts
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-e1495d267619047a7cca5cfe8f692729>
(8)
-
*M*
src/utils/find.ts
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-217a6dd71dcfdf610f0eb74f47cb27c5>
(206)
-
*M*
src/vueWrapper.ts
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-a4bf323f02c421d08f83ad781f87e349>
(2)
-
*A*
tests/components/A.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-f9e62393e4d25c0553125b4b20561d69>
(12)
-
*A*
tests/components/B.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-c46717364daca509382c6735f760c957>
(13)
-
*A*
tests/components/C.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-ad5db3687693b0c001186445c71de146>
(10)
-
*A*
tests/components/ComponentA.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-f29ba3101feb162b0fa0bd592680cf25>
(10)
-
*A*
tests/components/ComponentB.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-03d9f1ab9ffd4edf965a991885f826a7>
(10)
-
*A*
tests/components/D.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-0ac881c79d4161b759c0de6e726bd52f>
(10)
-
*A*
tests/components/Issue.vue
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-1228b01ed65528abbcc27504a1ccfdb3>
(25)
-
*M*
tests/findComponent.spec.ts
<https://github.com/vuejs/vue-test-utils-next/pull/184/files#diff-3e22d2badc1a61e2e80330efea26ef91>
(145)
Patch Links:
- https://github.com/vuejs/vue-test-utils-next/pull/184.patch
- https://github.com/vuejs/vue-test-utils-next/pull/184.diff
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#184>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAVL4BBJFUWHSQV6N2H3UFLSCDV3VANCNFSM4QIS64XQ>
.
|
Hi! Thanks for the write-up, Lachlan. It's been both helpful and informative. Quick question: you mentioned that "some (not all) these issues seem to go away when we use SFC". Are we able to track down which issues are not gone when using SFC? If that's the case, I'd +1 the idea of displaying a warning. I'm not a strong proponent or user of Again, thanks! |
@afontcu @JessicaSachs Updated above with some screenshots that may give more context! |
@JessicaSachs dunno about running in a browser. Would need to investigate. @afontcu yes, I think this feature sucks and no-one should use The reason I bring this up is I feel like if I do not implement it likely won't happen, at least before Vue 3 is out and people would like to start upgrading. So rather than feeling bad and saying "sorry we don't support it yet" and letting issues pile up (see VTU v1) I'd much rather say "sorry, we don't that feature because it can't be done properly/impractical". I may well be too far down the rabbit hole; if anyone is reading this that knows what I am missing here please let me know!!!!! |
Hi, after I just read about the text and before dive into the code, I'd like to say this: |
@JeremyWuuuuu hey, thanks for the feedback! I cannot find I think the slots edge case will prove more challenging (also much more common). I'll continue to explore, I just wanted to give some visibility to what's going on (since we had a few issues, I don't want users to think we are just ignored/not taking the migration to Vue 3 seriously!) Thanks for the post again, I'll see what else I can come up with! Feel free to have a hack if you like too :D |
By now, I am trying to undestand the main logic of VTU, so I copied the aggregateChildren(nodes, node.children)
if (node.component) {
// match children of the wrapping component
aggregateChildren(nodes, node.component.subTree.children)
aggregateChildren(nodes, [node.component.subTree])
if (node.component.subTree.component) {
aggregateChildren(
nodes,
node.component.subTree.component.subTree.children,
)
}
} the main difference is I tried |
Interesting - you can't always assume I think a recursive solution (like my PR) will make this much easier to understand. Either way - my next question is what do you get when you find If you can access the |
@lmiller1990 I try to explain it. <Comp><div>name</div></Comp>
const Comp1 = defineComponent({
name: 'Comp1',
setup() {
return () => <div>Comp1</div>
},
})
const Comp2 = defineComponent({
name: 'Comp2',
setup() {
return () => <div>Comp2</div>
},
})
const Main = defineComponent({
name: 'Main',
setup() {
return () => (
<div>
<Comp1 />
<Comp2 />
</div>
)
},
})
const App = defineComponent({
name: 'App',
setup() {
return () => <Main />
},
}) if we render I tried my demo with |
forget it, it's not that simple... |
} | ||
|
||
const wrapper = mount(Parent) | ||
wrapper.getComponent(Child) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused by getComponent
used in this context (and some following lines as well).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I will abandon this PR for now since @Jokcy found a better solution.
I was just doing this as a quick test - the idea is that if the component is not found, it will throw an error (findComponent
would not).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Just for my understanding, #188 is solving the slots case but not the functional components one?
If this is true, should we pursue your proposal of having some warning or anything else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say so. This would be a much better developer experience.
We could use this __isFunctionalComponent
property I am proposing we add here #185
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it!
It seems someone smarter than me has figured this out #188 I will close this for now. I am sure we are likely still missing some edge cases. Anyway, this was very frustrating, but I learned a lot about Vue and I am glad we are finding all these bugs before Vue 3 is official released. Great job everyone who was involved in this discussion and has contributed so far! |
Hi all! This is a bug write-up and I would like some feedback from everyone.
TL;DR is
findComponent
seems very difficult support. I could be wrong but either way here is my findings and some options.The current implementation very buggy (to the point it isn't really useful). See this issue for some context: #180
I rewrote the
findVNodes
function to covert some of the edge cases. How it works: basically all components have the concept of asubTree
, which stores the components VDOM state. We recurse down each level viasubTree
orchildren
. It works for some cases, but not for others. Here are the cases I cannot find a solution for:findComponent
them, or at least when you do find them you cannot get the instance, which means noVueWrapper
. I found a fix for this in this PR. But, since there is no instance, you cannot access.vm
and all the stuff you would expect (like props etc). We could return aDOMWrapper
, but this feels wrong. Users will expectfindComponent
to find a component. The whole point of splittingfind
and findComponent` was to emphasis the difference in the two.templates
, thesubTree
is different and there seems not way to get the instance via recursing down.Some (not all) these issues seem to go away when we use SFC (via
vue-jest
). I guess we need to dig deeper into how Vue compiles things. The original issue in #180 is fixed by using SFCs. 🤷♂️ Interesting. Other are not.Here are my proposed solutions:
FunctionalWrapper
that returns the non-instance with some limited methods we can support (eg,html()
andattributes
for static HTML attributes). Not ideal.@vue/compiler-sfc
to compile all non-SFC components to SFCs. This seems it might work. This is howvue-jest
works and pre-compiled SFCs seem not to have the same caveats. This would be a long term goal, at least if I need to write the code (I also need to focus on other bugs, vue-jest, VTU v1...).Tagging all the people who have expressed interested in contributing to and improving VTU in the last few weeks. Please give me your input!
Here is a screenshot explaining the problem:
@afontcu @dobromir-hristov @JeremyWuuuuu @JessicaSachs @cexbrayat @Jokcy @angelogulina @AtofStryker