npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@netsells/vue-storybook-test-utils

v2.0.3

Published

This package provides wrappers around `@vue/test-utils` for easier integration with storybook-based tests.

Downloads

834

Readme

@netsells/vue-storybook-test-utils

This package provides wrappers around @vue/test-utils for easier integration with storybook-based tests.

Note: This package works with the following stack:

  • Nuxt 3
  • Vite
  • @vue/test-utils
  • Vitest
  • Storybook 7+

For Nuxt 2 compatability see versions 1.x.

Why?

Most of your test composition and variants (e.g. props, slots) is already setup as part of storybook for use in component-first development and Visual Regression Testing process. It makes little sense to do this setup twice. This module allows you to pass your stories directly to the @vue/test-utils package and have the wrappers automatically generated with your provided props and story markup.

Note: Version 1.x of this package works with the following stack:

  • Nuxt 2
  • Storybook 6
  • Webpack
  • Jest

For Nuxt 3 see version 2.x or greater

Installation

$ yarn add -D @netsells/vue-storybook-test-utils

Setup

Ensure your project has a jest config, and create a setupFilesAfterEnv file, for example:

{
    "setupFilesAfterEnv": [
        "<rootDir>/tests/setup.js"
    ]
}

Within the file you should define any mocks, stubs, globals, etc that your test suite may rely on. This module provides helpers to facilitate this setup:

import { setStubs, setMocks, mockComponents, mockDirectives, mockStore, setupPlugins } from '@netsells/vue-storybook-test-utils';
import SlightlyImportantComponent from './stubs/SlightlyImportantComponent';
import moment from './mocks/moment';
import importantDirective from './mocks/important-directive';
import { BButton } from 'bootstrap-vue';
import MyPlugin from 'my-plugin';

// Stub any global components
setStubs({
    'unimportant-component': true,
    'slightly-important-component': SlightlyImportantComponent,
});

// Mock any global Vue properties
setMocks({
    $moment: moment,
});

// Register global components. This is similar to `setStubs` but assumes fully functional components
// e.g. here you would register bootstrap-vue globals
mockComponents({
    BButton,
});

// Mock any directives that might be used in your app
mockDirectives({
    'unimportant-directive': true, // Directive will be essentially noop'd to prevent errors
    'important-directive': importantDirective,
});

// Mock the store used in your application
// Avoid basing any mutable tests on this data, as it will change between your tests and create dirty data
// For testing with the store, refer to the `localVue` section below
mockStore({
    modules: {
        global: {
            state: {
                menu_open: false,
            },
            namespaced: true,
        },
    },
});

// Register any global plugins here:
setupPlugins([
    MyPlugin,
]);

Testing

Take the following story:

import TextInput from './TextInput';

export default {
    component: TextInput,

    argTypes: {
        disabled: {
            control: 'boolean',
        },
    },

    args: {
        label: 'My Input',
    },
};

export const textInput = (args = {}, { argTypes = {} }) => ({
    props: Object.keys({ ...args, ...argTypes }),

    components: { TextInput },

    template: `
        <text-input v-bind="$props" />
    `,
});

export const disabled = textInput.bind({});
disabled.args = {
    disabled: true,
};

Instead of invidually pulling in your stories and setting them up with the same data, you can leave it up to the generateStorySuite function to generate a suite of factories to help bootstrap your tests. For example:

import { generateStorySuite } from '@netsells/vue-storybook-test-utils';
import * as TextInput from './TextInput.stories';

const suite = generateStorySuite(TextInput);

describe('TextInput', () => {
    test('test default state', () => {
        const wrapper = suite.textInput({
            mocks: {
                'nuxt-link': true,
            },
            // Any additional setup config you may need for this component
        });
        
        // The default factory function is shorthand for the following:
        const wrapper = suite.textInput.mountStory(config);
        
        // If you prefer a shallow mount you can call the respective method:
        const wrapper = suite.textInput.shallowMountStory(config);
        
        // If you wish to just render the story you can use the following:
        const wrapper = suite.textInput.renderStory(config);
        
        // If for any reason you require access to the raw story, it can be accessed via the `story` property:
        console.log(suite.textInput.story);
    });
    
    test('test disabled state', () => {
        // To run tests against the `disabled` story:
        const wrapper = suite.disabled(config);
        
        // As above, you can also call the `mountStory`, `shallowMount` and 
        // `renderStory` methods to suite your testing needs
    });
    
    test('test the component directly', () => {
        // If you would prefer to test the component directly, you can do so using the following:
        const wrapper = suite.component(config);
        
        // The default factory function is shorthand for the following:
        const wrapper = suite.component.mount(config);
        
        // If you prefer a shallow mount you can call the respective method:
        const wrapper = suite.component.shallowMount(config);
        
        // If you wish to just render the component you can use the following:
        const wrapper = suite.component.render(config);
    });
});

TestId

Sometimes it's helpful to add a specific attribute to your tests to quickly and easily access your testable elements, without having to rely on specific markup. For this we recommend adding a data-testid="someKey" to your testable elements, and using the findByTestId helper. For example:

<ul>
    <li>Some item I don't want to test</li>
    <li data-testid="testableListItem">My testable element</li>
    <li>Another item I don't want to test</li>
</ul>

In the above example, previously you might do something like:

const listItem = wrapper.findAll('li').at(1);

The problem here, is that if you later add another list item above this, your test will fail, even though the content of the li may be correct.

With the introduction of the data-testid attribute we're able to avoid this problem by strictly fetching the item we need, e.g.:

const listItem = wrapper.find('[data-testid="testableListItem"]');

For further reading on this method, check out this article by Kent C. Dodds.

findByTestId

Typing out the full accessor path is a lot of duplication, so we can instead use the provided helper:

const listItem = wrapper.findByTestId('testableListItem');

findAllByTestId

You can also use the findAllByTestId method for finding multiple testIds.

const listItems = wrapper.findAllByTestId('testableListItem')

findComponentByTestId

If you have multiple of the same component and can't easily target it with a findComponent, you can use the findComponentByTestId method to do this. For example:

<my-component data-testid="componentOne" />
<my-component data-testid="componentTwo" />
<my-component data-testid="componentThree" />

<my-component
    v-for="i in 5"
    data-testid="repeatedComponents"
/>

You can differentiate and access the individual components using the following:

// Returns a single wrapper
const componentOne = wrapper.findComponentByTestId({ 
    name: 'my-component', 
}, 'componentOne'); 
// Returns a single wrapper
const componentTwo = wrapper.findComponentByTestId({ 
    name: 'my-component',
}, 'componentTwo');

Doing the above allows you to easily assert that the element you want to access is the correct component as well as ensuring it's the correct individual component you need to access.

findAllComponentsByTestId

If you need to access multiple components, for example to access a repeated collection, you can use this helper to return a wrapper array.

<my-component data-testid="componentOne" />
<my-component data-testid="componentTwo" />
<my-component data-testid="componentThree" />

<my-component
    v-for="i in 5"
    data-testid="repeatedComponents"
/>

You can differentiate and access the collection of components using the following:

// Returns a wrapper array
const repeatedComponents = wrapper.findAllComponentsByTestId({ 
    name: 'my-component', 
}, 'repeatedComponents');

v-test directive

To make this easier in your template, you can also make use of the provided v-test directive. Simply pull in and register the directive like so:

// setup.js
import test from '@netsells/vue-storybook-test-utils/directives/test'

mockDirectives({
    test,
});

Once pulled in, this directive can be used like so:

<ul>
    <li>Some item I don't want to test</li>
    <li v-test:testableListItem>My testable element</li>
    <li>Another item I don't want to test</li>
</ul>

There is also a simple nuxt module to prevent this method erroring in production. Simply add '@netsells/vue-storybook-test-utils/nuxt' to your buildModules section.

These data attributes will only render in test environments, i.e. in jest and storybook.

Routes

If your components/stories feature routes, you can provide these via the router.routes parameter on your story definitions.

Before this will work, you must mock the router in your setup file:

import { mockRouter } from '@netsells/vue-storybook-test-utils';

mockRouter({
    // Any vue-router config options can be passed here
});

You can then provide your routes within your stories:

export default {
    parameters: {
        router: {
            routes: [
                {
                    name: 'account',
                    path: '/account',
                },
            ],
        },
    },
};

You can also provide routes to specific stories if required:

story.parameters = {
    router: {
        routes: [
            {
                name: 'account',
                path: '/account',
            },
        ],
    },
};

Component Access

You can access the component import itself via suite.utils.component. You can also use wrapper.getComponent() to retrieve the main component you're testing. This may be helpful if you have a dynamic test suite that needs to find the component within the story, e.g.:

describe('when the input is updated', () => {
    test('the new value is emitted', async () => {
        const wrapper = suite.textInput();
        const component = wrapper.getComponent();
        const input = wrapper.find('input');

        await input.setValue('New value');

        expect(component.emitted('input').length).toBe(1);
        expect(wrapper.vm.val).toBe('New value');
    });
});

Similarly, the entire default export of your story is available as suite.utils.defaultExport if required.

localVue

Unlike the localVue property available on @vue/test-utils, you should provide a callback rather than an instance of your own. This reduces manual boilerplate and makes it more familiar to set up. The first argument of the callback is a Vue instance localised to your test. You should use this to setup any Vuex modules you may require in your tests.

const wrapper = suite.myComponent({
    localVue(Vue) {
        // Use `Vue` as you would normally, e.g. `Vue.use`, `Vue.mixin`
    },
});

Misc Utilities

waitForAnimationFrame

Returns a promise that resolves after the current repaint to ensure any animations have completed.

@vue/test-utils raw functions

We also pass through all the @vue/test-utils methods as they are in the original package, with the exception of mount and shallowMount, as these are overridden with additional functionality from this module. Should you require access to these methods, they can be called with the raw prefix, e.g. rawMount and rawShallowMount.