概要
Vue.js+TypeScriptでElement-ui利用なプロジェクトで単体テストを書いてたらテストが完了せずにハマったので覚書です。
Elementについては下記をご参考ください。
Element
http://element.eleme.io/#/en-US
Vue.jsのコンポーネント詰め合わせ「Element」がスゴかった
https://s8a.jp/vue-js-library-element
すべてを調べてないので、あれですが、ElementのLoadingコンポーネントをServiceとして利用する場合、close
メソッド内で、setTimeout
を利用しているのでnextTick
を忘れないようにしましょう(結論)
http://element.eleme.io/#/en-US/component/loading
Githubに検証で利用したプロジェクトをUPしています。よければご参考ください。
https://github.com/kai-kou/vue-js-typescript-element-ui-unit-test
準備
Vue-Cliで環境を作ります。
ここではDockerを利用して環境構築していますが、ローカルで構築してもらってもOKです。
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> vi Dockerfile
> vi docker-compose.yml
FROM node:10.8.0-stretch
RUN npm install --global @vue/cli
WORKDIR /projects
version: '3'
services:
app:
build: .
ports:
- "8080:8080"
volumes:
- ".:/projects"
tty: true
> docker-compose up -d
> docker-compose exec app bash
単体テストも書くので、Unit Testを忘れずに。
> vue create app
Vue CLI v3.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter, Unit
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Pick a linter / formatter config: TSLint
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Mocha
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
Use NPM
プロジェクトが作成できたら、Elementをインストールします。
> cd app
> yarn add element-ui
これで環境が整いました。
検証
プロジェクト作成すると最初からあるHelloWorldコンポーネントでElementのLoadingコンポーネントを利用します。
画像をクリックするとLoadingが表示されるようにします。
ただ、検証のため、ブラウザではほぼ視認できないかと思います^^
(略)
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
private mounted() {
const loadingInstance = Loading.service({});
loadingInstance.close();
}
}
(略)
それでは、単体テストを実行してみます。
> yarn test:unit
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed (179ms)
1) renders props.msg when passed
1 passing (2s)
1 failing
1) HelloWorld.vue
renders props.msg when passed:
Uncaught TypeError: Cannot read property 'split' of undefined
at getTransitionInfo (dist/webpack:/node_modules/vue/dist/vue.runtime.esm.js:7003:1)
at whenTransitionEnds (dist/webpack:/node_modules/vue/dist/vue.runtime.esm.js:6973:1)
at Timeout._onTimeout (dist/webpack:/node_modules/vue/dist/vue.runtime.esm.js:7201:1)
MOCHA Tests completed with 1 failure(s)
はい。
テストはパスしているものの、エラーが発生しています。エラーについても、
renders props.msg when passed:
Uncaught TypeError: Cannot read property 'split' of undefined
とあり、なんのことかよくわかりません。
原因
とりあえず、ElementのLoadingコンポーネントの実装を眺めてみます。
LoadingConstructor.prototype.close = function () {
var _this = this;
if (this.fullscreen) {
fullscreenLoading = undefined;
}
(0, _afterLeave2.default)(this, function (_) {
var target = _this.fullscreen || _this.body ? document.body : _this.target;
(0, _dom.removeClass)(target, 'el-loading-parent--relative');
(0, _dom.removeClass)(target, 'el-loading-parent--hidden');
if (_this.$el && _this.$el.parentNode) {
_this.$el.parentNode.removeChild(_this.$el);
}
_this.$destroy();
}, 300);
this.visible = false;
};
(0, _afterLeave2.default)
なるものが、なにやら怪しかったので、実装を見てみます。
export default function(instance, callback, speed = 300, once = false) {
if (!instance || !callback) throw new Error('instance & callback is required');
let called = false;
const afterLeaveCallback = function() {
if (called) return;
called = true;
if (callback) {
callback.apply(null, arguments);
}
};
if (once) {
instance.$once('after-leave', afterLeaveCallback);
} else {
instance.$on('after-leave', afterLeaveCallback);
}
setTimeout(() => {
afterLeaveCallback();
}, speed + 100);
};
oh。setTimeout
ですね。
ここで、Elementのドキュメントのことを思い出しました。
let loadingInstance = Loading.service(options);
this.$nextTick(() => { // Loading should be closed asynchronously
loadingInstance.close();
});
はい。
this.$nextTick
を忘れていました(白目
Loading should be closed asynchronously(ロードは非同期で終了する必要があります)
とありますし。
では、HelloWorldコンポーネントを修正します。
(略)
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
private mounted() {
const loadingInstance = Loading.service({});
this.$nextTick(() => {
loadingInstance.close();
});
}
}
(略)
テストを実行します。
> yarn test:unit
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed (535ms)
1 passing (633ms)
MOCHA Tests completed successfully
はい。
無事にエラーが発生しなくなりました。
ハマっていた当初はVuexやaxiosも利用していたので、原因はどこだーとあれこれ探って見つけられず仕舞いでしたが、こうやってシンプルな実装で検証すると見つかるものですね。教訓。
参考
Element
http://element.eleme.io/#/en-US
Vue.jsのコンポーネント詰め合わせ「Element」がスゴかった
https://s8a.jp/vue-js-library-element
Element - Loading
http://element.eleme.io/#/en-US/component/loading
Vue.js+TypeScriptで開発するときの参考記事まとめ
https://qiita.com/kai_kou/items/19b494a41023d84bacc7