goji.ioで正規表現Pattern

ハンドラのとこはgoji.ioで書いてます。見ると標準のnet/httpとgolang.org/x/net/contextをうまいこと活かしたからこその極薄なライブラリなのですが、リクエストパスでハンドラを切り替えるのに簡潔利便なので私はお好みです。しかし薄すぎて足りないところもあったのでパス切り替え判断のところ、ちょこっと補ってみました。

package patx

import (
    "regexp"
    "net/http"
    "golang.org/x/net/context"
)

type RegexpPattern struct {
    regexp *regexp.Regexp
}

func Regexp(expr string) *RegexpPattern {
    // MustCompileだと渡された表現文字列がコンパイルエラーのときpanicで止める。よろしい。
    return &RegexpPattern{regexp: regexp.MustCompile(expr)}
}

// goji.Patternインターフェイスを実装している
func (reg *RegexpPattern) Match(ctx context.Context, r *http.Request) context.Context {
    if reg.regexp.MatchString(r.URL.Path) {
        // 手抜きでMatchStringでの当り判定だけなので、積むものがなく、よってctxに何も積んでない。
        return ctx;
    }
    return nil
}

たったのこれだけで、とてつもなく手がかかってない実装ですが、そうはどっこい意外に便利。正規表現で書きたいこともある。

package main

import (
    "book"
    "goji.io"
    "goji.io/pat"
    "net/http"
    "patx"
)

func main() {
    mux := goji.NewMux()
    // 拡張子6種を簡単な正規表現でマッチさせてhttp.FileServerへルーティングする
    mux.Handle(
        patx.Regexp(`(.*\.(css|ttf|gif|png|jpg|js))$`),
        http.FileServer(http.Dir("public"))
    )
    mux.HandleFuncC(pat.Get("/book/list"), book.ListBookJSON)
    mux.HandleFuncC(pat.Get("/book/:title"), book.ListPageJSON)
    // こっから先はGoは静的HTML出すだけで、reactアプリになる
    mux.HandleFuncC(pat.Get("/*"), book.IndexHTML)
    http.ListenAndServe("localhost:8080", mux)
}

こんな感じです。GAEだと、こんな取り込みはいらず、yamlファイルに正規表現書いて同じことができますけど、平場のGOだと自分で書く。

なんで平場のGoで書かないといけないのか

GAE/GoのSDKでSierra対応版がまだ出てこないから。仮想でやるのは重いので緊急避難。GAE機能つかってるところはダメでも、そこらへんだけ殺してモック的に動かしておいて、当面はクライアントのところだけを書いてましょう。

後に気がつきましたが、macOS 10.12.1+go1.7.3 darwin/amd64環境下でGoのデバッガが「could not launch process: could not get thread count」と吐いてクラッシュしてしまいます。インクリメンタルデバッグはできない。実行はできるので、その延長線にてユニットテストはできる。

Goの正規表現

Syntax · google/re2 Wiki · GitHub

こちらの仕様とのことらしく。named captureができないなーと思ってたら、?P<name>が正解で?<name>は非サポートだった。。。

可搬性の高いGoのVendoring環境整備

GAE/Goでも、package main/func mainのGoでも、プロジェクトはdirenvをつかってVendoringを実現すると可搬性高くて最高というメモ。

direnv

シェルの実行パスに置かれたローカル設定を動的に反映させてくれるかしこいツール。素敵。インストールはHomeBrewで可能。マメとしてはGoで書かれてます。

http://direnv.net/

.bash_profile

# ~/.bash_profile
eval "$(direnv hook bash)"

direnvでシェルを監視します。私はmacOSbashなので上記のとおり。

.envrc

コントロールしたいフォルダに.envrcって名前の設定ファイルを置く。今はこんなの書いています。書いて配置したあと、一回はコンソールでフォルダにはいって、$ direnv allow って打たないといけない。

# ~/Projects/bookreader/.envrc
export GOPATH=$PWD/vendor:$PWD
# export PATH=$PWD/vendor/bin:$PWD/bin:$PATH

