Vue.jsのテストでwrap越しに子コンポーネントのevent up（$emit）をエミュレートする方法

# 概要

Vue.js（Vue CLI v4系）で、wrap = shallowMount() 越しに次のことをテストする方法について記載する。

* 子コンポーネント（Component）からevent upされた状態を検証する方法
* 対象のVueコンポーネントからevent upしたことを検証する方法

内容としては、公式サイトのガイドやAPI仕様書に記載されている通りなのだが、私はそれだけでは「実際にどのようにするのか？」を読み取れなかったので、その観点でのメモが目的。

# 対象とする被テストのコンポーネント

例として、画像ファイルを登録する前の説明ページを使う。
「ファイルの取り扱い」ダイアログを表示して、
その後に「同意する」チェックボックスを押した場合にのみ、
「画像を登録」ボタンを押せる仕様、とする。

実際の画像登録は、親コンポーネントへemit()して処理するものとし、
被テストのコンポーネントでは取り扱わない。


```vue:Explanation.vue
<template>
<div>
    <div id="id_button_confirm_privacy" v-on:click="openModalOfPrivacy">
        <span>【登録された画像ファイルの取り扱いについて】</span>
    </div>
    <!-- コンポーネント ModalConfirmPrivacy -->
    <ModalConfirmPrivacy id="id_modaldialog_pivacy" v-on:close="closeModalOfPrivacy" v-if="isModalDialog">
        <h3 slot="header">登録ファイルの取り扱いについてダイアログ</h3>
        <span slot="body">
            ここに任意のテキストを入れる。<br>
        </span>
    </ModalConfirmPrivacy>
    <br>
    <div id="id_checkbox_agree_privacy" v-on:click="toggleAgree">
        <span id="id_agree2policy" v-show="isAgree">[レ]</span>
        <span id="id_not_agree2policy" v-show="!isAgree">[ _ ]</span>
        <span>「ファイルの取り扱い」に同意する</span>
    </div>
    <br>
    <div id="id_button_select_image" v-on:click="go2Next">
        【画像を登録】
    </div>
</div>
</template>

<script>
// javascriptファイルをココへ配置
import ModalConfirmPrivacy from './ModalConfirmPrivacy.vue';

export default {
    name : "Explanation",
    components : { ModalConfirmPrivacy },
    data : function () {
        return {
            isModalDialog: false,
            isHavingSeen : false,
            isAgree : false
        }
    },
    methods : {
        openModalOfPrivacy : function () {
            this.isModalDialog = true;
        },
        closeModalOfPrivacy : function () {
            this.isModalDialog = false;
            this.isHavingSeen = true;
        },
        toggleAgree : function () {
            this.isAgree = (this.isHavingSeen) ? !this.isAgree : false;
        },
        go2Next : function () {
            if(this.isAgree){
                this.$emit('go2SelectImage')
            }
        },
    }
}
</script>

<style scoped>
/* Cssファイルはここへ配置する。 */
#id_button_confirm_privacy {
    cursor: pointer;
    text-decoration: underline;
}
#id_button_select_image {
    cursor: pointer;
    text-decoration: underline;
}
</style>
```

以下の環境で動作検証

```環境
    "vue": "^2.6.11"

    "@vue/cli-plugin-babel": "~4.2.0",
    "@vue/cli-plugin-eslint": "~4.2.0",
    "@vue/cli-plugin-unit-mocha": "~4.2.0",
    "@vue/cli-service": "~4.2.0",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-eslint": "^10.0.3",
    "chai": "^4.1.2",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.1.2",
    "vue-template-compiler": "^2.6.11"
```

※なお、`vue create` でインストールされる
  `@vue/test-utils@1.0.0-beta.31` だとtrigger() でDOMが更新されないように見える。
  `@vue/test-utils@1.0.0-beta.29` だと問題ない。
  なので、`vue create` した後で、`npm install @vue/test-utils@1.0.0-beta.29 --save-dev` にて上書きしてダウングレードインストールして動作検証した。

※↑の理解、誤り。DOMの更新は、$nextTick()をもって待たねばならないのが仕様。
  beta.29で動作したのは偶々だろう。
  たとえば次のようにPromise（もしくはawiteショートハンド）してから先に進めばよい。
  後で記事を修正する。

```
  // DOM変化（v-show, v-if）を伴った応答をテストするには、DOMの更新後を待つ必要がある。
  const waitVm = function (wrapper) {
    return new Promise((resolve)=>{
      wrapper.vm.$nextTick(()=>{
        resolve();
      })
    })
  };
``` 


# 子コンポーネントからのevent upを、スタブを用いてエミュレートする

Explanation.vueをshallowMount()でwrapper生成して、
コンポーネントModalConfirmPrivacyの `close` イベントに紐づけられた
`closeModalOfPrivacy()` が呼ばれた状態をエミュレートするには、
次にようにする。

