The Wayback Machine - https://web.archive.org/web/20201212162927/https://github.com/vuejs/vue-test-utils/issues/1507
Skip to content
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

How to test an object property of a child functional component (inside a non-functional component)? #1507

Open
matanshukry opened this issue Apr 14, 2020 · 19 comments

Comments

@matanshukry
Copy link

@matanshukry matanshukry commented Apr 14, 2020

Version

1.0.0-beta.33

Reproduction link

https://codesandbox.io/s/vue-template-yy6q9

Steps to reproduce

  1. Create a component that uses a functional component with a property of type object
  2. Use said functional component in template, and pass the property as an object
  3. (3.A) Try and use the .props() method - can't use it since the child component is a functional component
  4. (3.B) Try and use the .attributes() method. It will return the object as string "[object Object]", so you can't test the actual value of it.

What is expected?

To have a way to test the value passed as an object property to a child functional component

What is actually happening?

There is no way to test the value passed as an object property to a child functional component

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 17, 2020

How does your functional child use the prop? Presumably to render in the DOM (that's all functional components do).

You can test it by using mount and assert against the rendered DOM, not the passed props/attrs.

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 17, 2020

I tried to do that, but then I need to check the "href" attribute of the result; which means the route need to be resolved and I also need to put the app routes on it.

I tried to go that way as well, but I can't get it to work either; one of the nested components (BLink from vue-bootstrap specifically) uses this.$router , which is undefined when installing VueRouter on the localVue.

It works if I'm installing VueRouter on the global Vue, but this mess up mocking $route for other tests (as you probably know).

Code on using local vue that ends up with an undefined this.$router:

import VueRouter from "vue-router";
import router from "@/router";

const localVue = createLocalVue();
localVue.use(VueRouter);

// import Vue from 'vue';
// Vue.use(VueRouter);

const wrapper = mount(MyComponent, { localVue, router });
console.log("Html: ", wrapper.html());

As said, only when the 2 commented out lines are "commented-in" the result .html() will contain a valid href (and not just "#")

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 17, 2020

I managed to create a small sandbox code that shows the problem with the global vue (using mount like you suggested @lmiller1990 ):
https://codesandbox.io/s/vue-template-g6wqb

Currently if you run the test it will fail. If you go to MyComp.test.js you'll see a commented line (installing VueRouter globally). Uncomment it and the test will succeed.

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 17, 2020

This should work. I will debug this now.

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 18, 2020

I looked into this. I tried <b-nav-item :to="{ name: 'home' }">home</b-nav-item> using Jest locally and in an actual browser and both give "#" as the route. I don't think this is a bug in VTU, but the bootstrap nav component (or the usage).

I verified this here: https://gist.github.com/lmiller1990/f6b9f6c5fa28e4e6cb4089b5765cb4c4

If you run that in a browser you will see the normal <router-link> works fine, but <b-nav-link>

Original example from here: https://router.vuejs.org/guide/essentials/named-routes.html

You may need to investigate BootstrapVue and how it works with the router. On first look, their link component does not seem to check if the to prop is an object: https://github.com/bootstrap-vue/bootstrap-vue/blob/8241644477b174042bb163ba1741c3066165d9f9/src/components/link/link.js#L101

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 18, 2020

@lmiller1990 I'm confused - you do realize the codesandbox link I gave does work if you use VueRouter globally, even with VTU? That is, it does NOT give "#".

I haven't actually tried your gist, but I'm pretty sure it's not working due to case sensitive.

That is, the route is 'home' (lowercase 'h')

    { path: '/', name: 'home', component: Home },

while the nav item name is Home (uppercase 'H')

        <b-nav-item :to="{ name: 'Home' }">Home</b-nav-item>

Unlike the router links which all uses lowercase, matching the routes.

Regarding vue bootstrap link: the important part (regarding my issue) is actually computeTag and not computeHref. That is, computeTag will return the acutal tag used. If the tag is 'router-link' (which it should be), it will pass the to prop to it and there's no problem.

However, when installing VueRouter locally (unlike globally), the $router is undefined and hence the computeTag will return 'a' rather than 'router-link'. Line:

https://github.com/bootstrap-vue/bootstrap-vue/blob/8241644477b174042bb163ba1741c3066165d9f9/src/utils/router.js#L93

p.s.
To clarify, I know all that by debugging locally

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 18, 2020

I think I missed that you had the global usage commented out. I see what you are explaining now with the global router.

One you you forgot was localVue.use(BootstrapVue). Your localVue doesn't know about the bootstrap components.

I'll investigate some more. Do you have a real repo by any chance? It is very difficult to debug on code sandbox - constantly I get random errors like "record is undefined" for no reason. It not, no problem, I can copy paste from the sandbox.

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 18, 2020

This is so weird. Any non :to prop renders as object Object. What is going on with :to?

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 18, 2020

I dug deeper.

This is incorrectly returning when using localVue. For some reason BootstrapVue is not finding the locally installed router? Is it using the global vue instance instead maybe? There is a function in BoostrapVue that does isRouterLink(tag) and returns true if the tag is a - which it is in this example.

I think this particular bug is unique to BoostrapVue. As you demonstrated, regular functional components receive their props (as object Object).

BootstrapVue seems to do a bunch of stuff to integrate with VueRouter, something is going on there. This will require some more digging into BootstrapVue, I think. Do you know their codebase well? Maybe we can work together on this.

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 18, 2020

@lmiller1990 First let me say thanks for looking into it!

And I have a code base but it's only local on my computer and it's not something I can share.
I can debug any place if there's something specific you want me to.

from my debugging, the code you referred to in isRouterLink is not the problem. The problem is the tag that's passed to it.

The problem is with computeTag. Specifically the part where it's checking thisOrParent.$router which is undefined, which results in a the ANCHOR_TAG rather than the router-link tag.

I can debug a bit more to see why the $router is null, but I'm not sure when it's suppose to be "filled". One thing that could be is that when installing Vue-Router locally, this.$router is null at the render() hook method, which is where computeTag is being used. Not sure how to check ti though

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 18, 2020

Sorry, by code base I meant the BootstrapVue code base.

Something weird is definitely happening where BootstrapVue doesn't know about the mock router. I don't know if I can look too much more into it, but if anything comes to mind I will post it here 🤔

The fact it works w/ the global router is super weird to me. My guess is BootstrapVue does import Vue somewhere and it is getting the non local vue, but global one.

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 18, 2020

@lmiller1990 The BootstrapVue code base is open source on github: https://github.com/bootstrap-vue/bootstrap-vue

Also, the code that calls computeTag() is simply passing "this", where this.$router is undefined as well

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 18, 2020

@lmiller1990 Got a simpler code for you to check:

https://codesandbox.io/s/vue-template-bj9he

I pretty much copied the 'BLink' and 'BNavItem' code in there, but reduced a lot of code that's unrelated.

So now a router-link will always be created, but without the global router the path will either be "#" (incorrect) or "/hello" (correct).

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 20, 2020

^ Yep thanks, I ended up making my own case just like this. I think we need to do a deep dive in the bootstrap-vue codebase and explore how it is using vue router.

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 20, 2020

@lmiller1990 What? are you sure you looked at the code I provided?

To be clear, the code I provided doesn't use Bootstrap-vue at all. There are leftovers in there, but it's not part of the test.

Here, I even removed the import lines and the package.json usage, so now there's no mention to bootstrap-vue other than comments:
https://codesandbox.io/s/vue-template-2upde

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 20, 2020

👀

I'm silly, I assumed something incorrectly without properly looking at the code - I was looking at some left overs from previously.

This is very good minimal repro. I'll make a repo locally with this and debug a bit - sandbox does not appear to expose node_modules :(

I guess we are doing something incorrectly with functional components?

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 20, 2020

@lmiller1990 all good :)

I'm not too familiar with functional components to be honest. I suspected as well, which is why I've tried to make MyNav non functional, but since I'm not familiar with it enough it threw some errors I didn't know how to handle. I might give it another try tomorrow

@lmiller1990
Copy link
Member

@lmiller1990 lmiller1990 commented Apr 20, 2020

I don't see anywhere in our codebase where we do anything special with functional components except for the shallowMount hacks. This reproduction is very good though, let's dig a bit deeper, probably with a log console.logs and debug statements. Thanks @matanshukry!

@matanshukry
Copy link
Author

@matanshukry matanshukry commented Apr 20, 2020

@lmiller1990 Made it even smaller now:

https://codesandbox.io/s/vue-template-v2urw

Changes:

  • MyNav changed to MyNav2.vue. Using vue SFC, and it's really short
  • Removed a lot of unnecessary code from MyLink.js. It's really short now.
  • The test will fail, but if you remove the functional keyword in template functional in MyNav2.vue, it will pass

So it's either something with functional or something with the short code in MyNav.js. Hopefully it helps more!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.