GOPATHは最初に、./vendor で次に ./ と二つをこの順番で通す。PATHはそれぞれのbinだけどGAEだと ./binはでてこないし、私の使い方では ./vendorの下にもbinがでてきたことない。gofmtとかgoimportとかビルド時ツールがプロジェクト内で管理されてたりすると必要かな。

# ~/Projects/.envrc
export PATH=~/go_appengine_sdk:$PATH

GAE SDKのパス設定は適宜上位フォルダに.envrcを置いて通している。フォルダツリーに従ってちゃんと.envrcの設定がカスケードされる。こうすると、ローカルPCで動かすGoとGAE/Go向けGoとクロスコンパイルするGoとで同じPCかつ同じIDEを使い分けるのも簡単。

プロジェクトのフォルダ構成(GAE版)

$ tree -a -L 3 ./Projects/bookreader/
./Projects/bookreader/
├── .envrc
├── .idea
├── src
│   ├── app.go
│   ├── app.yaml
│   ├── book
│   │   ├── book.go
│   │   └── book_test.go
│   └── public
│       └── js
└── vendor
    ├── pkg
    │   └── darwin_amd64_appengine
    └── src
        ├── goji.io
        └── golang.org

こんな感じ。GOPATHはプロジェクトのルートより優先でvendorフォルダを指定しているので、goapp getでもgo getでもvendorフォルダに格納されて自分のコードと混在しない。gbとかの専用Vendoringツールが必要なく綺麗。GAEのyamlファイルはルートにおいていないのでvendorの中はリンクされるものだけを取ってくるので、GAE的に危険なコードがはいってても(アプリのソースから使わなければ)問題ない。

WebStormの設定

f:id:masataka_k:20161029080135p:plain

direnvが動的に設定するからなのか、まだ何か研究が足りないのか、WebStormのほうではライブラリパスを明示的に追加しないとうまくなかった。この設定をするとコード補完からビルドやデバッグまでALL OK。

ちなみにapp.yaml

version: 1
runtime: go
api_version: go1
handlers:
- url: /(.*\.(css|ttf|gif|png|jpg|js))$
  static_files: public/\1
  upload: public/.*\.(css|ttf|gif|png|jpg|js)$
- url: /.*
  script: _go_app

省略したけど、実際のプロジェクトでは同じプロジェクト内にreact + redux + material-uiのクライアントアプリもあって、gulpによるビルドで/src/public/jsの下にコピーしてきています。

react, react-router, redux, react-redux, react-router-redux, redux-actions, redux-form, material-ui, redux-form-material-ui, axiosというスタック。開発はbabel, eslint, jasmine, browserifyで。それぞれよくできている。

Sierraの上に仮想ElCapitanを作る

およそOKだったので、本体もSierraにアップデートしてしまいました。

試した範囲でおよそ問題なかったのですが、アップデートしてから動かなくなってしまったのに気がついたのが一つ、Go for Google App EngineSDKです。こちら内部にGo 1.6.2を抱え込んでいるのですがこれがSierraに対応していなかった。Go1.7でSierra対応したのを、バックポートのGo1.6.3で解消ということで、これらの最新Not GAE環境でのGoはストレスなく動作しますが、GAEのSDKに組み込まれて取り替えられないのでアップデート待ち。 具体的には以下のような動作をおこします。

  • $ goapp serve でローカルサーバでアプリを立ち上げるのは一回目は大丈夫だけど、一度でもCtrl+Cで終了したらFatal Errorで二度と上がらない。OS再起動すると、また一度は上がる。
  • エラーログを眺めると、go-app-buildコマンドが突然のパニックで終了している。これが根本問題なんでしょうね。

問題のきりわけでEl CapitanをSierra上に作りました。手順はSierraでやった通りのままなので一度経験している強みから、悩みも不安もなく迅速に進みます。El CapitanのダウンロードはSierraの時と違ってとても速く、待ち時間もさほどなくすすみました。そうして仮想環境つくって操作してみたら、ちゃんとアプリの上げ下げも、ビルドもできました。終了。

仮想Sierra環境を作る

f:id:masataka_k:20160924084408p:plain

