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

[recipe] Add Vue.js recipe #1276

Closed
blake-newman opened this issue Feb 17, 2017 · 21 comments
Closed

[recipe] Add Vue.js recipe #1276

blake-newman opened this issue Feb 17, 2017 · 21 comments

Comments

@blake-newman
Copy link
Contributor

blake-newman commented Feb 17, 2017

Note: this is not an issue but a reminder for me to add a recipe for Vue file support.

Add a Vue.js recipe for Vue file transpiling. The new vue-node (https://github.com/knpwrs/vue-node) works great as a solution to get Ava and Vue.js working along side each other. Little configuration needed other than using prexisting webpack configs.

I have it fully working together, although it is quite slow. I believe there is extra precompilation happening, caching transpiled code in vue-node should solve this issue.

I'm also looking into checking coverage reports with Vue.js files, since webpack is driving the transpiling it should work (fingers crossed).

Once I have completed my investigation I will add a recipe.ill keep this issue up to date. May be a couple of weeks, due to finishing a work project.

@novemberborn
Copy link
Member

Thanks @blake-newman!

@forresst
Copy link
Contributor

forresst commented Mar 1, 2017

Meanwhile, @knpwrs has written an article that is very well: http://knpw.rs/blog/testing-vue-in-node

@doriandrn
Copy link

@forresst Thanks for this!

@knpwrs
Copy link

knpwrs commented Mar 10, 2017

Hey, guys. @jackmellis commented on my post with his own approach which is rather interesting. He points to a forum post he made as well as two modules he made: require-extension-hooks and require-extension-hooks-vue. I think the implications of either approach should be discussed and the vue community should settle on one. This issue may or may not be the place to discuss that, but I would happily deprecate my package if people like @jackmellis' approach better, or keep it around if there are valid use cases for either approach.

@blake-newman
Copy link
Contributor Author

jackmellis/require-extension-hooks-vue#1

This adds more support for compiling of templates which should a) improve overall speeds and b) support es2015 features in templates

@jackmellis
Copy link

I've been playing with ava this last week with vue. It seems to work fine with require-extension-hooks - although loading babel-core in every process is incredibly slow - I found myself just writing a small script that just replaces all import/export statements instead of using the full force of babel in a node environment.

One issue I've come across is that ava will often hang when an assertion fails on a reactive property. I assume it's to do with trying to build a fairly complicated vue-embroiled stack trace?

Example:

// where vm.dirty === false

// This test causes ava to hang until the timeout has elapsed (and even then sometimes it keeps hanging)
test('...', t => {
  t.true(vm.dirty);
});

// This test works fine
test('...', t => {
  var dirty = vm.dirty;
  t.true(dirty); // dirty == false
});

@marcosmoura
Copy link

I could make it work using vue-node.
Its working great with istanbul for coverage using nyc:
https://github.com/marcosmoura/vue-boilerplate

@eddyerburgh
Copy link

Here's an example repo - https://github.com/eddyerburgh/avoriaz-ava-example

@jackmellis
Copy link

Nice. I've taken the exact test code and done the same with require-extension-hooks instead of webpack. Goes from 6.8s to 2.4s on my machine...

https://github.com/jackmellis/avoriaz-ava-example

@blake-newman
Copy link
Contributor Author

Will add recipe over next couple of days, should have enough free time to complete.

Require.extension.hooks is probably the best solution, most extensible and fastest. vue-node is much slower as each component requeires Webpack to do alot of work. Which adds approx 3-4s to each component import.

Extension hooks seems to have least impact, and with caching techniques this could be much faster to an already fast solution. Less configuration required aswell. Also mapping of code coverage is very accurate and not yet seen any issues.

@AdrianSkierniewski
Copy link

Hi, How about mocking dependencies in vue component in this approach?

@knpwrs
Copy link

knpwrs commented Apr 23, 2017

Ideally any dependencies you need to mock should be provided via props. When that's the case, mocking is trivial.

@AdrianSkierniewski
Copy link

@knpwrs Thank you for a quick answer to my question. I'm new in Vue.js world and I don't fully understand how I should test Vue components (everything in a single file).

Yesterday I created a simple setup with ava using guides from this issue.
Before I found this issue I was looking on Vue.js docs to see how recommended approach looks like, but I only found information about mocha + karma + webpack setup. Then I found this page https://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html

The solution suggested in this issue doesn't use vue-loader so I can't mock ES6 modules, right? That's why I asked about other ways to easily mock ES6 modules. There is no constructor dependency mechanism or IoC container in place.

Here are some files with my approach:

For now, I just created some method to simply return an instance, so I can easily mock it in tests here. I'm curious about how can I use props to pass ES6 module dependency. Isn't props designed to pass some variables from the layout?

I had one additional problem. I couldn't test mounted component with transition

<template>
  <transition appear name="slideFromBottom">

I guess it's a limitation of the approach suggested in this issue, right?

@jackmellis
Copy link

This might be a discussion for the vue forum. I haven't tried it myself and I'm not sure his well they'll play together but I'm assuming require-extension-hooks should work with rewire so that would give you a simple way to mock your required modules.

As for transitions; I'm not sure if anyone else has had issues like this? You could always try registering a mock transition component?

Abd now shameless plugging: You can do component dependency injection with vue-inject and quick quick component instances with mocked properties and props with vuenit :)

@knpwrs
Copy link

knpwrs commented Apr 24, 2017

I'm going to back up @jackmellis and say that it's probably better for a vue forum, but if you wanted to shoot me at email at ken@kenpowers.net we could discuss it at length.

Keep in mind that my recommendations are coming from general knowledge of component-based architecture (mostly React). This is by no means the only way to do things, just what I've found to work best for me. Essentially the pattern I would recommend is to pass all external state as props. In your case that would mean passing the accepted state as a prop, and emitting an "accepted" event when the user has accepted the policy. The root (entry-point) of your application, save for any other state-management patterns (vuex, etc), should be responsible for getting state from external resources (cookies, local storage, whatever else).

Consider a component tree where application state is passed unidirectionally from the root component to child components. By having your cookie law component materialize its own state from cookies, you effectively have multiple component tree roots. This isn't necessarily wrong, in fact in some cases it's what would be recommended (see the concept of container vs presentational components in React/Redux), but I would encourage keeping such patterns to a minimum. The idea is to be able to spin up your components in any environment without having to rely on the existence of any particular API in the environment.

So now you have a generic component which is easy to test, but now you may be asking how to test the entire application. At this point such a task should be handled by integration/e2e testing, which is a whole different beast. You may also be thinking that it's appropriate to have the cookie law component read state from cookies since that is the component's only purpose, but you should also keep in mind that there isn't actually a "cookie law", just a "user tracking law" (please note, I am not a lawyer, this is not legal advice). By constructing your component in a way where the accepted state is passed as a prop, your component more reflects the reality of the law, regardless of where the accepted state is actually stored.

I realize this has been long and rambly, so TL;DR: pass the accepted state as a prop, emit an accepted event when the user accepts. IMHO, realizing that this is not the only, or necessarily the most correct way to do things in all situations, the component should be constructed in such a way that it is not tracking the accepted state, but relying on the application to provide and manage accepted state.

@AdrianSkierniewski
Copy link

@knpwrs Thank you for such detailed answer :) I agree with your point of view, but in this particular application, I only need some small components that don't form any bigger component. So it feels a little overengineered in this particular case. I definitely take a closer look at this approach when I start writing some bigger components.

@jackmellis Do you have any example code how to use vue-loader with require-extension-hooks? I'm quite new on this topic and I'm not even frontend developer :) Any example would be nice, so I could try to figure out how it works.

@karlpokus
Copy link

Looks promising

@OmgImAlexis
Copy link

OmgImAlexis commented May 3, 2017

@blake-newman using the PR you opened as a base I tried adding this to my current vue app and I'm running into an issue no matter how I have it setup.

