vuejs / vue-test-utils

Component Test Utils for Vue 2
https://vue-test-utils.vuejs.org
MIT License
3.57k stars 669 forks source link

TypeError: Cannot read property 'split' of undefined #839

Closed PratsBhatt closed 6 years ago

PratsBhatt commented 6 years ago

Version

1.0.0-beta.20

Reproduction link

https://codesandbox.io/s/mzlzo73j7p

Steps to reproduce

Sorry unable to provide with the code as I would have to do a lot of boiler plate code as there are few dependencies such as Vuetify, Vuex and graphql.

I am providing a pseudo code. You can find the code in the test/test.js

Issue: When I am trying to test a button click event I am getting error 'TypeError: Cannot read property 'split' of undefined'. On button click an action is triggered that triggers mutation and updates the store. I am writing integration tests that actually mocks the graphql server and provides with the fake data.

Note: I have vuex store. I am using Vuetify as the component library.

Steps to Reproduce: const btn = wrapper.findAll({name: 'v-btn'}); btn.at(1).trigger('click'); After this I get above mentioned error.

I would like to know if there is something I can do to resolve the issue.

What is expected?

Expected behavior is that on the button click event the store should get updated with the mock data that was supplied.

What is actually happening?

Actually error is occurring - TypeError: Cannot read property 'split' of undefined


I have read few issue surrounding this which comments on transition. I have followed https://github.com/vuejs/vue-test-utils/issues/52 and applied stubs: { 'transition': TransitionStub, 'transition-group': TransitionGroupStub } With no success. I am not aware of what is actually happening it would be great if anybody can throw some light.

eddyerburgh commented 6 years ago

Without a reproduction I can't debug. But this might be an issue with the sync mode.

Try set the sync mounting option to false and use Vue.nextTick/ setTimeout to await watcher updates:

test('use Vue.nextTick', (done) => {
  const wrapper = mount(TestComponent, { sync: false })
  wrapper.trigger('click')
  Vue.nextTick(() => {
    expect(wrapper.text()).toBe('updated')
    done()
  })
})
PratsBhatt commented 6 years ago

Hello @eddyerburgh I understand your point, I will try to create a repo asap. Actually there are too many dependencies and that was the reason I wrote psuedo code only.

I tried sync=false, didn't work I got the same error.

console.error node_modules/vue/dist/vue.runtime.common.js:1739 TypeError: Cannot read property 'split' of undefined

In the mean while I will try to provide with more details. Actually I have 2 more test cases above that checks the data when entered in the text box. They all run fine.

It would be helpful if you could throw some more light on this matter. Thank you once again. Great work.

eddyerburgh commented 6 years ago

Unfortunately there isn't anything I can do without a reproduction, can you create a minimal reproduction for me?

martosoler commented 6 years ago

Hi,

I'm having a similar error when testing a simple Login component:

TypeError: Cannot read property 'split' of undefined
          at getTransitionInfo (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7005:58)
          at VueComponent.hasMove (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7935:18)
          at VueComponent.updated (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7881:35)
          at callHook (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:2919:21)
          at callUpdatedHooks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3020:7)
          at flushSchedulerQueue (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3005:3)
          at Array.<anonymous> (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1835:12)
          at flushCallbacks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1756:14)
          at <anonymous>
          at process._tickCallback (internal/process/next_tick.js:188:7)

The component is:

<template>
  <v-card>
    <v-form @submit.prevent="submit">
      <v-card-title primary-title class="primary white--text">
        <div class="headline">Login</div>
      </v-card-title>
      <v-card-text>
        <v-text-field
          v-validate="'required|email'"
          v-model="email"
          :error-messages="errors.collect('email')"
          label="E-mail"
          data-vv-name="email"
          required
        ></v-text-field>
        <v-text-field
          v-validate="'required|min:6'"
          v-model="password"
          :error-messages="errors.collect('password')"
          label="Password"
          data-vv-name="password"
          required
          :append-icon="showPassword ? 'visibility_off' : 'visibility'"
          :type="showPassword ? 'text' : 'password'"
          @click:append="showPassword = !showPassword"
        ></v-text-field>
        <span v-if="this.erroLogin" class="v-messages error--text">{{ this.erroLogin }}</span>
      </v-card-text>
      <v-card-actions class="pa-3">
        <router-link :to="{ name: 'forgot' }">Forgot Password?</router-link>
        <v-spacer></v-spacer>
        <v-btn color="primary" type="submit" id="login-btn">Login</v-btn>
      </v-card-actions>
    </v-form>
  </v-card>
</template>
<script>
import firebase from 'firebase';

export default {
  name: 'login',
  data() {
    return {
      email: '',
      password: '',
      showPassword: false,
      erroLogin: null,
    };
  },
  methods: {
    submit() {
      this.$validator.validateAll().then(isValid => {
        if (isValid) {
          firebase
            .auth()
            .signInWithEmailAndPassword(this.email, this.password)
            .then(
              () => {
                this.erroLogin = null;
                this.$router.push({ name: 'home' });
              },
              err => {
                this.erroLogin = this.getErrorMessage(err.code);
              },
            );
        }
      });
    },
    getErrorMessage(errorCode) {
      ...
    },
  },
};
</script>

And the test:

import Vue from 'vue';

import Vuetify from 'vuetify';
import { mount, createLocalVue } from '@vue/test-utils';
import VeeValidate from 'vee-validate';
import VueRouter from 'vue-router';
import Login from './Login';

jest.mock('firebase');

Vue.config.silent = true;

const localVue = createLocalVue();

localVue.use(Vuetify);
localVue.use(VeeValidate);
localVue.use(VueRouter);

