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

Some stubbed Vuetify components can't be found with find wrapper method #1272

Closed
anthonygore opened this issue Jul 11, 2019 · 23 comments · Fixed by #1441
Closed

Some stubbed Vuetify components can't be found with find wrapper method #1272

anthonygore opened this issue Jul 11, 2019 · 23 comments · Fixed by #1441

Comments

@anthonygore
Copy link

anthonygore commented Jul 11, 2019

Version

1.0.0-beta.29

Reproduction link

https://github.com/anthonygore/vuetify-test

Steps to reproduce

npm i
npm run test:unit

What is expected?

All tests should pass

What is actually happening?

The second test fails as the VTextField component can't be found.


I don't know if this is a Vuetify issue or a Vue Test Utils issue, but some stubbed child components can't be found with the find wrapper method when the parent is shallow mounted.

It's not just VTextField, either, VSelect also has this problem, while others like VIcon work just fine.

import { shallowMount } from "@vue/test-utils";
import Vuetify from "vuetify";
import { VBtn, VTextField } from "vuetify/lib";

Vue.use(Vuetify);

const component = {
  template: `
    <div>
      <v-btn />
      <v-text-field />
    </div>
  `
};

// Passes
it('should find v-btn', () => {
  const wrapper = shallowMount(component);
  expect(wrapper.find(VBtn).exists()).toBe(true);
});

// Fails
it('should find v-text-field', () => {
  const wrapper = shallowMount(component);
  expect(wrapper.find(VTextField).exists()).toBe(true);
});

Update

I think the issue is something to do with how some global components are registered because this works:

Vue.use(Vuetify);

const component = {
  template: `
    <div>
      <v-btn />
      <v-text-field />
    </div>
  `,
  components: {
    VTextField
  }
};

Note that I don't need to locally register VBtn, just VTextField...

@lmiller1990
Copy link
Member

lmiller1990 commented Jul 11, 2019

Hey @anthonygore , This is likely something to do with the fact VTextField extends from VInput, as shown here in the source code for Vuetify.

Iirc if you pass a component to find it will match against the prototype, and in this case VTextField.prototype !== VInput.prototype it returns false.

A work around would be to pass an object with name to find. So you could try:

it('should find v-text-field', () => {
  const wrapper = shallowMount(component);
  expect(wrapper.find({ name: 'v-text-field' }).exists()).toBe(true);
});

That may work.

@anthonygore
Copy link
Author

Hey @lmiller1990, I tried your solution and it doesn't work. But, I think you're onto something with the extends thing. It seems that all the Vuetify components that don't work with find do extend another component. That gives me a lead to keep researching. Thanks!

@lmiller1990
Copy link
Member

Interesting. I recently got asked to help out with a Vuetify project that has vue-test-utils in it, so I'll have a hack this weekend and see if I can figure this out (likely I will run into this soon as well).

@anthonygore
Copy link
Author

Champion, thank you.

@lmiller1990
Copy link
Member

Happens to v-list-tile-title too: https://github.com/vuetifyjs/vuetify/blob/6f32c6439a4c789ac992794f5d514db6fec4a5fc/packages/vuetify/src/components/VList/index.ts#L12

There is currently no way to find a functional component - I doubt this will change.

@anthonygore
Copy link
Author

anthonygore commented Jul 13, 2019

Are you sure? v-icon is functional and I can find that one.

import { VIcon } from "vuetify/lib";

it('should find v-icon', () => {
  const wrapper = shallowMount(component);
  expect(wrapper.find(VIcon).exists()).toBe(true); // Passes
});

@lmiller1990
Copy link
Member

Hm, I guess that isn't what prevents me finding v-list-tile-title.

I wonder what the common thing that prevents find from working...

@thejcannon
Copy link
Contributor

I'm having this issue as well in my own library (not using or related to Vuetify).

In my jest setup I use Vue.component('FooBar', require('.../path/to/FooBar.vue').default)
In my test I import 'FooBar' from '.../path/to/FooBar.vue'
I provide stubs: {FooBar: true} in my options when mount-ing (not shallow)

Then wrapper.find(FooBar) fails, even though I'm using <foo-bar .../> in the component.

I've noticed that wrapper.find('foobar-stub') works, but the props are empty so testing it is moot.

@thejcannon
Copy link
Contributor

I find it very interesting too that the stub I found is named foobar (without the dash!)

@thejcannon
Copy link
Contributor

thejcannon commented Jul 18, 2019

I still don't understand why wrapper.find(VBtn) works above, but your update makes sense if you look at the code

When stubbing, vue-test-utils uses the component's components options. So without providing any components (relying on the ones already given to Vue) then it will fail to use the component to stub and make a new stub component instead.
In your update, since you pass the components option, it'll find it correctly.

Seems to me that line 56 could be changed to Object.assign(Object.create(_Vue.options.components), componentOptions.components) to also reference the globally registered components.

Testing this out locally did the trick for me.

@thejcannon
Copy link
Contributor

thejcannon commented Jul 18, 2019

As a workaround before I declare my tests I do:

ComponentUnderTest.components = Object.assign(Object.create(Vue.options.components), ComponentUnderTest.components || {});

And then the stubs work like a charm

