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

Failed to mount when returning render function from setup() while testing with Jest and vue-jest #151

Closed
longnh301 opened this issue Oct 2, 2019 · 22 comments · Fixed by #383

Comments

@longnh301
Copy link

longnh301 commented Oct 2, 2019

Code snippet:

// menu.vue
export default {
  name: 'Menu',
  setup(props, context) {
    // logic here...
    return () => h('div', options, children)
  }
}

// test.js
const localVue = createLocalVue()
localVue.use(VueCompositionApi)

describe('Menu', () => {
  it('should render', () => {
    const wrapper = mount(Menu, {localVue})
    expect(wrapper.html()).toMatchSnapshot()
  })
})

which returns the following error

[Vue warn]: Failed to mount component: template or render function not defined. 

I'm currently using jest 24.9.0, @vue/test-utils and vue-jest 3.0.5. Other tests work fine with templated SFCs using composition API

@sduduzog
Copy link

sduduzog commented Dec 7, 2019

I just posted a question on stackoverflow for exacltly this. Here's a copy paste version of it

I am trying to test a jsx component by passing props to it and asserting that it renders them correctly. This fails though for a reason I cannot understand. Here's the component

// components/toast.tsx

import { createComponent } from "@vue/composition-api";
import style from "./toast.module.scss";
export default createComponent({
  name: "Toast",
  props: {
    message: String
  },
  setup(props) {
    return () => (
      <div class={style.toast}>
        <p>{props.message}</p>
      </div>
    );
  }
});

Here's the snippet of the unit test that is failing:

// tests/unit/components/toast.spec.ts

import Toast from "@/components/toast";
import { shallowMount } from "@vue/test-utils";

// ... a describe block

  it("should render props correctly", () => {
    const msg = "hello";
    const wrapper = shallowMount(Toast, {
      propsData: {
        message: msg
      }
    });
    expect(wrapper.isVueInstance()).toBeTruthy();
    expect(wrapper.text()).toMatch(msg);
  });

And the output from the failing test is here:

Expected substring: "hello"
    Received string:    "function (el) {
          var obj;·
          var args = [], len = arguments.length - 1;
          while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
          if (shouldNotBeStubbed(el, stubs, modifiedComponents)) {
            return originalCreateElement.apply(void 0, [ el ].concat( args ))
          }·
          if (isConstructor(el) || isComponentOptions(el)) {
            if (stubAllComponents) {
              var stub = createStubFromComponent(el, el.name || 'anonymous', _Vue);
              return originalCreateElement.apply(void 0, [ stub ].concat( args ))
            }
            var Constructor = shouldExtend(el, _Vue) ? extend(el, _Vue) : el;·
            return originalCreateElement.apply(void 0, [ Constructor ].concat( args ))
          }·
          if (typeof el === 'string') {
            var original = resolveComponent(el, originalComponents);·
            if (!original) {
              return originalCreateElement.apply(void 0, [ el ].concat( args ))
            }·
            if (isDynamicComponent(original)) {
              return originalCreateElement.apply(void 0, [ el ].concat( args ))
            }·
            var stub$1 = createStubIfNeeded(stubAllComponents, original, _Vue, el);·
            if (stub$1) {
              Object.assign(vm.$options.components, ( obj = {}, obj[el] = stub$1, obj));
              modifiedComponents.add(el);
            }
          }·
          return originalCreateElement.apply(void 0, [ el ].concat( args ))
        }"

      16 |     });
      17 |     expect(wrapper.isVueInstance()).toBeTruthy();
    > 18 |     expect(wrapper.text()).toMatch(msg);
         |                            ^
      19 |   });
      20 | });
      21 | 

      at Object.it (tests/unit/components/toast.spec.ts:18:28)

Everything works! except for this one part. If I use a single file component (.vue) the component mounts and the test passes with no problem.

@vip30
Copy link

vip30 commented Dec 27, 2019

I have the same problem on using tsx with composition api in jest

and if I log the wrapper html it is just an empty string while a .vue will return the raw html

@sduduzog
Copy link

I've gone back to using .vue component files to avoid this. I do however suspect that this might not have anything to do with the composition api and it's just on tsx