Error logs
➜  wvvw.me git:(master) ✗ yarn ava test/*.spec.js
yarn ava v0.23.3
$ "/Users/xo/code/wvvw.me/node_modules/.bin/ava" test/post.spec.js
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
[Vue warn]: Vue is a constructor and should be called with the `new` keyword 
/Users/xo/code/wvvw.me/node_modules/vue/dist/vue.runtime.common.js:3414
  this._init(options);
       ^

TypeError: this._init is not a function
    at Array.Vue$2 (/Users/xo/code/wvvw.me/node_modules/vue/dist/vue.runtime.common.js:3414:8)
    at hook (/Users/xo/code/wvvw.me/node_modules/require-extension-hooks/hook.js:15:26)
    at Object.require.extensions.(anonymous function) [as .vue] (/Users/xo/code/wvvw.me/node_modules/ava/lib/process-adapter.js:100:4)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/xo/code/wvvw.me/test/post.spec.js:4:1)
    at Module._compile (module.js:571:32)
    at extensions.(anonymous function) (/Users/xo/code/wvvw.me/node_modules/require-precompiled/index.js:13:11)
    at Object.require.extensions.(anonymous function) [as .js] (/Users/xo/code/wvvw.me/node_modules/ava/lib/process-adapter.js:100:4)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/xo/code/wvvw.me/node_modules/ava/lib/test-worker.js:49:1)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.runMain (module.js:605:10)
    at run (bootstrap_node.js:423:7)
    at startup (bootstrap_node.js:147:9)
    at bootstrap_node.js:538:3

  1 exception

  ✖ test/post.spec.js exited with a non-zero exit code: 1

error Command failed with exit code 1.
post.spec.js
import Vue from 'vue';
import test from 'ava';

import Post from '../src/components/Post.vue';

test(t => {
    const N = Vue.extend(Post);
    const vm = new N({
        propsData: {
            post: {
                title: 'Test Post',
                content: 'This is a test post.',
                tags: ['test', 'post', 'example']
            }
        }
    });
    Vue.nextTick(() => {
        t.is(vm.$el.textContent, 'This is a test post.');
    });
});
./src/components/Post.vue
<template>
    <div v-bind:class="['post', 'styled', (post.published ? '' : 'unpublished')]">
        <template v-if="post.published || (!post.published && user)">
            <h1 class="title">{{post.title}}</h1>
            <div class="content" v-html="marked(post.content)"></div>
            <span class="meta">
                <a v-bind:href="post.permalink">{{new Date(post.date).toDateString()}}</a>
                <span class="owner">by <a v-bind:href="'/user/' + owner.id">{{owner.name}}</a></span>
            </span>
        </template>
        <template v-else>᠎᠎
            <p class="content">This post hasn't been published yet.</p>
        </template>᠎᠎᠎
    </div>
</template>

<script>
import Vue from 'vue';
export default Vue.extend({
    name: 'post',
    props: {
        post: {
            type: Object
        },
        user: {
            type: Object
        }
    },
    computed: {
        owner() {
            var vm = this;
            var isAnonymous = 'author' in vm.post;
            return {
                name: isAnonymous ? vm.post.author.username : 'anonymous',
                id: isAnonymous ? vm.post.author._id : 'anonymous'
            };
        }
    }
})
</script>

@OmgImAlexis
Copy link

Here's what I got working. One thing to keep in mind is you can't use Vue.extend(Component); in your testing if you imported Vue anywhere in any of your components as it'll throw an error.

OmgImAlexis/wvvw.me@7d63710...master

@jackmellis
Copy link

@OmgImAlexis I've published require-extension-hooks-vue 0.2.2 which may or may not fix your issue...🤞

@OmgImAlexis
Copy link

OmgImAlexis commented May 9, 2017

Since most people use vue-router or something similar would you be up for adding details on how to use it to #1361? I noticed I can test components fine but trying to test my app.vue spits out errors because my routes field is missing.

The code below allows vue-router to work properly.

import Vue from 'vue';
import VueRouter from 'vue-router';
import test from 'ava';
import App from '../src/app.vue';
import HomeComponent from '../src/components/home.vue';

test.only('App should render', t => {
    Vue.use(VueRouter);
    const router = new VueRouter({
        routes: [{
            name: 'home',
            path: '/',
            component: HomeComponent
        }]
    });
    const vm = new Vue({
        router,
        render: h => h(App)
    }).$mount();
    const tree = {$el: vm.$el.outerHTML};
    t.snapshot(tree);
});

Edit: Although this is working nyc isn't returning anything in the coverage report.

➜  vue git:(feature/add-default-vue-route) ✗ yarn coverage
yarn coverage v0.23.4
$ nyc ava 

  1 failed

  App should render
  /Users/xo/code/Medusa/vue/test/app.spec.js:21

   20:     const tree = {$el: vm.$el.outerHTML};
   21:     t.snapshot(tree);                    
   22: });                                      

  Did not match snapshot

  Difference:

      Object {
    -   "$el": "<div id="app"><div>Home</div></div>",
    +   "$el": "<div id="app"><div>
    +         This is the homepage.
    +     </div></div>",
      }

  Test.fn (test/app.spec.js:21:7)
  processEmit [as emit] (node_modules/nyc/node_modules/signal-exit/index.js:155:32)
  processEmit [as emit] (node_modules/nyc/node_modules/signal-exit/index.js:155:32)

----------|----------|----------|----------|----------|----------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
----------|----------|----------|----------|----------|----------------|
All files |  Unknown |  Unknown |  Unknown |  Unknown |                |
----------|----------|----------|----------|----------|----------------|
error Command failed with exit code 1.

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

No branches or pull requests