Mac環境を汚さないように壊れてよい仮想なMacを作ります。どうせいまなら出たばかりのSierraで。Mac on MacVMWareをつかうと超簡単と聞きますが、追加無償でも十分!VirtualBoxで作ります。ホストはEl Capitan。トータルで2時間半ぐらいかかりましたが、ほとんどがSierraのダウンロードと、インストールの待ち時間であり、作業は正味30分もかからなかったと思います。

Sierraのイメージを手にいれる(実績1時間以上かかった)

App Storeで「macOS Sierra」の「ダウンロード」ボタンを押してダウンロード開始。回線状態にもよるでしょうが大変時間がかかります(自宅は超高速なんですが、オフィスの回線は遅い)。ダウンロード完了したらインストーラーが起動するのをキャンセルします。/Applicationに、「Install OS X Sierra.app」が得られます。わたしはこのファイル名だったけど時期によって違うときもあるみたい?

Sierraのisoファイルを作成する(15分)

Creating a bootable El Capitan ISO image - 0xcafebabe

この記事のとおり。記事はEl CapitanですがそのままSierraに読み代えれば全てOKです。一発目のhdiutil attachのときに引数をダブルクォーテーションで囲わないとエラーです。タブキー押しでファイル名補完をその後のcpではOKですが一発目のhdiutilだけはダメ。あと、二発目のhdiutil createがちょっと時間かかって不安になりましたが、待てば良い。結果として言えることは、El CaptianとSierraでインストーラの構造などまったく一緒なんですね。

$ hdiutil attach "/Applications/Install OS X Sierra.app/Contents/SharedSupport/InstallESD.dmg" -noverify -nobrowse -mountpoint /Volumes/esd
$ hdiutil create -o Sierra.cdr -size 7316m -layout SPUD -fs HFS+J
$ hdiutil attach Sierra.cdr.dmg -noverify -nobrowse -mountpoint /Volumes/iso
$ asr restore -source /Volumes/esd/BaseSystem.dmg -target /Volumes/iso -noprompt -noverify -erase
$ rm /Volumes/OS\ X\ Base\ System/System/Installation/Packages
$ cp -rp /Volumes/esd/Packages /Volumes/OS\ X\ Base\ System/System/Installation
$ cp -rp /Volumes/esd/BaseSystem.chunklist /Volumes/OS\ X\ Base\ System/
$ cp -rp /Volumes/esd/BaseSystem.dmg /Volumes/OS\ X\ Base\ System/
$ hdiutil detach /Volumes/esd
$ hdiutil detach /Volumes/OS\ X\ Base\ System
$ hdiutil convert Sierra.cdr.dmg -format UDTO -o Sierra.iso
$ mv Sierra.iso.cdr Sierra.iso 
$ rm Sierra.cdr.dmg

VirtualBoxをインストールする(10分)

https://www.virtualbox.org/ からMacインストーラをダウンロードしてきてインストール。これもダウンロード長かった。

VirtualBoxでゲストインスタンスを作る(20分)

Mac OS X(64-bit) ですべてデフォルトにて作成すればOK。先ほど作った「Sierra.iso」を指定する。コンソールに起動ログが流れるという、いままで見たことのないMacの立ち上がり方ののちにmacOSインストーラが立ち上がる。そのままではインストール先ディスクがないので、ディスクユーティリティを起動してメインのディスクを「消去」にて、Mac OS拡張(ジャーナリング)指定。ユーティリティを終了するとインストール先として選択できるようになっている。以下の記事に後半にスクショもあって詳しい。前半のisoイメージ作るところはこちらは手作業で終わってますので飛ばします。スクショで見るディスクユーティリティとSierraのディスクユーティリティは画面が結構違うのですが、ちょっと考えればすむ程度の違いです。

VirtualBoxにOS X Yosemiteをインストールする – OTTAN.XYZ

ゲストのSierraからホストのフォルダを見えるようにする(1分)

ゲストへホストから色々とブツを送り込むために、フォルダ共有をかける。以下の記事の説明どおりにSMB共有をかける。

How to Access Host's Shared Folder from MacOSX Guest

