vuejs / vue-test-utils

Component Test Utils for Vue 2
https://vue-test-utils.vuejs.org
MIT License
3.57k stars 670 forks source link

Hyphens are stripped from kebab-case attributes when camelCase props present #1190

Closed chrisbradleydev closed 1 year ago

chrisbradleydev commented 5 years ago

Version

1.0.0-beta.29

Reproduction link

https://github.com/ctbradley/vue-test-utils-demo

Steps to reproduce

npm i

npm run test

or run using wallaby...

the most relevant files are:

src/components/HelloWorld.vue

src/components/HelloWorld.test.ts

src/components/VForm.vue

comment out props in VForm.vue to demonstrate behavior

What is expected?

VFormImport attributes are expected as kebab-case.

What is actually happening?

VFormImport attributes are being stripped of their hyphens.


Note that this only affects components that are being imported as demonstrated in src/components/HelloWorld.test.ts.

hectorguo commented 4 years ago

I have the same issue after upgrading Webpack from 1 to 4.

// Before
<loader-bar-stub
   is-loading="true"
/>

// After
<loader-bar-stub
   isloading="true"
/>

I don't know which lib converted it. Maybe babel or vue-test-utils. I would really appreciate if anyone could hep identify the root cause.

I'm using babel 6 and vue-test-utils@1.0.0-beta.29

afontcu commented 4 years ago

Hi @hectorguo! Just to cross off some stuff from the list, is it possible for you to update to latest version of vue-test-utils (v1.0.3) and check if the issue is still there?

hectorguo commented 4 years ago

Hi @afontcu, thanks for the quick response. I've found the root cause and got a working solution.

The reason is because of this line code: https://github.com/vuejs/vue-test-utils/blob/dev/packages/create-instance/create-component-stubs.js#L119

Previously, I'm using require(...) to import child components in Vue. After upgrading to webpack v4, vue-loader deprecated the support for the commonJS. So it's changed to require(...).default.

Before upgrading, the originalComponent is something like { default: VueComponent }. So all options (attrs, props, etc.) are returning undefined.

After changing to require(...).default, the output becomes VueComponent directly, all options can get the correct values.

Root Cause

However, this line code is passing Vue props to HTML attributes.

In Vue render, all HTML attributes will be converted to lowercase since they are case insensitive. So when creating stub components, if the props are camelCase, it will be converted to lowercase instead of kebab-case. It's why the snapshot is different.

export default {
    name: 'LoadingBar',
    props: ['isLoading']
}

// Before
<loading-bar is-loading="true" />

// After
<loading-bar isloading="true" />

Here is a sandbox link to proof that Vue render is lowercasing attrs: https://codesandbox.io/s/vue-render-function-vnul1?file=/src/components/CustomList.vue image

Solution

There are 2 solutions for it.

  1. change line 119 to

    // To make sure there is always a default wrapping the component
    // It's working great when upgrading webpack from v1 to v4 
    const componentOptions = resolveOptions(originalComponent.default ? originalComponent : { default: originalComponent }, _Vue)
  2. Hyphenate $props

    render: function render(h, context) {
      // it will make sure camelCase props will be converted to kebab-case
      // e.g. `{ isLoading: true } ` to `{ 'is-loading': true }`
      const hyphenatedProps = Object.keys(this.$props).reduce((acc, key) => {
        const hyphenatedKey = hyphenate(key);
        acc[hyphenatedKey] = this.$props[key];
        return acc;
      }, {})
      return h(
        tagName,
        {
          attrs: componentOptions.functional
            ? Object.assign({}, context.props,
                context.data.attrs,
                {class: createClassString(
                  context.data.staticClass,
                  context.data.class
                )})
            : hyphenatedProps
        },
        context ? context.children : this.$options._renderChildren
      )
    }})

I really hope that it can be fixed in current version instead of hacking by myself.

philgruneich commented 3 years ago

Struggling with this issue since while upgrading to the latest @vue/test-utils version (1.2.1). Stubs' attributes are created as merged lowercase strings, therefore, attributes such as "data-test" become "datatest" and then most selectors are breaking. Is there any temporary fix that can be done?

ebisbe commented 1 year ago

https://github.com/vuejs/vue-test-utils/issues/1564#issuecomment-1424857260