vue3.0でTDDしたくて試行錯誤

vue3.0でアプリを作成しようとしてますが、vue-cliは使わず、viteを使っています。
そのためテスト環境は自分で用意する必要がありました。

環境は
windows10＋WSL2 Docker＋node:16-buster-slim＋VScode＋typescriptです。

まずはviteでプロジェクトを作ります。

```
yarn create vite my-vue-app --template vue
```
起動出来ることを確認しておきましょう。

vue公式ページに紹介されているテストツールは2種類あり、2つとも入れてしまいます。

```
yarn add --dev @babel/core @babel/preset-env @testing-library/jest-dom @types/jest @vue/test-utils@next babel-jest jest ts-jst vue-jest@next
```
古い記事だとvue2.0用の物もあり、@nextを後ろにつけないとvue3.0のものになりません。

インストールが終わったら、少し手直しをします。

```jest.config.js
const path = require('path')
module.exports = {
rootDir: path.resolve(__dirname),
clearMocks: true,
coverageDirectory: 'coverage',
coverageProvider: 'v8',
moduleFileExtensions: ['vue', 'js', 'json', 'jsx', 'ts', 'tsx', 'node'],
// The alias set
moduleNameMapper: {
'@/(.*)$': '<rootDir>/src/components/$1'
},
preset: 'ts-jest',
testEnvironment: 'jsdom',
// The test file
testMatch: ['<rootDir>/tests/unit/*.spec.ts?(x)'],
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\js$': 'babel-jest',
'^.+\\.(t|j)sx?$': 'ts-jest'
}
}
```

```babel.config.js
module.exports = {
    presets: [
        [
            '@babel/preset-env',
            {
                'modules': 'false',
                'useBuiltIns': 'usage',
                'targets': '> 0.25%, not dead',
            }
        ]
    ],
    env: {
        test: {
            presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
        },
    },
};
```
ターミナルから"yarn test"コマンド入力でテスト実行できるようにこちらも追加

```package.json
  "scripts": {
    "test": "jest"
  },
```

現時点で動かそうとするとエラーが出ます。

```
FAIL  src/tests/HelloWorld.spec.ts
 ● Test suite failed to run

   TypeError: Cannot destructure property 'config' of 'undefined' as it is undefined.
```
jest,ts-jestのバージョンが27だとダメなようで、

```
jest@26 ts-jest@26
```
26に落とします。

後は、srcフォルダの下にでもtestsフォルダ作成、その下にテストファイルを作成していけばOKです。
以上がテスト環境構築になります。

次に、TDDするにあたって、DIするために試行錯誤してみました。
コンポーネントに必要なデータを渡すためにprovide/inject機能を使えるように触ってみました。
[公式ページ]("https://v3.ja.vuejs.org/guide/component-provide-inject.html")は何度も読みましたがいまいち分かりません笑

実プロジェクトをイメージしたときに、ログイン機能を実現するときには活用できそうです。

お手軽に本格webアプリっぽいデザインを使いたかったので、[Prime-vueのprestige]("https://www.primefaces.org/prestige-vue/#/")を有料購入しました。
![スクリーンショット 2021-08-09 065823.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157360/ccf1c5d5-cbb2-f45e-4e84-f63a1c0ee356.png)

vue3.0対応と書かれていましたが中身はjavascript＋OptionAPIで書かれてて少し残念だったので、自分好みに書き直していくことに 
prestigeの構成としてはmain.tsからAppWrapperを呼び出し、そこからログインページやログイン後のページをコントロールする形です。
![スクリーンショット 2021-08-09 065247.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157360/50d9ef62-5c6e-273c-973b-eaaf3d18b385.png)
ログイン後のアイコンやメニューなどはAppコンポーネントで行っています。
なので、AppWrapperはじめ複数のコンポーネントで状態管理をする必要があります。
そのため、provice/inject機能を使っていこうと思います。

各コンポーネントで共通する情報は「types」フォルダを作成しデータの型とします。

```types/login.ts
export interface ILogin {
    name: string;
    readonly isLogin: boolean;
    jobTitle: string;
}
```
テストでmock化することを考えてinterfaceにしています。またログインしたかどうかisLoginは勝手に書き換えられないようにreadonlyにしています。
各コンポーネントで利用するためにはデータとKeyを組み合わせなければなりません。
同じファイルにKeyも載せてしまいます。

```types/login.ts
import { InjectionKey } from "vue";
export const LoginKey: InjectionKey<ILogin> = Symbol("Login");
export interface ILogin {
    name: string;
    readonly isLogin: boolean;
    jobTitle: string;
}
```

使い方は簡単。親になるコンポーネントのsetupにてprovideします。
interfaceをどこかで実装したデータとKeyを渡します。

```AppWrapper.vue
import { LoginKey, ILogin } from "./types/login";
const login: ILogin = reactive({
    name: '山田 太郎',
    isLogin: false,
    jobTitle: '係長',
});
export default defineComponent({
  name: "AppWrapper",
  setup(props, context) {
  provide(LoginKey,login);
```

受け取るためのuseLoginStoreを作成します（作成しなくても良い）
vue-routerと同じような使い勝手になります。

```stores\loginStore.ts
import { inject } from 'vue';
import { LoginKey } from '../types/login';

export function useLoginStore() {
    const store = inject(LoginKey);
    if(!store) {
        throw new Error('store not found');
    }
    return store;
}
```

受け取るコンポーネントでは

```AppTopBar.vue
<template>
    {{ userName }} {{ jobTitle }}
</template>
<script lang="ts">
import { defineComponent, toRefs } from 'vue';
import { useRouter } from 'vue-router';
import { useLoginStore } from './stores/loginStore';
	export default defineComponent({
		name: 'AppTopbar',
	setup(props, context) {
		const loginUser = useLoginStore();
		const { name, jobTitle } = toRefs( loginUser );

		const goToHome = () => {
			const router = useRouter();
			router.push('/');
		};
		return{
			userName: name,
			jobTitle,
		};
	},
	});
```
loginUserにAppWrapperでprovideしたデータが入ってくれるのですが、データの一部を利用する場合、そのままではリアクティブにならないため、toRefsを利用して取り出します。

vuexとの使い分けなど正直分からないのですが、
provide/inject機能を使えばvuexは必要ないような気がします。
また、interfaceを利用している例が少なくこれが正解かは分かりませんがテストしやすいことを考えるとこのようになりました。

テスト自体は時間か要望があるときに。

っと書いてましたが、少し進めてみました。

```App.spec.ts
import { shallowMount } from '@vue/test-utils';
import { reactive } from 'vue';
import testComponent from '../App.vue';
import { LoginKey, ILogin } from "../types/login";
const login: ILogin = reactive({
    name: 'nanashi'; などなどを記載
});
test('inject test', () => {
  const wrapper = shallowMount(testComponent,
    {
      global: {
        provide: {
          [LoginKey as symbol]: login,
        },
      }
    }
  );
  console.log(wrapper.html());
});
```
やっててハマったこと。
* LoginKeyをコードで書いてはNG.provideとinject両方で同じファイルから取得すること（Symbolの基本なのかもしれませんが知らなかった。。）
* symbolをキーにする場合は、[]で囲むこと。また、

```ts
[LoginKey as symbol]: login,
```
as symbolを入れないとエラー。