1. shallowMount() に stabs オプションを指定して、「closeイベントを$emitするノードを持ったスタブ」を設定する
1. スタブ内のノードを trigger()して $emitを経由してcloseイベントを発火する

これにより、`closeModalOfPrivacy()` を直に呼び出すのではなく、
あくまでも `close` イベントに紐づけられたソレを呼び出した状態、を検証できる。

具体的なテストコードの例は以下となる。

```Explanation.spec.js
import { shallowMount } from '@vue/test-utils';
import { expect } from 'chai';
import targetVue from '@/components/Explanation';

describe('Explanation.vue', () => {
  const factory = (propsData)=>{
    return shallowMount(targetVue,{
      stubs: {
        ...propsData
      }
    });
  };

  describe('画像ファイルの取り扱いへ同意のチェックボックス', ()=>{
    it('ダイアログModalConfirmPrivacyでcloseModalOfPrivacy()が呼ばれた後は、id_checkbox_agree_privacyをクリックしてid_agree2policyが表示（チェック）へ変化する', () => {
      const wrapper = factory({
        ModalConfirmPrivacy : '<div><span id="_stub_id_modal_close" @click="$emit(\'close\')">closeボタンだけを持ったモーダルダイアログへスタブ化</span></div>'
      });
  
      // チェックボックス（相当）の初期状態を検証
      expect(wrapper.find('#id_agree2policy').isVisible()).to.be.false;
      expect(wrapper.find('#id_not_agree2policy').isVisible()).to.be.true;

      // ダイアログを開いて閉じる。
      wrapper.find('#id_button_confirm_privacy').trigger('click');
      wrapper.find('#_stub_id_modal_close').trigger('click'); // ↑をclickしてからでないと、これは取れない。

      // チェックボックス（相当）をクリック
      wrapper.find('#id_checkbox_agree_privacy').trigger('click');
  
      // チェックボックス（相当）が変更されたことを検証
      expect(wrapper.find('#id_agree2policy').isVisible()).to.be.true;
      expect(wrapper.find('#id_not_agree2policy').isVisible()).to.be.false;
    });
  });
});
```



# 被テスト対象のVueコンポーネントで $emit() されたことを検証する方法

コンポーネント内で「`this.$emit('発火するイベント名称')` が呼ばれたか？」
を検証するには `emitted()` を用いる。

具体的なテストコードの例は以下となる。

```Explanation.spec.js
import { shallowMount } from '@vue/test-utils';
import { expect } from 'chai';
import targetVue from '@/components/Explanation';

describe('Explanation.vue', () => {
  const factory = (propsData)=>{
    return shallowMount(targetVue,{
      stubs: {
        ...propsData
      }
    });
  };


  describe('画像ファイル登録ボタン', ()=>{
    it('id_agree2policyが表示（チェック済み）ならば、親コンポーネントのgo2SelectImage()が呼ばれること', () => {
      const wrapper = factory({
        ModalConfirmPrivacy : '<div><span id="_stub_id_modal_close" @click="$emit(\'close\')">closeボタンだけを持ったモーダルダイアログへスタブ化</span></div>'
      });

      // ダイアログを開いて閉じる。
      wrapper.find('#id_button_confirm_privacy').trigger('click');
      wrapper.find('#_stub_id_modal_close').trigger('click'); // ↑をclickしてからでないと、これは取れない。
      // チェックボックス（相当）をクリック
      wrapper.find('#id_checkbox_agree_privacy').trigger('click');
  
      // 画像登録ボタンをクリック
      wrapper.find('#id_button_select_image').trigger('click');

      // 親コンポーネントのgo2SelectImage()を呼び出したことを検証
      // ※引数がある場合は、キー（例：'go2SelectImage'）のバリューとして配列で入ってくる。
      //   今回の事例では引数無しなので省略。
      expect(wrapper.emitted()).has.property('go2SelectImage');
    });
  });
});
```

以上ー。

# 参考サイト

* イベントをシミュレーションする - Vueテストハンドブック
    * https://lmiller1990.github.io/vue-testing-handbook/ja/simulating-user-input.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B
* wrapperで利用できるプロパティとメソッド
    * https://vue-test-utils.vuejs.org/ja/api/wrapper/#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3
* 一般的なヒント - Vue Test Utils
    * https://vue-test-utils.vuejs.org/ja/guides/common-tips.html
* trigger(eventType [, options ]) - Vue Test Utils.html
    * https://vue-test-utils.vuejs.org/ja/api/wrapper/trigger.html
* Vue.jsのテストでコンポーネントをいい感じにwrapする方法
    * https://qiita.com/ykhirao/items/8e8a9547a693c677813c
    * （※結局私は、↑のQiitaページを見てようやく方法が分かった＾＾；）