- 投稿日:
Jestで無限ループ処理をテストするときにループから抜けられなくて困ったのでメモ。
参考までにこの記事ではイベントループのように脱出手段のある無限ループを想定しています。
確認環境
Env | Ver |
---|---|
@types/jest | 29.5.7 |
jest | 29.7.0 |
typescript | 5.2.2 |
サンプルコード
テスト対象のコード
import { getHandle } from './lib/get-handle';
export const main = () => {
for (;;) {
const handle = getHandle();
if (handle === 'Click') {
console.log('click');
} else if (handle === 'KeyDown') {
console.log('keydown');
} else {
break;
}
}
};
テストコード
.mockReturnValue()
ではなく.mockReturnValueOnce()
を使うのが肝です。
.mockReturnValue()
だと同じ値が毎回返るので無限ループから抜けられませんが、.mockReturnValueOnce()
は一回だけなので抜けられます。チェーンすると二回目、三回目の返り値も設定できます。
なお、以下の例では.mockReturnValueOnce()
をチェーンさせていますが、させなくてもテストとしては正常に動作します。指定がない場合はundefined
が返るようです。(基本的には明示的に指定しておいた方が良いと考えています。
import { main } from '.';
import * as getHndl from './lib/get-handle';
jest.mock('./lib/get-handle');
describe('main', () => {
it('Clickイベントの分岐に入ること', () => {
jest
.spyOn(getHndl, 'getHandle')
.mockReturnValueOnce('Click')
.mockReturnValueOnce('');
const spiedConsoleLog = jest.spyOn(console, 'log');
main();
expect(spiedConsoleLog).toHaveBeenCalledWith('click');
});
});
余談
.toHaveBeenCalledWith()
は.toEqual()
と同じロジックで判定しているらしいので、.toStrictEqual()
版もあると便利な気がしました。気が向いたらPR出してみようかな…。
参考
- 投稿日:
特急はまかぜ大阪行きは姫路駅で長時間停車を行うことがありますが、播但線ホームに停車するため停車したホームには売店がありません。しかし隣のホームには売店が見えます。
何とかして買いたいですが、播但線から出るには中間改札を抜ける必要があります。しかし中間改札を抜けると切符を取られて戻ってこられなくなりそうです。中間改札を出入りして売店を利用することはできないのかと思ったのですが、流石に事故ると怖いし、実験するのも大変なのでJR西日本に聞いてみました。
結論としては停車中に中間改札を往来することは可能ですが、ICカードで自動改札を利用するとエラーになるので駅係員に切符ないしICカードを提示して通行すれば問題ないとのことでした。切符を通した場合にどうなるのかは教えてくれなかったので謎ですが、暇な時に試してみようかとは思います。(不審者に見られそうですが…)大阪市営地下鉄の定期券みたいに出入り自由ならワンチャンある気がします。
勿論、改札通過時に駅員が別に対応をしているなどではまかぜの乗車に間に合わなかった場合は自己責任になるので、ほぼ賭けです。ぶっちゃけほとんどやる意味はないと思います。ただ香澄駅からはまかぜ6号に乗ると駅弁がないんですね。折角の特急なので食べたいじゃないですか。そもそもあの時間に駅弁売ってるかや、在来線ホームでいまだに販売があるのかが謎ですが…w
- 投稿日:
タイトル通りトップページのレイアウトを変えてみました。
誰が見に来てるのか解らない部分なので変えたところでアレですが…w(トップページはアクセス計測してないので。。。)
PCビュー
変更前は縦長のレイアウトだったのですが、もうちょっと余白を活かした方がいいかなと思い、横にもレイアウトしてみました。デザインセンスが皆無なのでなんか余計に微妙になった気もしますが…。
他にも表組だった場所を段落にしたり、Sponsoredのバナーを縦置きにしたり細々と変えてます。
変更前 | 変更後 |
---|---|
![]() |
![]() |
SPビュー
経験技術のところは見やすさがマシになったと思います。余白が詰まって少し息苦しくなった気もします。
PCビューが縦長だったのはレスポンシブ対応をサボる為というのもあったので、今回はそこそこ真面目にやりました。結果、まぁまぁちゃんとレスポンシブになっているとは思います。まぁ、一応本業なので…。ブレークポイント周りの挙動は怪しい気もしてますが…(
変更前 | 変更後 |
---|---|
![]() |
![]() |
あとがき
半ば何のためにあるかわからないトップページですが、素のHTMLを書く機会が少ないので、それを書くためだけにある節があります。それもあってJSは入れず、HTMLとCSSだけで書いてます。
一応自分なりにセマンティクスには拘って書いているつもりですが、概ね自己満足レベルな気はしています。Lighthouseの数値は悪くない気がするので、無難なところかなという気はしていますが…。
Lighthouseの結果は取り合えずぼちぼちな気はする。
まぁまた気が向いたときにいじろうと思います。この新レイアウトも8月頭に着想して原型を作って、中途半端な状態で今日まで放置してたのを直して何とか形にしたくらいなので…。
こんなんでもやってるとMedia Queries Level 4という新機能を知れたり、HTMLやCSSを書く能力が多少は鍛えられるので悪くないです。
MDNによるとMedia Queries Level 4の対応状況は「それなり」ということですが、今回使用したMedia Queries: Range Syntaxは、どのブラウザも対応してくれてそうで良かったです。MDNでも全てのブラウザがFull supportとあるので安心です。
- 投稿日:
ローカルで動作するNode.jsのライブラリ(node_modules
)欲しくないですか?欲しいですよね?という訳で作ってみました。
要件としてはTypeScriptで実装出来、Jestでテスト可能で、ESLintでLintが可能というところです。
確認環境
Env | Ver |
---|---|
@swc/cli | 0.1.62 |
@swc/core | 1.3.93 |
@swc/jest | 0.2.29 |
@types/jest | 29.5.5 |
@types/node | 20.8.6 |
@typescript-eslint/eslint-plugin | 6.7.5 |
@typescript-eslint/parser | 6.7.5 |
eslint | 8.51.0 |
eslint-config-prettier | 9.0.0 |
eslint-plugin-jest | 27.4.2 |
jest | 29.7.0 |
jest-watch-typeahead | 2.2.2 |
prettier | 3.0.3 |
ts-jest | 29.1.1 |
typescript | 5.2.2 |
Node.js | 20.8.0 |
npm | 10.1.0 |
サンプル実装
https://github.com/Lycolia/ts-library-example
実装について
フォルダ構成
monorepoでmainからlibrary/singleやlibrary/multi/hoge, piyoを参照するような構成です。
root
└─packages
├─library // ライブラリ側
│ └─packages
│ ├─multi // 複数ファイルのライブラリ
│ │ └─src
│ │ └─utils
│ │ ├─hoge
│ │ └─piyo
│ └─single // 単一ファイルのライブラリ
│ └─src
└─main // ライブラリを使う側
└─src
└─libs
実装時のポイント
これが全てというわけではないと思いますが、一旦今回作ったもののポイントを解説していきます。利用側がtsc
を利用しない場合、ライブラリ側と利用側で構成は別々になります。
またこの実装はエディタにVSCodeを利用し、import
するパスが相対パスであることを前提に説明しています。
ライブラリ側のポイント
ビルドに関して
まずビルドに関してはtsc
でやります。
これはTypeScriptで開発するためにはビルド成果物として.d.ts
ファイルが必要になるのと、Jestを通すためにビルド成果物がCommonJS形式(以下CJS)である必要があるためです。CJSが吐けるなら何でもいいとは思いますが、.d.ts
も必要になるので、tsc
を使うのが無難な選択肢だと思います。
テストファイルを出力したくないので、tsconfig.json
はビルドと開発で分けます。
またtsconfig.json
は各ワークスペースのルートに置いて置く必要があります。これはTypeScriptがtsconfig.json
のパスを起点として動作するためです。
ビルド用の設定ポイントとしては以下の通りです。
"compilerOptions"
"module": "NodeNext"
- これがないとパス解決が上手く行きません
"moduleResolution": "NodeNext"
- これがないとパス解決が上手く行きません
"declaration": true,
.d.ts
の出力に使います
"outDir": "./dist",
- ビルド成果物の出力先です
"exclude": ["src/**/*.spec.ts"]
- ビルド時にテストファイルは不要なので無視します
"include": ["src/**/*"]
- 参照するソースコードです
Jestに関して
ビルドにtsc
を使うため、Jestのローダーとしてもts-jestを利用します。公式ではbabelが推奨されているようですが、構成方法が不明だったので諦めました。BabelはJest公式の案内通りにやっても多分上手く行きません。
jest.config.js
には以下の設定を追加します。
preset: 'ts-jest'
開発用の設定について
開発とビルドでtsconfig.json
を分けるので、開発用のも必要です。これはビルド用から"exclude": ["src/**/*.spec.ts"]
を抜くだけです。
ライブラリを外部参照させる方法
基本的にはpackage.json
に外部参照させるための定義を書くことによって行います。
この辺りの仕様はdocs.npmjs.comにはなく、nodejs.orgにあります。
単一ファイルの外部参照
単一ファイルを外部参照(import出来るように)するときに使う手法です。
import
をimport { hoge } from '@my-lib/example'
みたいな書き方をしたい時に必要になるやつです。
以下の様にビルド成果物の.js
のパスをpackage.json
に追加してやると出来るようになります。
"main": "./dist/index.js",
以下の書き方でも同様に可能です。
"exports": {
".": {
"default": "./dist/index.js"
}
},
上記には他にtypes
というフィールドがあり、本来ここに.d.ts
を追加するのですが、TypeScriptが解決してくれるので、なくても動きます。一応ない場合はファイル探索を行うようなので、あった方が少しだけパフォーマンスが上がるかもしれません。
複数ファイルの外部参照
複数ファイルを外部参照(import出来るように)するときに使う手法です。
基本的に何もしなくてよいですが、import { hoge } from '@my-lib/example'
みたいな書き方もしたい場合は以下の記述が必要です。
"main": "./dist/core/index.js"
ここで指定していないものはimport { hoge } from '@my-lic/example/dist/hoge'
みたいにして参照します。dist
がダサくて嫌な場合は適当な名前に変えます。
参考までに@actions/githubはバージョン6.0.0時点でdist
に相当する部分をlib
にしており、import { Context } from '@actions/github/lib/context';
の様にして参照するようになっています。
exports
を使ってdist
を隠すことも出来るとは思うのですが面倒なので試してません。
ライブラリ側を利用する側のポイント
今回利用する側はビルドにswcを使う想定ですが、たぶんなんでも動くと思います。参考までにswc-loader + webpackでも動きました。
開発用の設定について
tsconfig.json
に以下の設定があれば恐らく最低限大丈夫だと思います。
"compilerOptions"
"module": "NodeNext"
- これがないとパス解決が上手く行きません
"moduleResolution": "NodeNext"
- これがないとパス解決が上手く行きません
"include": ["src/**/*"]
- 参照するソースコードです
ライブラリのインストール方法について
以下のように相対パスを指定するとインストールできます。
npm i ../package/library/package/single
消すときはパッケージ名を指定すれば消せます。
npm un @lycolia/library-example-single
試したけどダメだったやつら
色々してる過程で試行錯誤した名残。
ライブラリ側のJestでbabelを使おうとやったこと
以下の二通りの設定は試しましたが、どっちもダメだったので諦めました。何よりtscを使うならts-jestの方が楽なのは確定的に明らかですし…。
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
presets: [
['@babel/preset-env', {targets: {node: 'current'}, modules: 'commonjs'}],
'@babel/preset-typescript',
],
ライブラリ側をswcでビルド
.swcrc
を以下の設定にしても
"module": {
"type": "commonjs"
},
以下の出力がされるため、jest.spyOn()
が上手く動かない(get
が邪魔でhello
が見れない
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "hello", {
enumerable: true,
get: function() {
return hello;
}
});
const hello = (param)=>{
console.log(param);
};
どの道これでは.d.ts
が出せないので、あんま意味ないなと…。
参考にしたもの
実装方法は@actions/githubが一番単純で参考になると思います。
- 仕様
- 実装
- Jest周り
実装はビルド成果物とビルド前のコード、package.json
やtsconfig.json
辺りがどうなっているのかを参考にしました。