vuejs / test-utils

Vue Test Utils for Vue 3
https://test-utils.vuejs.org
MIT License
1.02k stars 242 forks source link

@vue/compat and VTU 2.0 incompatilibity #721

Closed marina-mosti closed 3 years ago

marina-mosti commented 3 years ago

Problem:

When trying to port an application from Vue 2 to Vue 3.1 compat VTU throws errors.

Example:

With the following package.json:

{
  "name": "vue31vtu",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@vue/compat": "^3.1.0",
    "core-js": "^3.6.5",
    "vue": "^3.1.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-unit-jest": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.1.0",
    "@vue/test-utils": "^2.0.0-0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^7.0.0",
    "typescript": "~3.9.3",
    "vue-jest": "^5.0.0-0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {},
    "overrides": [
      {
        "files": [
          "**/__tests__/*.{j,t}s?(x)",
          "**/tests/unit/**/*.spec.{j,t}s?(x)"
        ],
        "env": {
          "jest": true
        }
      }
    ]
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

Consider the following component:

export default {
    props: {
        chevron: { type: Boolean, default: false }
    },
    render (h) {
        const icon = this.chevron
            ? h('BaseIcon', {
                props: {
                    color: 'text-grey-900',
                    icon: 'fas fa-chevron-right'
                },
                class: ['ml-2']
            })
            : null

        return h(
            'a',
            {
                class: [
                    'hover:text-orange-700 font-bold cursor-pointer text-teal-600',
                    { 'flex items-center': this.chevron },
                    this.$options.class
                ],
                on: this.$listeners,
                attrs: this.$attrs
            },
            [this.$slots.default, icon]
        )
    }
}

When serving the application with Vue 3 compat enabled, Vue will render the component correctly and issue a warning:

[Vue warn]: (deprecation RENDER_FUNCTION) Vue 3's render function API has changed. You can opt-in to the new API with:

  configureCompat({ RENDER_FUNCTION: false })

When trying to run the related unit tests for this component we get errors: image

A similar problem occurs when porting components using v-model, where VTU will fail because it is expecting update:modelValue instead of input even though the component is not yet set for that compat configuration yet.

Expected behavior: VTU should use the vue/compat build and not issue an error, but a console warning.

I havent' seen any particular documentation on how to tell VTU 2.0 to use a specific build of Vue. In the migration docs a webpack config aliasing vue to @vue/compat is suggested as so:

config.resolve.alias.set('vue', '@vue/compat')

    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        return {
          ...options,
          compilerOptions: {
            compatConfig: {
              MODE: 2
            }
          }
        }
      })

If we could have a way to alias the vue package that VTU is using this may clear up the issue. Any suggestions?

lmiller1990 commented 3 years ago

Hi Marina!

I have not tried this yet, but @xanf has been doing a ton of work around VTU and Vue compat - most of which was merged in the last few days and has not been released. Here is a list of the recent PRs.

I am going to do a release soon - something in 3.1.4 completely broke some parts of Test Utils, I need to find out what before I do a release. @xanf might have some ideas, I heard he is working on a small project with VTU + Vue 3 + compat build.

xanf commented 3 years ago

@lmiller1990 the project size quickly goes out of control What I've actually done - I've written a pretty thin "compat" layer which serves as a bridge for making VTU 1.x code to work in VTU2

@lmiller1990, WDYT of introducing compat flags similar to Vue to @vue/test-utils Something like

import { configureCompat } from '@vue/test-utils'

configureCompat({
  DESTROY_INSTANCE: true, // support calling wrapper.$destroy() 
  FIND_ALL_WRAPPERS: true, // return wrapper instead of array of instances
...
}) 

with each compat documented in VTU. This will greatly help people in Vue2 -> Vue3 migration because while Vue3 now has a compat build, VTU silently forcing to rewrite all tests in one time (neither VTU1 works with compat build obviously, nor VTU2 works with VTU1 tests codebase)

lmiller1990 commented 3 years ago

We could, my only concern is complexity. Adding another layer could make things really difficult to maintain, since we have a huge number of combinations of flags to test.

I personally haven't got any large Vue 2 apps I am upgrading, so I don't have a lot of context on the difficult of migrating - you'd definitely have a better perspective than me.

I wonder if it would make more sense to back-port some of the new API/changes to VTU v1, instead of re-implementing them here?

Alternatively, just asking people to update their code for simple cases (like the changes to find) might not be the worst - they likely have more resources to work on their app than we do to work on Test Utils (only a handful of unpaid volunteers).

What do you think are the most challenging for migrating a large VTU code-base that we should consider? If we can support the most challenging ones with a compat layer, I think that'll help out a lot.

lmiller1990 commented 3 years ago

For example, things like the wrapper.find API changes are pretty easy to do by hand - my main concern is around some of the more complex ones, that are difficult to understand unless you know Vue extremely well (the v-model changes, render functions, that sort of thing).

If you want have some ideas @xanf happy to consider! A huge code-base like GitLab will probably have the strongest use case for this kind of feature. I don't have enough perspective on the breaking changes (all the apps I work on are either staying Vue 2 - no benefit to update - or new Vue 2 apps).

lmiller1990 commented 3 years ago

@marina-mosti can you try the latest version (rc.10) https://github.com/vuejs/vue-test-utils-next/releases/tag/v2.0.0-rc.10.

It might work better for you - lots of fixes around compat in this release.

marina-mosti commented 3 years ago

Hey @lmiller1990 @xanf thanks, ill check out the PRs and try the build

marina-mosti commented 3 years ago

Hey @lmiller1990 sad to report its still broken :( I've uploaded the code to this repo for easier repro on your end: https://github.com/marina-mosti/vtu-compat-tests

The test is still breaking with the same error: TypeError: h is not a function


Additionally, I've found a new problem with the compat build with stubs. (or at least with VTU 2 not sure if its compat related).

When I stub out a component and try to findAllComponents(stub) like this:

const linkStub = {
  template: '<a/>'
}

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      props: { msg },
      global: {
        stubs: {
          BaseLink: linkStub
        }
      }
    })

    const links = wrapper.findAllComponents(linkStub)
    expect(links).toHaveLength(1)
  })
})

VTU can not find any. If I add a name property to the linkStub:

const linkStub = {
  template: '<a/>',
  name: 'LinkStub'
}

Then it works, I'm guessing because something is checking for a name to exist and then identifying it as an actual component. Its not a problem, but I don't think/havent found this documented anywhere if is working as intended.

This bug is also on the repo above if you want to check it out just remove the name

Edit: Should I open a new issue for ^? LMK

xanf commented 3 years ago

@marina-mosti you need to add to your jest.config.js moduleNameMapper: { '^vue$': '@vue/compat', }

to tell your test-environment about compat build

Regarding findAllComponents - IMO it is worth separate issue. Frankly speaking I find it a bit... wrong... finding stub instead of stubbed component (findAllComponents(BaseLink) should work), but this is point of separate discussion

marina-mosti commented 3 years ago

Thanks @xanf 👍🏻 that clarifies. I'll open a new issue then for the other

lmiller1990 commented 3 years ago

If you found a bug, you should definitely open an issue, thanks @marina-mosti!

I'll reply in that issue if/when you open it. 👍

In general stubs are probably the mosts complex part of VTU. I am sure there are going to be some slight breaking changes between v1 and v2, but we should definitely match the old behavior as much as possible. If something worked in VTU v1, ideally it should work here, too.