describe('Login', () => {
  describe('Error handling', () => {
    const routes = [
      { path: '/forgot', name: 'forgot' },
    ];

    const router = new VueRouter({ routes });

    const wrapper = mount(Login, {
      localVue,
      router,
      sync: false,
    });

    it('should call Firebase login', (done) => {
      wrapper.setData({ email: 'test@email.com', password: 'password' });

      const loginButton = wrapper.find('#login-btn');
      loginButton.trigger('click');

      Vue.nextTick(() => {
        console.log(wrapper.vm.$route.name);
        done();
      });
    });
  });

What's weird is that the test passes but the console is showing that stacktrace.

The project is using Vue, Vuetify and VeeValidate.

PratsBhatt commented 6 years ago

Hello @martosoler,

Thank you for providing with a reproduction code. I have seen when the code is as below then the test fails on execution.

it('should call Firebase login', () => { wrapper.setData({ email: 'test@email.com', password: 'password' }); return wrapper.vm.$nextTick().then(() => { const loginButton = wrapper.find('#login-btn'); loginButton.trigger('click'); console.log(wrapper.vm.$route.name); }); I am not sure if it's the correct way to do it actually.

martosoler commented 6 years ago

I tried upgrading the version of vue-test-utils to the lastest (1.0.0-beta.23) but the issue is still there, the only new thing I have found is that the stacktrace appears every time I use flushPromises():

it('should go to home after sucessfull login', async () => {
      authentication.login = jest.fn(() => Promise.resolve(''));

      wrapper.setData({ email: 'test@test.com', password: 'password' });
      await flushPromises();

      wrapper.find('#login-btn').trigger('click');
      await flushPromises();

      expect(authentication.login).toHaveBeenCalledWith('test@test.com', 'password');
      expect(wrapper.vm.$route.name).toBe('home');
    });

Note: I have abstracted Firebase library call into a authentication module So, in the above call the following stacktrace appears twice:

 PASS  src/components/User/Login/Login.spec.js
  ● Console

    console.error node_modules/vue/dist/vue.runtime.common.js:1739
      TypeError: Cannot read property 'split' of undefined
          at getTransitionInfo (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7005:58)
          at VueComponent.hasMove (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7935:18)
          at VueComponent.updated (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7881:35)
          at callHook (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:2919:21)
          at callUpdatedHooks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3020:7)
          at flushSchedulerQueue (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3005:3)
          at Array.<anonymous> (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1835:12)
          at flushCallbacks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1756:14)
          at <anonymous>
    console.error node_modules/vue/dist/vue.runtime.common.js:1739
      TypeError: Cannot read property 'split' of undefined
          at getTransitionInfo (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7005:58)
          at VueComponent.hasMove (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7935:18)
          at VueComponent.updated (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7881:35)
          at callHook (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:2919:21)
          at callUpdatedHooks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3020:7)
          at flushSchedulerQueue (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3005:3)
          at Array.<anonymous> (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1835:12)
          at flushCallbacks (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:1756:14)
          at <anonymous>

I can see that the error is being thrown in the getTransitionInfo function but I'm not using transitions anywhere in my code, I suspect Vuetify or VeeValidate might be using it internally.

eddyerburgh commented 6 years ago

Yes @martosoler that's an issue with an un stubbed TransitionGroup. Are you able to provide a reproduction?

The error is thrown because you're running in an environment without transition styles (I'm guessing JSDOM). A quick workaround is to monkey patch window.getComputedStyles before you run your tests:

const { getComputedStyle } = window
window.getComputedStyle = function getComputedStyleStub(el) {
    return {
        ...getComputedStyle(el),
        transitionDelay: '',
        transitionDuration: '',
        animationDelay: '',
        animationDuration: '',
    }
}
tbsvttr commented 6 years ago

I have the same problem with:

I mount all my test wrappers with sync: false.

@eddyerburgh That monkey patch solved the issue for me. Any chance to solve this in a more clean way?

eddyerburgh commented 6 years ago

Yes, this is an issue with transition not stubbed correctly, but im unable to reproduce the bug in order to fix it. Are you able to provide a reproduction?

On Mon, 6 Aug 2018, 18:00 Tobias Vetter, notifications@github.com wrote:

I have the same problem with:

  • Vue 2.5.16
  • Vuetify 1.1.9
  • Vue Test Utils 1.0.0 Beta 16
  • Jest 23.1.0 (and a lot of other stuff)

I mount all my test wrappers with sync: false.

@eddyerburgh https://github.com/eddyerburgh That monkey patch solved the issue for me. Any chance to solve this in a more clean way?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/vuejs/vue-test-utils/issues/839#issuecomment-410777480, or mute the thread https://github.com/notifications/unsubscribe-auth/AMlbW2e2QEdq5n829pj5X9GJJtA_LFyDks5uOHZEgaJpZM4VYQPj .

Mourdraug commented 6 years ago

@eddyerburgh I was finally able to reproduce this in simpler project, I tried to simplify it more, but simple move-transition didn't trigger it, so it still uses Vuetify. There are 2 identical specs, only one uses workaround you provided.

https://github.com/Mourdraug/transition-bug-repro

zeroarst commented 5 years ago

For me I need to do both things to completely pass the test and get rid of error in console.

const { getComputedStyle } = window
window.getComputedStyle = function getComputedStyleStub(el) {
    return {
        ...getComputedStyle(el),
        transitionDelay: '',
        transitionDuration: '',
        animationDelay: '',
        animationDuration: '',
    }
}
test('use Vue.nextTick', (done) => {
  const wrapper = mount(TestComponent, { sync: false })
  wrapper.trigger('click')
  Vue.nextTick(() => {
    expect(wrapper.text()).toBe('updated')
    done()
  })
})

This happens when the test dimisses the VueMaterial MdDialog.