@lmiller1990
Copy link
Member

I was able to use this plugin with Vue Test Utils fine - I suspect it's related to tsx. Simple demo of VTU + composition API here.

@sduduzog
Copy link

sduduzog commented Jan 8, 2020

@lmiller1990 The render function not playing nicely. Which also suggests that this issue should be closed and opened somewhere else

@lmiller1990
Copy link
Member

Does this render in a browser? Or does the error only happen in testing? Could be related to Jest not knowing how to read TSX.

@sduduzog
Copy link

sduduzog commented Jan 8, 2020

It only happens in tests. That also makes sense. I tried this with the simplest HelloWorld component. Maybe my project might not be configured corectly. I wish there was a reference sample should this be a non issue

@lmiller1990
Copy link
Member

lmiller1990 commented Jan 8, 2020

I have had the problem before too. Did you make your project with Vue cli? Last time I did TS + TSX, I chose "include babel alonside TypeScript" option, and installed Vue TSX support with vue add tsx-support. A bit more info here.

Anyway I think this is not a problem with this repo... try the above, you can open an issue in Vue Test Utils if you are still stuck and I can try to help there.

@longnh301
Copy link
Author

Update: I managed to run tests using configurations like so, the component uses jsx

import Vue from 'vue/dist/vue.common.js'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
import GMenu from '../GMenu';

function createElement() {
  if (document) {
    const elem = document.createElement('div')
    if (document.body) {
      document.body.appendChild(elem)
    }
    return elem
  }
}

describe('Menu', () => {
  const template = `
        <div>
          <g-menu v-model="showMenu">
            <template #activator="{toggleContent}">
              <button @click="toggleContent">Activator</button>
            </template>
            <div>content</div>
          </g-menu>
        </div>`

  afterEach(() => {
    document.body.innerHTML = ''
  })

  it('should render slots', () => {
    const vm = new Vue({
      template,
      components: { GMenu },
      data() {
        return {
          showMenu: false
        }
      }
    }).$mount(createElement());
    expect(vm.$el.outerHTML).toMatchSnapshot()
    expect(vm.$children[0].$scopedSlots.default).toBeTruthy()
    expect(vm.$children[0].$scopedSlots.activator).toBeTruthy()
  })
})

@vip30
Copy link

vip30 commented Jan 9, 2020

I have had the problem before too. Did you make your project with Vue cli? Last time I did TS + TSX, I chose "include babel alonside TypeScript" option, and installed Vue TSX support with vue add tsx-support. A bit more info here.

Anyway I think this is not a problem with this repo... try the above, you can open an issue in Vue Test Utils if you are still stuck and I can try to help there.

I tried vue-tsx-support before, jest is worked as expected but composition api is not.
So just wonder if it's because of composition api setup function

@m4rvr
Copy link

m4rvr commented Feb 17, 2020

Any solution found? :/

@lmiller1990
Copy link
Member

Can someone post a repo that reproduces the problem?

@sduduzog
Copy link

sduduzog commented Feb 18, 2020

I've attempted this yesterday, Here's a little example. I did this is a rush because I'm still at work but I hope it provides some context.

Here's my test

 it("renders message", () => {
    const localVue = createLocalVue();
    localVue.use(VueCompositionApi);
    const msg = "Hello, world";
    const wrapper = shallowMount(HelloWorld, { localVue });
    expect(wrapper.isVueInstance()).toBe(true);
    expect(wrapper.vm.$el.textContent).toMatch(msg);
  });

Here's what passes

export default createComponent({
  name: "HelloWorld",
  props: {
    msg: String
  },
  setup() {},
  render() {
    return <div>Hello, world</div>;
  }
}) as VueConstructor;

Here's what doesn't

export default createComponent({
  name: "HelloWorld",
  props: {
    msg: String
  },
  setup() {
    return () => <div>Hello, world</div>;
  }
}) as VueConstructor;

NB: I'm using TSX here,

@lmiller1990
Copy link
Member

I mean, can you post an entire repository someone can pull down a run? There are so many plugins in these examples (composition api, jsx, typescript, jest vue transformer, babel) it's highly likely this is a configuration problem.

@sduduzog
Copy link