EDIT: Looks like I can find FooBar now (and test it's props), but triggering events isn't working as expected.

@aislanmaia
Copy link

Are you sure? v-icon is functional and I can find that one.

import { VIcon } from "vuetify/lib";

it('should find v-icon', () => {
  const wrapper = shallowMount(component);
  expect(wrapper.find(VIcon).exists()).toBe(true); // Passes
});

For me, I cant use wrapper.find with a VBtn, for example. The console returns:

 TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
    tests/unit/infosets/components/InfosetsHeader.spec.ts:17:25 - error TS2345: Argument of type 'Component<DefaultData<never>, DefaultMethods<never>, DefaultComputed, Record<string, any>>' is not assignable to parameter of type 'NameSelector'.
      Type 'FunctionalComponentOptions<Record<string, any>, PropsDefinition<Record<string, any>>>' is not assignable to type 'NameSelector'.
        Types of property 'name' are incompatible.
          Type 'string | undefined' is not assignable to type 'string'.
            Type 'undefined' is not assignable to type 'string'.

    17     expect(wrapper.find(VBtn).exists()).toBe(true);

@lmiller1990
Copy link
Member

@aislanmaia I noticed I could find some functional components too - I can't see any common pattern that makes a component unfindable at this point.

@aislanmaia
Copy link

@lmiller1990 I've some progress here, but in the same point 0 again.
Take a look:

 InfosetsHeader.component
    ✓ renders Minhas Listas at top when rendered (3ms)
    ✕ renders a Button component to create new lists (13ms)

  ● InfosetsHeader.component › renders a Button component to create new lists

    expect(received).toBe(expected) // Object.is equality

    Expected: true
    Received: false

      32 |     const btn = wrapper.find(".v-btn");
      33 |     // console.log("btn", btn);
    > 34 |     expect(btn.exists()).toBe(true);
         |                          ^
      35 | 
      36 |     /* mock event */
      37 |     const event = jest.fn();

      at Object.<anonymous> (tests/unit/infosets/components/InfosetsHeader.spec.ts:34:26)

The v-btn and any vuetify components are unfindable.
The current state is that Vuetifiy is just untestable at all, at least with Jest.
Things like that, just increase the Javascript fatigue for me, because IMHO, things like this (testing, etc), should work out-of-box really.

@lmiller1990
Copy link
Member

It feels like Vuetify is responsible for a lot of problems around vue-test-utils, I've had an equally frustrating experience (with attempting to test Vuetify based apps).

One work-around you can try is adding <v-btn data-test="button"> ..... </v-btn> and trying to do wrapper.find('[data-test="button"]') and see if you can find it that way.

@aislanmaia
Copy link

Don't work either. It seems that the element isn't there yet, or maybe it never wasn't there in the component at the moment the test is running.

@lmiller1990
Copy link
Member

lmiller1990 commented Nov 21, 2019

What if you do console.log(wrapper.html({ pretty: true })) (not sure if the pretty: true API is correct or not, been a while). Can you see the markup you expect?

If you are using post vue-test-utils beta 28, the way rendering updates are applied changed a bit. You might need to do await wrapper.vm.$nextTick() to ensure Vue has had time to update the DOM.

@aislanmaia
Copy link

aislanmaia commented Nov 22, 2019

What if you do console.log(wrapper.html({ pretty: true })) (not sure if the pretty: true API is correct or not, been a while). Can you see the markup you expect?

If you are using post vue-test-utils beta 28, the way rendering updates are applied changed a bit. You might need to do await wrapper.vm.$nextTick() to ensure Vue has had time to update the DOM.

It doesn't accept a parameter like pretty. Just calling wrapper.html() is enough to get returned the html, however in unformatted way.

The markup returned show up some Vuetify components marked with the "-stub" in the names.
It happens that even referencing this names like wrapper.find('.v-btn-stub') the "find" cannot return my Vuetify components.

@lmiller1990
Copy link
Member

I might take (another) look at this one this week. Whoever solves this will be a hero; this issue has plagued us for a while. I have noticed this only happens sometimes, especially in larger projects. Have you had a similar experience?

@aislanmaia
Copy link

@lmiller1990 no similar, because our project is in early stages and not considered big right now. Saying that, this is our first unit tests with Vuetify, and it is disappointing that it not working at all with jest.

@Lewenhaupt
Copy link

@aislanmaia I just found that removing the . actually solves this. When running the wrapper.html() I saw that the stubs aren't actually divs or similar with classes applied but actually a v-btn-stub element.

What if you do console.log(wrapper.html({ pretty: true })) (not sure if the pretty: true API is correct or not, been a while). Can you see the markup you expect?
If you are using post vue-test-utils beta 28, the way rendering updates are applied changed a bit. You might need to do await wrapper.vm.$nextTick() to ensure Vue has had time to update the DOM.

It doesn't accept a parameter like pretty. Just calling wrapper.html() is enough to get returned the html, however in unformatted way.

The markup returned show up some Vuetify components marked with the "-stub" in the names.
It happens that even referencing this names like wrapper.find('.v-btn-stub') the "find" cannot return my Vuetify components.

@lmiller1990
Copy link
Member

Regarding Vuetify, what I am currently doing is just using mount and doing things on the raw <input /> or <button /> that VInput and VBtn render. I'm starting to think shallowMount should be avoided wherever possible - it's not ideal to be testing against something different (eg stubs) to your actual component.

We are exploring better ways to test third party UI components for the Vue 3 integration.

@thejcannon
Copy link
Contributor

@aislanmaia Have you tried my workaround above?

thejcannon added a commit to thejcannon/vue-test-utils that referenced this issue Feb 20, 2020
thejcannon added a commit to thejcannon/vue-test-utils that referenced this issue Feb 20, 2020
When specifying the components to stub, merge the globally registered components into the locally registered ones. (fix vuejs#1272)
thejcannon added a commit to thejcannon/vue-test-utils that referenced this issue Feb 22, 2020
When specifying the components to stub, merge the globally registered components into the locally registered ones. (fix vuejs#1272)
lmiller1990 pushed a commit that referenced this issue Feb 25, 2020
When specifying the components to stub, merge the globally registered components into the locally registered ones. (fix #1272)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants