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

Provide options to adapt the document before mount #98

Closed
ChristophWalter opened this issue Sep 27, 2019 · 12 comments · Fixed by #100
Closed

Provide options to adapt the document before mount #98

ChristophWalter opened this issue Sep 27, 2019 · 12 comments · Fixed by #100
Labels
bug Something isn't working released

Comments

@ChristophWalter
Copy link

ChristophWalter commented Sep 27, 2019

Describe the feature
We are using Vuetify as component library in one of our projects. I would like to introduce vue-testing-library and came up with some setup troubles on the way. Vuetify requires a wrapper element containing the attribute data-app. This element is used to append components like dialog to the root DOM level. The required setup can be done using jest setup files. Thought, the dialog content can not be found when using the exposed queries of the vue-testing-libraries render() method.

To Reproduce
Steps to reproduce the behavior:

  • Init a vue app with vuetify
  • Create a basic dialog
  • write a test which should validate that the dialog content is visible when a button is clicked
  • Add Vuetify and <div data-app></div> to your test DOM instance before mount

=> queries like getByText will not query the dialog content

I tried to create a codesandbox, but failed. Is there a working example for vue-testing-library?
https://codesandbox.io/s/vuetify-with-vue-testing-library-bhu8f?previewwindow=tests

Expected behavior
I expect a configuration option like it exists for react:

  • baseElement: Specify the base element for the queries.
  • container: Specify the surrounding DOM node

Related information:

  • @testing-library/vue version: 3.0.0
  • Vue version: 2.5.18
  • node version: 8.12.0
  • yarn version: 1.17.3
  • Vuetify version: 1.2.10

Relevant code or config (if any)

import CreateLorem from "./CreateLorem";
import { render } from "@testing-library/vue";
import Vuetify from "vuetify";
import Vue from "vue";

var app = document.createElement("div");
app.setAttribute("data-app", true);
document.body.appendChild(app);
Vue.use(Vuetify);

it("should create a lorem", () => {
  const { getByText } = render(CreateLorem);
  getByText("open");
});
@ChristophWalter ChristophWalter added the bug Something isn't working label Sep 27, 2019
@afontcu
Copy link
Member

afontcu commented Sep 28, 2019

Hi! Thanks for the detailed issue.

We've got similar requests/issues through other channels regarding modals and, in general, elements that render outside of the parent component DOM node tree.

I do believe there's a divergence between Vue Testing Lib and React Testing Lib, since their queries are bound to the whole document, not the wrapper of the rendered element.

Bounding queries to the outermost element (the body, as to speak) would fix your issue, wouldn't it? It is something I plan on working soon, and I'll use your example to test it out. I'll keep you posted.

For the time being, and from the top of my head, would creating a custom renderer solve your issue?

import { render as VTLRender } from '@testing-library/vue'

const renderWithVuetify = (comp, options, cb) => {
  return VTLRender({
    // render function
    render(h) {
      // wrap your component with a <div data-app="true"> node
      return h('div', { attrs: { 'data-app': true }}, comp)
    },
    options,
    cb
  })
}


// and then, somewhere else:

const { getByText } = renderWithVuetify(MyComp)