記事で「I think the only issue that you want to ask is "why the server address is 10.0.2.2?".」というように、10.0.2.2につなぐってのが驚いたけどたしかに問題なく接続完了。OK!

App Storeでシステムアップデート(40分)

ゲストのApp Storeでアップデート検索をする。iTunes 12.5.1他ありました。これまたダウンロードが長い。

ところでSierraって

いろいろ試してみたら、不具合なく動くものばかりでしたが、私は唯一!愛用のキーアサインメントを変更するツールである「Karabiner」が最新の10.21.0でもSierraは対応していなく動きませんでした。サイトを見るとまだ対応していないって書いてあります。

Karabiner - Software for OS X

macOS Sierraサポート状況 Karabinerは今のところmacOS Sierraでは動作しません。 Sierra対応は、まずは単純なキーの変更を行える機能をKarabiner-Elementsとして開発中です。 (設定画面を除くとSierra上で動作しています) Karabinerのフル機能のSierra対応はKarabiner-Elementsが完成してから対応します。

ということで、Karabiner-ElementsへGO。英語キーでの左右Commandキーの空押しに英数/かなを割り当てるという一点だけなのですが、これがないと死ねる。

Karabiner-Elementsの設定

GitHub - tekezo/Karabiner-Elements: The next generation Karabiner for macOS Sierra

.karabiner.d/configuration/karabiner.json の内容を書いてみた。キーの空押し対応はドキュメントになかったのでとりあえず左commandはコピー・ペースト・編集戻しと多用しますので割り当てず、左optionで代用。commandもoptionも二個あるからなんとでもなる。

{
    "profiles": [
        {
            "name": "Default profile",
            "selected": true,
            "simple_modifications": {
                "left_option": "japanese_eisuu",
                "right_command":"japanese_kana"
            }
        }
    ]
}

追記:⌘英かな

GitHub - iMasanari/cmd-eikana: 左右のコマンドキーを単体で押した時に英数/かなを切り替えるようにするアプリ。その他キーのリマップも可能。

commandキーの空押し問題で調べてたら、これをみつけました。これでいい!

SONY MDR-100ABN/Yを買った

長らくBeats Studio WirelessのTitaniumカラーを愛用してきましたが、急に激しい物欲が発作したのと、それを正当化する理由がいくつか出てきたので新しいヘッドホンを購入しました。選んだのはSONY MDR-100ABN/Yで、外れたら返品すればいいやと他の候補とは悩んでません。/Yは色型番でLime Yellowという色名前になります。日本での実勢価格が3万円ぐらいに下がってきていたみたいなところ、初見ではAmazonでもどこでも米国定価の$348で横並びでした。今月に日本出張の予定があるので日本で買おうかと思ってたら、翌日に偶然にも(本当に偶然。本当に驚いた)Amazonで新品$300で出てたのを見つけたので即ポチ。色は5色あるうちの2色だけが$300でしたが検討していたLime Yellowがあったので反射神経で押してました。もう一つの候補だった緑は安くなってなかったのでこちらも悩みがなくなった。プライム会員の無料2Dayお届けですぐに手元へ。Sales Taxもゼロなのでポッキリ$300。

参考:Beatsを買った時のこと

Beats by Dr. Dre を買うまで - まさたか日記

Beats Studio Wireless短評 - まさたか日記

開梱の儀

f:id:masataka_k:20160712111612j:plain

f:id:masataka_k:20160712111625j:plain

外箱ですが、インターナショナル版。英語と中国語と韓国語が併記されています。日本語はありません。

f:id:masataka_k:20160712111813j:plain

f:id:masataka_k:20160712112126j:plain

箱からケーブルまで統一されたLime Yellow色は、やや緑がかかった綺麗な中間色。色としては優しい色なんですが、黒とか銀とか白とかが多いヘッドホンとしてはかなり派手ですね。今月で44歳になる地味なおっさんですから、ヘッドホンぐらい派手にして気分盛り上げようと思います。

f:id:masataka_k:20160714114641j:plain

f:id:masataka_k:20160714114649j:plain

