Vuetify: Cannot read property 't' of undefined when testing components with v-data-table

Created on 14 Aug 2018  路  11Comments  路  Source: vuetifyjs/vuetify

Versions and Environment

Vuetify: 1.1.9
Vue: 2.5.2
Vue Test Utils: 1.0.0-beta.23
Browsers: JSDom
OS: High Sierra 10.13.4

Steps to reproduce


Companies.vue

<template>
  <div>
    <v-container grid-list-md text-xs-center>
      <v-layout row wrap>
        <v-flex xs12>
          <v-card>
            <v-data-table
              :headers="headers"
              :items="items"
              :loading="loading"
              class="elevation-1"
            >
              <v-progress-linear slot="progress" color="blue" indeterminate></v-progress-linear>
              <template slot="headerCell" slot-scope="props">
                <v-tooltip bottom>
                  <span slot="activator">
                    {{ props.header.text }}
                  </span>
                  <span>
                    {{ props.header.text }}
                  </span>
                </v-tooltip>
              </template>
              <template slot="items" slot-scope="props">
                <td>{{ props.item.name }}</td>
                <td>{{ props.item.contactName }}</td>
                <td>{{ props.item.contactEmail }}</td>
                <td class="justify-center layout px-0">
                  <v-icon
                    small
                    class="mr-2"
                    @click="viewItem(props.item)"
                  >
                    domain
                  </v-icon>
                  <v-icon
                    small
                    class="mr-2"
                    @click="editItem(props.item)"
                  >
                    edit
                  </v-icon>
                  <v-icon
                    small
                    @click="deleteItem(props.item)"
                  >
                    delete
                  </v-icon>
                </td>
              </template>
              <template slot="no-data">
                <v-btn color="primary" flat @click="createNewCompany">
                  Add company
                  <v-icon right dark>add_box</v-icon>
                </v-btn>
              </template>
            </v-data-table>
          </v-card>
        </v-flex>
        <v-flex xs12>
          <v-btn
            color="primary"
            class="white--text"
            @click="createNewCompany"
          >
            Create New Company
            <v-icon right dark>domain</v-icon>
          </v-btn>
        </v-flex>
      </v-layout>
      <ConfirmModal ref="Confirm"></ConfirmModal>
    </v-container>
  </div>
</template>
<script>
import vuetifyToast from 'vuetify-toast';
import { getCompanies, deleteCompany } from '@/services/database/company';
import { ConfirmModal } from '@/components/commons';

export default {
  name: 'companies',
  data() {
    return {
      headers: [
        { text: 'Company', align: 'center', value: 'name' },
        { text: 'Contact Name', align: 'center', value: 'contactName' },
        { text: 'Contact Email', align: 'center', value: 'contactEmail' },
        { text: 'Actions', value: 'name', sortable: false },
      ],
      items: [],
      loading: false,
      selectedItem: null,
    };
  },
  mounted() {
    this.loadInfo();
  },
  methods: {
    loadInfo() {
      this.items = [];
      this.loading = true;

      getCompanies()
        .then(companies => {
          this.items = companies.map(c => ({
            id: c.id,
            name: c.value.name,
            contactName: c.value.contact.name,
            contactEmail: c.value.contact.email,
          }));

          this.loading = false;
        })
        .catch(() => {
          this.loading = false;
          vuetifyToast.error('Error getting companies');
        });
    },
    viewItem(item) {
      this.$router.push({
        name: 'company',
        params: { companyId: item.id, originPage: 'companies' },
      });
    },
    editItem(item) {
      this.$router.push({
        name: 'companyDetail',
        params: { companyId: item.id, originPage: 'companies' },
      });
    },
    deleteItem(item) {
      this.selectedItem = item;
      this.$refs.Confirm.open(
        `Are you sure you want to delete company ${this.selectedItem.name}?`,
        this.removeCompany,
      );
    },
    removeCompany() {
      deleteCompany(this.selectedItem.id)
        .then(() => {
          vuetifyToast.success(`Company ${this.selectedItem.name} was deleted`);
          this.items = this.items.filter(item => item.id !== this.selectedItem.id);
          this.selectedItem = null;
        })
        .catch(() => {
          vuetifyToast.error(`Error deleting company ${this.selectedItem.name}`);
        });
    },
    createNewCompany() {
      this.$router.push({
        name: 'companyDetail',
        params: { companyId: 'new', originPage: 'companies' },
      });
    },
  },
  components: {
    ConfirmModal,
  },
};
</script>

