vuejs / vue-loader

📦 Webpack loader for Vue.js components
MIT License
4.99k stars 915 forks source link

Regression: Injected components seemingly fail to mount in tests #873

Open riovir opened 7 years ago

riovir commented 7 years ago

Version

13.0.0

Reproduction link

https://github.com/riovir/injected-component-reprod

Steps to reproduce

Clone - install - test with the given repo, or:

  it('should render correct contents even when injecting', () => {
    const HelloInjector = require('!!vue-loader?inject!@/components/Hello')
    const Constructor = Vue.extend(HelloInjector({}))
    const vm = new Constructor().$mount()
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App')
  })
ERROR LOG: '[Vue warn]: Failed to mount component: template or render function not defined. (found in <Root>)'

What is expected?

Mock-injected components to behave the same way as their regularly loaded counterparts. Particularly where the render function is concerned.

What is actually happening?

In arbitrary situations (project size seems to be a factor) inject-loaded Vue components fail to mount due to missing render function.

LinusBorg commented 7 years ago

This is not a regression, it's a result of the documented breaking change concerning require():

Similarly, old CommonJS-style requires will also need to be updated:

// before
const Foo = require('./Foo.vue')

// after
const Foo = require('./Foo.vue').default

The difference when using ?inject compared to the given example above is that you would not do:

const HelloInjector = require('!!vue-loader?inject!@/components/Hello').default // wrong!

because with ?inject, the require does return a constructor, not the module exports object.

Instead, We have to get the .default after we have run the constructor:

const Constructor = Vue.extend(HelloInjector({}).default)
LinusBorg commented 7 years ago

We should fix the docs in this regard, though.

riovir commented 7 years ago

I've applied the suggested fix on the sample repo (also downgraded the inject-loader to 2.0.0): https://github.com/riovir/injected-component-reprod/commit/8db5a94cf722fde144785bc0dff57ad4c1ba2841

Unfortunately the problem persists.

LinusBorg commented 7 years ago

I'll check it out...

LinusBorg commented 7 years ago

Dang, forgot about this one. assigned myself now and will take a look on the weekend hopefully.

riovir commented 7 years ago

Let me know if I can help.

phoenix741 commented 7 years ago

I have the same kind of problem : https://github.com/phoenix741/passprotect-server/blob/update-versions/test/unit/specs/components/user/Login.spec.js

I use es6 import in test (cjs import don't resolve the problem event using .default). When i import vue file without inject, it work, but if i use inject the created mock don't contains the template and i have the warning.

I made a console.log to see the created object :

LOG LOG: Object{name: 'login', mixins: [Object{created: ..., mounted: ..., methods: ...}], data: function data() { ... }, computed: Object{usernameValidation: function usernameValidation() { ... }, passwordValidation: function passwordValidation() { ... }}, methods: Object{submitForm: function submitForm() { ... }}, render: undefined, staticRenderFns: undefined, __file: 'client/components/user/Login.vue'}
ERROR LOG: '[Vue warn]: Failed to mount component: template or render function not defined.

How can i wrote my test ? Is it a bug or it was my mistake ?

LinusBorg commented 7 years ago

Please consult the forum for support.

forum.vuejs.org

phoenix741 commented 7 years ago

I have open a topic on the forum.

The problem come from component-normalizer that get compiledTemplate.render and compiledTemplate.staticRenderFns but we should get the compiledTemplate.default.render and compiledTemplate.default.staticRenderFns because i use ES6

phoenix741 commented 7 years ago

I have made a correction on my computer (directly in node_modules) in component-normalizer :

// render functions if (compiledTemplate) { compiledTemplate = compiledTemplate.default || compiledTemplate options.render = compiledTemplate.render options.staticRenderFns = compiledTemplate.staticRenderFns }

LinusBorg commented 7 years ago

Thanks for investigating this, there seems to be an issue indeed.

Appreciated!

LinusBorg commented 7 years ago

on second thought, would you be so kind and opens proper bug report issue for this?

The current issue is 1) old and 2) originally about a a different problem (improving docs to reflect the changed from v13)

phoenix741 commented 7 years ago

Ok I will create a bug on a new issue, but strangely reinstalling the project in another path with a new node_modules resolve the problem.

I search why to reproduce the problem in a minimal project

ghost commented 7 years ago

@phoenix741 I've reinstalled into another path, but i keep having the same problem. Did you find out why it's working on your side after reinstalling ? (i'm currently using you're proposed solution which makes everything working)

