vuejs / vue-test-utils

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

Documentation for testing a component without resolving sub-components #497

Closed lambdalisue closed 6 years ago

lambdalisue commented 6 years ago

What problem does this feature solve?

Intro

Assume that there are two components, <x-icon> and <x-checkbox>.

The <x-icon> component is for showing a font icon given by face="..." attributes like

<x-icon face="error"/>              <!-- It shows an error icon -->
<x-icon face="checkbox-empty"/>     <!-- It shows a checkbox empty icon -->
<x-icon face="checkbox-checked"/>   <!-- It shows a checkbox checked icon -->

And <x-checkbox> uses <x-icon> with face attribute like

<div class="checkbox">
    <x-icon :face="face"/>  <!-- 'face' attributes are internal and linked to 'checked' attribute -->
    <input v-model="checked" type="checkbox">
</div>

So the final HTML will be one of the followings (without resolving <x-icon>)

<div class="checkbox">
    <x-icon face="checkbox-empty"/>
    <input  type="checkbox">
</div>

<!-- or -->

<div class="checkbox">
    <x-icon face="checkbox-checked"/>
    <input type="checkbox" checked>
</div>

What I would like to do

I would like to test <x-checkbox> component without understanding an implementation of the <x-icon>. For example, I would like to check

  1. Is initial HTML shows <x-icon face="checkbox-empty">
  2. Is HTML shows <x-icon face="checkbox-checked"> after <x-checkbox> has clicked.

For this purpose, I cannot use stubs: { 'x-icon': true } while

  1. x-icon is defined in components attributes of x-checkbox component and in this case, stubs seems ignored.
  2. stubs creates an empty comment so I cannot check if the component is x-icon or not
  3. stubs removes attributes even I uses stubs: { 'x-icon': SomeThing } so I cannot check if the component attributes of x-icon has correctly changed

What does the proposed API look like?

I'm not really proposing but currently, I use a wrapper function to

  1. Overwrite localVue.config.isUnknownElement function to allow elements in components
  2. Remove components to prevent element resolving

like

import { mount as mountBase } from '@vue/test-utils';

const original = Vue.config.isUnknownElement;

export function mount(component: any, options: any= {}) {
  const localVue = options.localVue || createLocalVue();
  if (!!component.options && !!component.options.components) {
    const ignoredElements = Object.keys(component.options.components);
    Vue.config.isUnknownElement = (tag: string): boolean => {
      if (ignoredElements.includes(tag)) {
        return false;
      }
      return original(tag);
    }
    component.options.components = {};
  }
  return mountBase(component, {
    ...options,
    localVue,
  });
}

Is this a correct way?

iztsv commented 6 years ago

@lambdalisue please see similar feature request here #410

lambdalisue commented 6 years ago

Actually, I've read that and https://github.com/vuejs/vue-test-utils/issues/458 before making this issue. Yes, it's similar but the point is stubs remove attributes so it does not suit my purpose. Am I wrong?

iztsv commented 6 years ago

as a workaround you can try to use generateStubs: https://github.com/vuejs/vue-test-utils/issues/410#issuecomment-375894160

I hope that I'll be have enough time at current holidays and send PR to solve the issue.

lambdalisue commented 6 years ago

@ilyaztsv I've tried that one several days ago and it did not work in my environment so I ended up. Today I fixed that function (commented on #410) and that works perfectly. Thanks for pointing me a correct direction 👍

lambdalisue commented 6 years ago
<!-- x-icon -->
<template>
  <i class="icon">{{ face }}</i>
</template>
<script lang="ts">
export default {
  name: 'IconView',
  prop: ['face'],
}

<!-- x-checker -->
<template>
  <div class="checker">
    <x-icon @click="clicked" :face="face"/>
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import IconView from './icon.vue';

@Component({
  components: {
    'x-icon': IconView,
  }
})
export default class CheckerView extends Vue {
  checked: boolean = false;

  get face(): {
    return this.chekced ? 'checkbox-checked' : 'checkbox-empty';
  }

  clicked(): void {
    this.checked = !this.checked;
  }
}
</script>

With this case, stubs way has the following issues but my way.

// XXX: Imaginally code

const checker = shallow(CheckerView, {
  stubs: generateStubs(CheckerView),
});

const icon = checker.find('x-icon');

// With 'stubs', this click event cannot call 'clicked'
icon.trigger('click');

// So the follwoing fail.
expect(checker.vm.checked).toBe(true);
eddyerburgh commented 6 years ago

You can use shallow and test the component props:

const wrapper = shallow(TestComponent)
wrapper.find(XComponent).props()

If the stubs option doesn't work, that is a bug. The intended behavior is to always stub the component

lambdalisue commented 6 years ago

I'd love to but as I explained above, it is not what I expected.

eddyerburgh commented 6 years ago

I'm closing this issue as it's not clear to me what the intention is.

The original issue is a feature request. We have recently changed stubs to render the component name, which I believe satisfied your use case. If you would like a different feature, please create a new issue with a proposal.

If you would like to report a bug, like you mentioned in your other components, please create an issue with a reproduction.