左ハウジングにはかぶって前方から、マイク・有線ケーブル接続・ミニUSB接続・電源ボタン・電源インジケータ・ノイズキャンセラ(NC)ボタン・NCインジケータと並びます。

f:id:masataka_k:20160714114701j:plain

右は前方からマイク・音量-・+、戻り・再生/停止/電話応答・送り。再生ボタンはスライドと押し込みの両方で使い分けるようになっています。左右両方にマイクがあるのは驚いた。Beatsは片方。

f:id:masataka_k:20160715035633j:plain

横からアーチ部に見えるエンボスSONYロゴは左右とも同じ箇所に刻まれています。頭頂部近くに「Wireless」と黒銀で印字されていますがこれは右だけ。手に持ってるのは愛用中のLAMY万年筆ですが、2015年限定色のネオンライムとお揃いになりました。

箱といい本体といい、まんまBeatsの雰囲気ですが、後発なのにパクリきれてない。SONYのデザイン力についてBeatsのそれよりは少々落ちると断じていいんじゃないかな。少々ですけど。

短評

www.youtube.com

このYouTube動画を見て欲しくなったのですが、この動画の冒頭で電車の騒音を消しているところの効果のほどはほとんど嘘じゃないです。このぐらい騒音消して静かにしてくれます。

使ってみての短評について、他に持ってないので比較対象がBeats Studio Wirelessしかありませんが以下の通りです。

  • Beatsよりややこちらの方が軽い。頭頂や耳周辺の圧迫感は同等ぐらい。
  • 音はこちらの方が高音も低音も断然いいと思います。Beatsは無線接続時の音が悪いように思うので普段は有線接続で使ってましたが、こちらは無線で十分以上に音質いいと満足してるのでずっと無線で使ってます。
  • ノイズキャンセラーはBeatsはもともと弱め機能で、本格的なこちらに当然の軍配あげ。無音時にうっすらとホワイトノイズが残りますがBeatsの比でなくとても静かです。エアコンや外の騒音など全てカット。しかし人の声だけは通ります。どうやら製品としてそういう調整がされているらしい。
  • 操作はBeatsがハウジング全体がボタンになってるというオシャレ設計のため、再生の送りや戻しの時にダブル・トリプルの押し込みが必要なのですが、こちらはスライドなので間違いなく便利。たくさんボタンがあるためオシャレ度は落ちますけど、あまり見えるところじゃないし関係ないかな?
  • バッテリー持ちは多分、こちらの方が長そう。届いてからまともに充電しないまま、使用三日目になります。毎日6時間以上ぐらいは使ってるんじゃないかな?朝からオフィスで作業しながら5時間音楽聴いてて、夕方になると日本とのビデオ会議で1時間以上。
  • バッテリー残量を調べる術がこちらにはない?説明書にも書いてない。Beatsはボタンを軽く押すと5つのLEDインジケータがグラデーション点灯してうまいこと残量表示してくれ、エフェクトが素敵だし便利だった。
  • 試しに充電のミニUSBケーブル繋いだら、電源落ちて使えなくなった。Beatsは充電しながら使えたのでこれはSONYの残念な点。
  • どちらも有線接続するとハウジングの操作ボタン類が効かなくなる。Beatsはその代りにケーブルにボタンが付いているけどSONYはボタンなし。ちょっとだけSONY残念な点。でも無線でしか使わないからどうでもいいかな。
  • 有線接続すると、Beatsは電源が自動で入ります。この時バッテリー残量ゼロだと音が聞こえない。でもこちらは電源OFFで有線接続しても音も聞こえるしマイクも使える!これは地味だけど大加点ポイントだなあ。有線接続では電源オンとオフどちらも可能で、オンにするとノイズキャンセラーを有効にすることができます。

f:id:masataka_k:20160714114523j:plain

さて、左右ハウジングの上部に穴(写真では黒く見えるところ)がアームに隠れて開いてます。ここから音が漏れる。ノイズキャンセラが強く効くので再生音量を小さくすればいいとクチコミサイトにはありますが、比較論としてBeatsの方が音漏れしない。まあ、私には日本で暮らしてた時のように通勤電車で聴くとかの人混みシチュエイションが全くないので問題ではないんですけど。