phoenix741 commented 7 years ago

Hi, I reproduce the problem only some times, and think that when i start the app and then test. Test fails. If i start the test without starting the application it work. I have the feeling that the problem is related to a cache (babel cache ? vue cache ?).

ntsim commented 7 years ago

Tried applying your suggested fix @LinusBorg to use Vue.extend(Injector({}).default). Doesn't seem to have done anything unfortunately and the injected component seems to still have the notorious undefined render function.

I'm currently running these tests using these versions:

A horrible fix I've employed to get around this is to import the original component and replace the injected component's render method with the original's.

import OriginalComponent from '@/Component.vue';
import injector from '!!vue-loader?inject!@/Component.vue'; 
// require('!!vue-loader?inject!@/Component.vue') also does the same thing

const Component = injector({ ... });
Component.render = OriginalComponent.render;

This is pretty horrendous though and it would be good to not have to do this :disappointed:

Trainmaster commented 7 years ago

I'm having a similar issue with a karma@1.7.1 + jasmine@2.8.0 + webpack@2.2.1 + inject-loader@3.0.1 setup.

Component.vue:


<template>
    <div></div>
</template>
<script>
  export default {};
</script>

ComponentTest.js:

import Component from './Component.vue';
import Injector from './Component.vue?inject';

// ...

beforeEach(() => {
  console.log('Component', Component);
  console.log('Injector', Injector());
});

The ?inject is resolved via webpack config:

// ...
      {
        test: /\.vue$/,
        include: includePaths,
        oneOf: [
          {
            resourceQuery: /inject/,
            loader: 'vue-loader',
            options: {
              inject: true,
              loaders: {
                js: jsLoader
              },
            },
          },
          {
            loader: 'vue-loader',
            options: {
              loaders: {
                js: jsLoader
              },
            },
          },
        ],
      },
// ...

With vue-loader@13.5.0:

LOG: 'Component', Object{render: function () { ... }, staticRenderFns: [], _compiled: true, __file: 'tests/pages/Component.vue'}
LOG: 'Injector', Object{render: undefined, staticRenderFns: undefined, _compiled: true, __file: 'tests/Component.vue'}

// or

LOG: 'Component', Object{render: function () { ... }, staticRenderFns: [], _compiled: true, __file: 'tests/Component.vue'}
LOG: 'Injector', Object{render: function () { ... }, staticRenderFns: [], _compiled: true, __file: 'tests/Component.vue'}

With vue-loader@12.2.2:

LOG: 'Component', Object{render: function (){ ... }, staticRenderFns: [], __file: '/home/user/.../tests/Component.vue'}
LOG: 'Injector', Object{render: function (){ ... }, staticRenderFns: [], __file: '/home/user/.../tests/Component.vue'}

With vue-loader@13.5.0 the props render and staticRenderFns are randomly undefined. First I thought it's a caching issue. But in my case it happens randomly, so maybe a timing issue?

fracasula commented 6 years ago

I had the same issue and solved it (for now) by avoiding to import components straight away (as in without using the inject loader) even when I don't have to mock any dependencies.

import Search from './../../../src/components/Search.vue'; // AVOID
import SearchInjector from '!!vue-loader?inject!./../../../src/components/Search.vue'; // IT WORKS

I just had to remove the import Search from './../../../src/components/Search.vue'; and then when I need to test the component without mocking any dependencies I simply do:

const vm = new Vue(SearchInjector());

I took me some time to figure out that was the problem because I wasn't getting the failure in the test where I was doing the mounting (which in fact was passing when ran on its own). I got the failure only when running ALL tests as in some tests I had some component imports without injection and it made the mounting fail also in the other tests (which makes me wonder about tests isolation really).

Here's my working configuration with Karma and Webpack 3: https://github.com/fracasula/inject-loader-test

scottbedard commented 6 years ago

Can anyone report what the status of this issue is? I'm experiencing this problem as well.

Trainmaster commented 6 years ago

@yyx990803 In https://github.com/vuejs/vue-loader/commit/611fda8c76b5f2c612536eb90ac5694384717659 using the inject-loader is deprecated. Will there be a replacement?

shroomist commented 6 years ago

I had to downgrade to vue-loader@12.2.2 because >v13 has the above issue. how do we inject dependencies from now?

Trainmaster commented 6 years ago

We replaced inject-loader with babel-plugin-rewire and could finally upgrade to vue-loader@14.2.1.