Companies.spec.js

import Vue from 'vue';
import Vuetify from 'vuetify';
import VueRouter from 'vue-router';
import flushPromises from 'flush-promises';
import { mount, createLocalVue } from '@vue/test-utils';
import VeeValidate from 'vee-validate';
import * as companiesDatabase from '@/services/database/company';
import Companies from './Companies';

Vue.config.silent = true;

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

describe('Companies', () => {
  const routes = [
    { path: '/', name: 'home' },
  ];

  const router = new VueRouter({
    routes,
  });

  let wrapper;
  beforeAll(() => {
    console.log('version', Vuetify.version);
    const { getComputedStyle } = global;
    global.getComputedStyle = function getComputedStyleStub(el) {
      return {
        ...getComputedStyle(el),
        transitionDelay: '',
        transitionDuration: '',
        animationDelay: '',
        animationDuration: '',
      };
    };
  });

  beforeEach(async () => {
    companiesDatabase.getCompanies = jest.fn();
    companiesDatabase.getCompanies.mockReturnValueOnce(Promise.resolve([
      {
        id: '1',
        value: {
          name: 'name',
          contact: {
            name: 'contact',
            email: '[email protected]',
          },
        },
      },
    ]));

    const App = localVue.component('App', {
      components: { Companies },
      template: `
        <div data-app>
          <companies />
        </div>
      `,
    });

    const mountedApp = mount(App, {
      localVue,
      attachToDocument: true,
      router,
      sync: false,
    });

    wrapper = mountedApp.find(Companies);
  });

  it('should render the companies', () => {
    expect(wrapper.is(Companies)).toBe(true);
  });
});

Expected Behavior

Test should pass

Actual Behavior

An error appears in the console:

TypeError: Cannot read property 't' of undefined

      at VueComponent.listData (node_modules/vuetify/dist/vuetify.js:9433:47)
      at Watcher.get (node_modules/vue/dist/vue.runtime.common.js:3140:25)
      at Watcher.evaluate (node_modules/vue/dist/vue.runtime.common.js:3247:21)
      at VueComponent.computedGetter [as listData] (node_modules/vue/dist/vue.runtime.common.js:3505:17)
      at VueComponent.staticList (node_modules/vuetify/dist/vuetify.js:9452:99)
      at Watcher.get (node_modules/vue/dist/vue.runtime.common.js:3140:25)
      at Watcher.evaluate (node_modules/vue/dist/vue.runtime.common.js:3247:21)
      at VueComponent.computedGetter [as staticList] (node_modules/vue/dist/vue.runtime.common.js:3505:17)
      at VueComponent.genList (node_modules/vuetify/dist/vuetify.js:9606:29)
      at VueComponent.genMenu (node_modules/vuetify/dist/vuetify.js:9662:33)
      at VueComponent.genDefaultSlot (node_modules/vuetify/dist/vuetify.js:9592:26)
      at VueComponent.genInputSlot (node_modules/vuetify/dist/vuetify.js:6067:22)
      at VueComponent.genInputSlot (node_modules/vuetify/dist/vuetify.js:12793:94)
      at VueComponent.genControl (node_modules/vuetify/dist/vuetify.js:6011:22)
      at VueComponent.genContent (node_modules/vuetify/dist/vuetify.js:6006:49)
      at Proxy.render (node_modules/vuetify/dist/vuetify.js:6138:17)
      at VueComponent.Vue._render (node_modules/vue/dist/vue.runtime.common.js:4542:22)
      at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.js:2786:21)
      at Watcher.get (node_modules/vue/dist/vue.runtime.common.js:3140:25)
      at new Watcher (node_modules/vue/dist/vue.runtime.common.js:3129:12)
      at mountComponent (node_modules/vue/dist/vue.runtime.common.js:2793:3)
      at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.js:7997:10)

Reproduction Link


Additional Comments:

Looking at the stacktrace the following line in the vuetify.js file is the one having the issue:
noDataText: this.$vuetify.t(this.noDataText),

Thanks in advance for the help

question

Most helpful comment

Polluting global Vue instance is a terrible idea.

All 11 comments

I'm testing with the Vuetify 1.1.12 and the errors can be reproduced though the stacktrace line numbers had changed:

TypeError: Cannot read property 't' of undefined
          at VueComponent.listData (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:9453:47)
          at Watcher.get (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3140:25)
          at Watcher.evaluate (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3247:21)
          at VueComponent.computedGetter [as listData] (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3505:17)
          at VueComponent.staticList (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:9472:99)
          at Watcher.get (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3140:25)
          at Watcher.evaluate (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3247:21)
          at VueComponent.computedGetter [as staticList] (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3505:17)
          at VueComponent.genList (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:9626:29)
          at VueComponent.genMenu (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:9682:33)
          at VueComponent.genDefaultSlot (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:9612:26)
          at VueComponent.genInputSlot (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:6065:22)
          at VueComponent.genInputSlot (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:12813:94)
          at VueComponent.genControl (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:6009:22)
          at VueComponent.genContent (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:6004:49)
          at Proxy.render (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vuetify/dist/vuetify.js:6136:17)
          at VueComponent.Vue._render (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:4542:22)
          at VueComponent.updateComponent (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:2786:21)
          at Watcher.get (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3140:25)
          at new Watcher (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:3129:12)
          at mountComponent (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:2793:3)
          at VueComponent.Object.<anonymous>.Vue.$mount (/Users/devgurus/projects/newleads/newleads-admin-site/node_modules/vue/dist/vue.runtime.common.js:7997:10)

Looks like you need to look at the internationalization instructions again...

import sv from './i18n/vuetify/sv'

Vue.use(Vuetify, {
  lang: {
    locales: { sv },
    current: 'sv'
  }
})

The import of locale, you may need to find the path to that file, "en" and a few other languages are supported...

en is set by default, you don't need to change anything if you're just using that.

@martosoler localVue.use(Vuetify) won't work properly because we access things from Vue.prototype.$vuetify, see vuejs/vue#8278. You'll have to use VueRouter and Vuetify globally instead, or mock the missing pieces.

Thanks for the feedback though I don't understand how should I do, its a simple unit test what I'm trying to implement :(

You are using _avoriaz_ to do the unit tests and the author of that library is now involved in the official test library for Vue. I hope he can chime in.

Will post here if I found an alternative as many of our components use the table component.

Thanks a lot !!

Simply

Vue.use(VueRouter);
Vue.use(Vuetify);

instead

@KaelWD I have the same issue with Vuetify, that some components like data-table, date-picker cannot really be tested, because of 2 instances of Vue, being in charge.

In webpack it's actually easy to alias vue$ to local vue located in node_modules, but the same work-around doesn't seem to work in Jest:

    "moduleNameMapper": {
      "@/(.*)$": "<rootDir>/app/javascript/spa/$1",
      "^vue$": "<rootDir>/node_modules/vue/dist/vue.js",
      ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub"
    },

I think the problem is that @vue/test-utils do not set vue as a peerDependency in package.json and do not set it as external in their build config, so basically vue-test-utils ships with the copy of Vue, that's why there are 2 versions.

In order to make tests working, @vue/test-utils should use Vue as a external library and do not include source code of Vue in the dist file. Test utils are using lerna as a bundling tool and I am not sure if it's even possible to configure it in a way to treat Vue as external library.

FYI @eddyerburgh

Sorry @eddyerburgh & @KaelWD

I have just found out that @vue/test-utils is setting vue as external dependency: https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/scripts/build.js#L59 in the rollup config.

So the problem is somewhere else, maybe it's because of Typescript in Vuetify? Not sure :thinking:

I am having the same problem " Cannot read property 't' of undefined" in the same line "noDataText: this.$vuetify.t(this.noDataText)", any fix for that I am including Vuetify in nuxt using '@nuxtjs/vuetify'. Please help me to resolve the problem for me the problem occurs in normal page rendering

Polluting global Vue instance is a terrible idea.

Same problem here. I am getting the same error on normal rendering. What to do?

We kindly ask users to not comment on closed/resolved issues. If you believe that this issue has not been correctly resolved, please create a new issue showing the regression or reach out to us in our Discord community.

Was this page helpful?
0 / 5 - 0 ratings