総合的に、Beatsより満足。買ってよかった。毎日ビデオ会議があるからマイク付きのヘッドホンは必需品なのです。そして毎日使うところに気に入ったものが手に入ったので、癒された。7/20米国発、7/21〜8/4に日本滞在ですが往復の飛行機での機内静音ぶりが今から楽しみ。

material-uiのSVGアイコンを作る

Googleはマテリアルデザインのガイドラインを提供するほかに、CSS+ JavaScriptのライブラリ(Material Design Lite)やフォント(Roboto/Notoなど)も用意し、アイコンもまたまとまった数のものがあります。

design.google.com

このアイコンは最も使われるだろうPNG形式、ついでアイコンフォント形式のほか、SVG形式も一緒に配布されています。取り扱いとして他より工夫がいるけれども拡大縮小に強い上でHTML文書に直接埋め込めるSVGは、私は最近理解して使い始めたばかりですが、アプリケーションも構成しやすくコードで直接コントロールもできるので重宝し始めました。

承前としてSVG

<html>
<body>
<svg>
    <path d="M11.5 17l4-8v-2h-6v2h4l-4 8h2z"/>
</svg>
</body>
</html>

まず承前として上記のHTML断片は数字の「7」をSVGで書いてます。描画はsvg>path@dで設定されているコマンド文字列でベクタ描画。これの読み方が知らないと訳わかりませんが順を追っていくと結構単純なことの組み合わせでした。まず"M11.5 17l4-8v-2h-6v2h4l-4 8h2z"はアルファベット+数字の組み合わせで、区切り文字はカンマか空白か符号というルールで分解します。以下は独自ですが関数呼び出しのフォーマットで書き直してみます。

  • M(11.5, 17)
  • l(4, -8)
  • v(-2)
  • h(-6)
  • v(2)
  • h(4)
  • l(-4, 8)
  • h(2)
  • z

分解するとあとはアルファベットが命令、数字が座標です。命令アルファベットが大文字だと続く数字は絶対座標で、小文字だと相対座標になります。Mは始点の指定コマンドのためまず(11.5, 17)から続くl(小文字のエル)コマンドで相対座標(+4, -8)へ直線を引きます。vは垂直線、hは水平線ですから、vで相対座標(0, -2)、hで相対座標(-6, 0)へ。。。以下続く。最後にzはパスを閉じるコマンドで、ここで始めにMで決めた絶対座標(11.5, 17)へ戻ってきてなければいけません。他、ベジェ曲線を書くこともできます。

Paths - SVG | MDN

material-ui

materilal-uiでは、このSVGを保持するReactコンポーネントがあり、Material Iconsをそのまま取り込んでくれています。

'use strict';
import React from 'react';
import {ToggleStar, ToggleStarHalf, ToggleStarBorder} from 'material-ui/lib/svg-icons';

const Stars = React.createClass({
    render() {
        return (
            <div>
                <div><ToggleStarBorder/></div>
                <div><ToggleStarHalf/></div>
                <div><ToggleStar/></div>
            <div>
        );
    }
});
export default Stars; 

f:id:masataka_k:20160228064049p:plain

こんな感じ。このToggleStarとかは見るとSvgIconコンポーネントにMaterial Iconからそのままもらってきたpathデータを流し込んで作ってます。

SVGアイコン自作

ということで、さっきの7をコンポネント化します。そもそも7を作り8も9も作った理由はMaterial IconでImage-Looksという角丸の四角に素朴な味わいの数字が入ったアイコンが1から6までしかなかったからでした(looks one 〜 looks 6まで)。

'use strict';

import React from 'react';
import {SvgIcon} from 'material-ui';
const backgroundRoundRect = 'M19 3c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2h-14c-1.1 0-2-.9-2-2v-14c0-1.1.9-2 2-2h14z';
const seven = 'M11.5 17l4-8v-2h-6v2h4l-4 8h2z';
function Looks7(props) {
    return (
        <SvgIcon {...props}>
            <path d={backgroundRoundRect + seven}/>
        </SvgIcon>
    );
}