(Can't test it right now, so syntax might be a bit off).

@afontcu
Copy link
Member

afontcu commented Sep 28, 2019

Oh, by the way, Vue.use(Vuetify) should be performed using the callback function provided by render() (example):

const { getByText } = render(MyComp, {}, vue => {
  vue.use(Vuetify)
})

this is something that could be done inside your custom renderer, too, so you don't have to repeat it every time.

@ChristophWalter
Copy link
Author

Thanks for your fast response!

Bounding queries to the outermost element (the body, as to speak) would fix your issue, wouldn't it?

Yes, this would work. 👍

For the time being, and from the top of my head, would creating a custom renderer solve your issue?

I did it quite similar and it works. Just not sure on how to pass all events up, so emitted() still works. Here is the (fixed) example:

import { render as VTLRender } from '@testing-library/vue'

function renderWithVuetify(component, options, callback) {
	return VTLRender(
		{
			render(h) {
				return h('div', { attrs: { 'data-app': true } }, [
					h(component, {
						props: options.props,
					}),
				])
			},
		},
		options,
		callback
	)
}

Oh, by the way, Vue.use(Vuetify) should be performed using the callback function provided by render() (example):

Yeah, it should... But vuetify has an issue with multiple vue instances if it is initialized like that. This might be already fixed in the current version. In the meantime I use the jest setup file to set this once for every test. Here is the vuetify issue: vuetifyjs/vuetify#4068

@afontcu
Copy link
Member

afontcu commented Sep 30, 2019

Hi! Thanks for the detailed answer.

I've set up a PR to try to bound queries to the parent node: #100

afontcu added a commit that referenced this issue Oct 9, 2019
Closes #98 

BREAKING CHANGE: baseElement is no longer tied to the document body, and container is no longer the parent node of the element wrapper, but a wrapper div.

These changes shouldn't affect you if you weren't relying on either `baseElement` or `container`.
@afontcu
Copy link
Member

afontcu commented Oct 9, 2019

🎉 This issue has been resolved in version 4.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bennettdams
Copy link
Contributor

bennettdams commented Nov 13, 2019

Thanks to this conversation I created a wrapper to use Vuetfiy, but there is one thing that I'm not sure about.

I would like to eliminate the need of writing Vue.use(Vuetify) in every test, so I now do that when overwriting the render function as discussed in this conversation.

In the end it is another global Vue instance, so are there any downsides of doing it like that?
Could/should you somehow use the callback of the render function?

export const renderWithVuetify = (component, options, callback) => {
  Vue.use(Vuetify);  // downsides?
  return VTLRender(
    {
      render(h) {
        // wrap the component with a <div data-app="true"> node
        return h("div", { attrs: { "data-app": true } }, [h(component)]);
      }
    },
    options,
    callback
  );
};
// in the test
// const { getByText } = renderWithVuetify(MyComponent);

Or should the test pass its Vue instance via the wrapper function?

export const renderWithVuetify = (component, vueInstance, options, callback) => {
  vueInstance.use(Vuetify);  // downsides?
  return VTLRender(
  ....
// in the test
// const { getByText } = renderWithVuetify(MyComponent, Vue);

@afontcu
Copy link
Member

afontcu commented Nov 13, 2019

Hi! I can't think of any downsides of the top of my head, as long as Vuetify issue with multiple Vue instances is still a thing (I guess it is!). This is something that should be fixed on their own, so I'd say having a comfortable workaround should do the trick 👍

@bennettdams
Copy link
Contributor

bennettdams commented Nov 13, 2019

The example above works only for Vuetify components that don't use the $vuetify instance property

(e. g. <v-menu> will always throw an undefined error in the test).

You can bypass this by using { vuetify: new Vuetify(), ...options },.

For any future readers, here is the complete setup:

Maybe interesting for the documentation/examples?

test-setup.js executed via Jest's setupFiles in the jest.config.js

import Vue from "vue";
import Vuetify from "vuetify";

// We need to use a global Vue instance, otherwise Vuetify will complain about
// read-only attributes.
// More info: https://github.com/vuetifyjs/vuetify/issues/4068
//            https://vuetifyjs.com/en/getting-started/unit-testing
Vue.use(Vuetify);

test-helper.js used in the tests instead of VTL's render

import { render as VTLRender } from "@testing-library/vue";
import Vuetify from "vuetify";

/**
 * Custom render wrapper to integrate Vuetify with Vue Testing Library.
 *
 * Vuetify requires you to wrap your app with a `v-app` component that provides
 * a `<div data-app="true">` node.
 *
 * More info:
 *    https://github.com/vuetifyjs/vuetify/issues/4068
 *    https://vuetifyjs.com/en/getting-started/unit-testing
 *
 * @param {*}           component Component from test to render
 * @param {*}           options Render options
 * @param {() => void}  callback Render callback
 * @return {*}          Render function of VTL including Vuetify `data-app`
 */
export const renderWithVuetify = (component, options, callback) => {
  return VTLRender(
    // anonymous component
    {
      // Vue's render function
      render(createElement) {
        // wrap the component with a <div data-app="true"> node and render the test component
        return createElement("div", { attrs: { "data-app": true } }, [
          createElement(component)
        ]);
      }
    },
    { vuetify: new Vuetify(), ...options },
    callback
  );
};

Actual component test

import { renderWithVuetify } from "../test-helper";
import MyComponent from "../../src/views/MyComponent.vue";

describe("MyComponent.vue", () => {
  test("Some test...", () => {
    const { getByText } = renderWithVuetify(MyComponent);

    expect(getByText(/some text/));
  });
});

@afontcu
Copy link
Member

afontcu commented Nov 15, 2019

Looks like a nice improvement for the Vuetify example. Fancy to open up a PR with the appropriate modifications? 🤗

@bennettdams
Copy link
Contributor

bennettdams commented Nov 15, 2019

Will do!

Update: Done! #114

@emilsgulbis
Copy link

renderWithVuetify helper is great, but unfortunately looks like updateProps doesnt work in this situation, i guess thats because it's trying to update props for data-app wrap component!?

@ChamNouki
Copy link

same things for emitted helper returning the events emitted by data-app wrap component?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working released
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants