DEV Community

Carlos Rodrigues
Carlos Rodrigues

Posted on

How to test your library for vue 2 and vue-next

With vue-next now a release candidate, many library authors have begun porting their vue 2.x library to vue-next.

There are solutions to enable library compatibility with both Vue 2 and Vue 3. For example, vue-demi allows your library to access the appropriate Vue APIs for the version of vue you have installed installed.

However, there are no current solutions for testing your library against both Vue 2 and Vue 3. This is particularly relevant to me as I maintain vue-composable, a library that has both vue 2.x and vue-next support -- and of course, a suite of automated tests.

Preface

First, an important note: this may not be the most correct or the cleanest approach, but it works and has been tested for quite a few months now.

Probably the best place for this to be used is with the composition-api libraries, because the render has breaking changes, that make this method to be a bit messy.

Introduction

I will cover how to setup a project to work with both vue-next and vue 2 + composition-api. It will target vue-next primarily.

I've tried to keep the example as simple as possible, but some prior knowledge is required:

  • Vue 3
  • Vue 2.x + @vue/composition-api
  • Jest

Project

For the project we will create a composable called useApi

import { ref } from "vue";

export function useApi(factory, handleResponse) {
  const isLoading = ref(false);
  const result = ref(null);
  const error = ref(null);
  const execute = async (...args) => {
    const request = factory(...args);

    isLoading.value = true;
    error.value = null;
    try {
      const response = await fetch(request);
      const valueResponse = await handleResponse(response);

      result.value = valueResponse;
      return valueResponse;
    } catch (e) {
      error.value = e;
      result.value = null;
    } finally {
      isLoading.value = false;
    }
  };

  return {
    isLoading,
    result,
    error,
    execute,
  };
}

As written, this would work with vue-next, but not with vue 2.x.

api.js

So we will need to replace the import { ref } from 'vue' with our own import { ref } from './api'.

// api.js [Vue3]
export { ref } from "vue";

In api.js we will expose all the required Vue APIs needed in the project. All vue dependencies should come from this file. (Otherwise we might end up importing different vue versions and breaking reactivity.)

vue 2 + composition-api

Now we will create a new file called api.2.js

export { ref } from "@vue/composition-api";

We can hot-swap in this file when testing.

Test

Now we just need to create a test __tests__/index.spec.js

import { useApi } from "../index";
import { mockFetch } from "./utils";

describe("useApi", () => {
  it("should work", async () => {
    mockFetch({ name: "Luke Skywalker" });
    const userList = useApi(
      (page) => ({ url: `https://swapi.dev/api/people/${page}` }),
      (r) => r.json()
    );
    await userList.execute(1);

    expect(userList.result.value.name).toBe("Luke Skywalker");
  });
});

Now we could just change the import { ref } from './api' to import { ref } from './api.2'. Only one problem remains: @vue/composition-api needs to be initialised by running Vue.use(VueCompositionApi).

Jest config

So we will add a jest.config.js and create a file (__tests__/setupTest.js) where we can initialise the Vue 2 composition-api plugin.

Because we currently only need the setupTest.js when using vue 2.x we will pass an environment variable named VUE with the version number. If is 2 we will include

// jest.config.js
const isVue2 = process.env.VUE === "2";

module.exports = {
  setupFiles: isVue2 ? ["<rootDir>/__tests__/setupTest.2.js"] : undefined,
  testMatch: ["<rootDir>/__tests__/**/*spec.[jt]s?(x)"],
};

For the setupTest.2.js we will need to import vue, but we can't just import it normally because we already have vue@next in our package dependencies. So we will need to add vue 2.x with a different name (vue2).

// package.json
  "dependencies": {
    "@vue/composition-api": "^1.0.0-beta.6",
    "vue": "^3.0.0-rc.4",
    "vue2": "npm:vue@^2.6.1"
  }

Now we can import it in setupTest.2.js

import Vue from "vue2";
import VueCompositionApi from "@vue/composition-api";

Vue.use(VueCompositionApi);

Great. However, we're not done yet. If you pass env.VUE === '2', it may initialize the composition api, but the tests would still run using vue-next 🤔

To make sure we run with the correct API we will mock the api.js module with jest.

// setupTest.2.js

// mock for the correct api
jest.mock("./../api", () => jest.requireActual("./../api.2"));

//... importing vue and use CompositionApi

Now if you pass the VUE=2 environemnt variable it will use @vue/composition-api instead of vue-next.

Scripts

You can have yarn test test both versions by doing

// package.json
  "scripts": {
    "test": "yarn test:vue2 && yarn test:vue3",
    "test:vue2": "VUE=2 yarn jest",
    "test:vue3": "yarn jest"
  },

Multi-platform

You can install cross-env and run both

yarn add cross-env -D
//package.json
  "scripts": {
    "test": "yarn test:vue2 && yarn test:vue3",
    "test:vue2": "cross-env VUE=2 yarn jest",
    "test:vue3": "yarn jest"
  },

Pitfalls

If there's file that imports vue or @vue/composition-api, most likely it will compile but it will not work properly, reactivity will not work, etc. Please make sure you only import from api.js.

Typescript types might be different and have some nuances, if using with typescript please export the types from api.ts, if the types don't exist in v2 or v3, export custom types using api.ts or api.2.ts.

Conclusion

Hopefully this will give you a good start to begin experimenting with both libraries within the same code base. There are a few missing apis in @vue/composition-api, and this approach will allow you to implement some of them (example)

Please check up-to-date repo for the all the code and working project.

Big thanks to @danielroe for reviewing this article, so many awesome changes 🎉

Top comments (2)

Collapse
 
martinmalinda profile image
Martin Malinda

This is great and just what I need to do with vue-concurrency.

I am missing one thing though: this just solves testing, but the importing from ./api would effectively make the lib possible to use only for Vue 3 right? So there's some more hacking needed to make the library usable both with Vue 2 and Vue 3.

Collapse
 
martinmalinda profile image
Martin Malinda

To answer my own question: vue-composable renames the file as needed during build.

github.com/pikax/vue-composable/bl...