const eight = 'M11 17h2c1.1 0 2-.89 2-2v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5v-1.5c0-1.11-.9-2-2-2h-2c-1.1 0-2 .89-2 2v1.5c0 .83.67 1.5 1.5 1.5-.83 0-1.5.67-1.5 1.5v1.5c0 1.11.9 2 2 2zM11 9h2v2h-2v-2zM11 13h2v2h-2v-2z';
function Looks8(props) {
    return (
        <SvgIcon {...props}>
            <path d={backgroundRoundRect + eight}/>
        </SvgIcon>
    );
}

const nine = 'M13 7h-2c-1.1 0-2 .89-2 2v2c0 1.11.9 2 2 2h2v2h-4v2h4c1.1 0 2-.89 2-2v-6c0-1.11-.9-2-2-2zm0 4h-2v-2h2v2z';
function Looks9(props) {
    return (
        <SvgIcon {...props}>
            <path d={backgroundRoundRect + nine}/>
        </SvgIcon>
    );
}

苦労した点は、Material Iconのデータが”汚い”コードだってことが一つ。多分自動生成した際のロジックが悪いのか、描画内で相対座標と絶対座標を雑に混在したものになっているために、既存アイコンのパスデータの一部を持ってきて新しいアイコンを作ろうとすると、すぐ座標系が壊れちゃう。そのためにパス要素毎にまず始めにMコマンドで絶対座標を決めてからそこからzで閉じるまでは相対パスで書き通すように変更して再利用しました。

苦労したもう一つは、7/8/9のアイコンを作るにあたって、背景の ”backgroundRoundRect” と "seven"/'eight"/"nine" をつなげて数字がくり抜きできなかったこと。苦労の結果、今は白抜きできています。こちらの原因はパスの書き方で、同じ図形でも時計回りにパスを辿っても反時計周りに辿っても見かけは一緒なのですが、同じ巻きを重ねてもくり抜かれないというルールがありました。よって数字は反時計まわりだったので背景は時計まわりにしなければならない。超はまった。

しかし、わかると最強。これは便利だ。

JsDOM+React TestUtilsの使い方を改良

TestUtilsにはいろいろ便利なメソッドがあるのを見つけてテストの書き方を変えたら、UserAgentが設定されていないということでエラーが出るようになってしまいました。かといって直接にnavigator.userAgentを設定しようにもGetterしかないと弾かれてしまいます。調べるうちにJsDOMの使い方を変えるとOKなことを発見。

gulp.task('test', () => {
    jsdom.env({
        html: '',
        userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ' +
                           '(KHTML, like Gecko) Chrome/49.0.2454.85 Safari/537.36',
        done: (err, window) => {
            if(err) throw err;
            for (let key in window) {
                if (window.hasOwnProperty(key) && !global[key]) {
                    global[key] = window[key];
                }
            }
            console.debug = console.log; // console.debugがundefinedだった
            gulp.src('src/**/__tests__/*Test.js')
                .pipe(jasmine({reporter: new reporter()}));
        }
    });
});

gulpfile.jsの中ではJsDOMのenv()を使うようにしました。結局はglobalにプロパティを書き出しているのでテスト環境が隔離されているのではないのは変わりませんが、useAgentは設定できます。この環境ではconsole.debugがundefinedだったので足してあげたりもしています。値は、inline-style-prefixer/prefixer-test.js at master · rofrischmann/inline-style-prefixer · GitHub から持ってきました。そもそもこのライブラリがエラー出してたのです。

describe('TestTable', () => {
    it('表示', () => {
        const root = TestUtils.renderIntoDocument(<TestTable value={data}/>);
        const trs = TestUtils.scryRenderedDOMComponentsWithTag(root, 'tr');
        const node = ReactDOM.findDOMNode(trs[0]).childNodes;
        expect(node[1].textContent).toBe('test-test-test');
    });
});

scryRenderedDOMComponentsWithTag / scryRenderedDOMComponentsWithClass / scryRenderedDOMComponentsWithType がそれぞれ何かと使える。