I hope this helps https://github.com/sduduzog/vue-composition-api-tsx-example

@LeBenLeBen
Copy link

LeBenLeBen commented Mar 4, 2020

I'm also having this issue in a repo using TypeScript, but without TSX. Here’s a summary of the setup function throwing the warning in Jest:

setup() {
  return () => createElement('div', slots.default());
},

Let me know if you want me to setup another reproduction repo.

@LeBenLeBen
Copy link

LeBenLeBen commented Mar 4, 2020

I've investigated a bit and I found some stuff but not really the origin of the problem.

binding = setup(props, ctx);

In the initSetup function the binding above has two very different values depending if its run in the browser or through Vue Test Utils.

In the browser it properly returns the component setup Function:

function () {
  return Object(_vue_composition_api__WEBPACK_IMPORTED_MODULE_0__["createElement"])('div', slots["default"] && slots["default"]());
}

While being rendered by Vue Test Utils, binding value is an Object:

{ length: 0, name: '' }

This result in the following block being ignored and therefor the render method assignment skipped:

if (isFunction(binding)) {
// keep typescript happy with the binding type.
const bindingFunc = binding;
// keep currentInstance accessible for createElement
vm.$options.render = () => {
resolveScopedSlots(vm, ctx.slots);
return activateCurrentInstance(vm, () => bindingFunc());
};
return;
}

I'm not sure where to look at from here…

@lmiller1990
Copy link
Member

lmiller1990 commented Mar 23, 2020

@sduduzog I cannot install your repo; I get

warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
error An unexpected error occurred: "https://npm.codeo.co.za/@vue%2fcli-plugin-pwa/-/cli-plugin-pwa-4.2.2.tgz: Request failed \"401 Unauthorized\"".
info If you think this is a bug, please open a bug report with the information provided in "/Users/lachlan/code/dump/vue-composition-api-tsx-example/yarn-error.log".

It doesn't look like returning JSX from setup even is supported according to the readme in this repository. How did you get this working? setup doesn't have access to h and it isn't exported in Vue 2 from what I can see.

Has anyone else got that reproduction repo to run? Maybe it's a problem on my end.

@LeBenLeBen
Copy link

@lmiller1990 I was able to make the repo works with npm, I can setup a simplier reproduction without JSX if you like, since it's probably not the problem here.

@sduduzog
Copy link

@sduduzog I cannot install your repo; I get

warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
error An unexpected error occurred: "https://npm.codeo.co.za/@vue%2fcli-plugin-pwa/-/cli-plugin-pwa-4.2.2.tgz: Request failed \"401 Unauthorized\"".
info If you think this is a bug, please open a bug report with the information provided in "/Users/lachlan/code/dump/vue-composition-api-tsx-example/yarn-error.log".

It doesn't look like returning JSX from setup even is supported according to the readme in this repository. How did you get this working? setup doesn't have access to h and it isn't exported in Vue 2 from what I can see.

Has anyone else got that reproduction repo to run? Maybe it's a problem on my end.

Sorry @lmiller1990 please delete the yarn lock file or package.json-lock file then rebuild. I have a private npm registry that we use at work

@xmsz
Copy link

xmsz commented Apr 23, 2020

any update?

@lmiller1990
Copy link
Member

lmiller1990 commented Apr 23, 2020

Yes, read more details here: vuejs/vue-test-utils#1484. Basically it's a conflict between VTU and composition API, both override the render function, VTU to accomplish shallowMount and stubs, and this library to allow a component's setup function to return a render function.

There is a potential fix I describe here, if anyone has some time they can try and implement this in VTU. I have not seen if this breaks anything else in VTU.

Here is the start of a fix vuejs/vue-test-utils#1512 however this makes any tests calling inject in setup error out, since we called setup before the instance exists. Maybe we can move this logic somewhere like a mixin with a beforeCreate hook. The above PR is good place to start debugging this.

If you are working on a small/medium app, or just trying out the composition API and liked it, you can probably upgrade to Vue 3 and VTU next fairly easily to have everything work as expected. Plus all the goodness of Vue 3 like Suspense, Portal etc. I am not sure if JSX works for Vue 3 yet, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants