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短評 - まさたか日記
開梱の儀
外箱ですが、インターナショナル版。英語と中国語と韓国語が併記されています。日本語はありません。
箱からケーブルまで統一されたLime Yellow色は、やや緑がかかった綺麗な中間色。色としては優しい色なんですが、黒とか銀とか白とかが多いヘッドホンとしてはかなり派手ですね。今月で44歳になる地味なおっさんですから、ヘッドホンぐらい派手にして気分盛り上げようと思います。
左ハウジングにはかぶって前方から、マイク・有線ケーブル接続・ミニUSB接続・電源ボタン・電源インジケータ・ノイズキャンセラ(NC)ボタン・NCインジケータと並びます。
右は前方からマイク・音量-・+、戻り・再生/停止/電話応答・送り。再生ボタンはスライドと押し込みの両方で使い分けるようになっています。左右両方にマイクがあるのは驚いた。Beatsは片方。
横からアーチ部に見えるエンボスSONYロゴは左右とも同じ箇所に刻まれています。頭頂部近くに「Wireless」と黒銀で印字されていますがこれは右だけ。手に持ってるのは愛用中のLAMY万年筆ですが、2015年限定色のネオンライムとお揃いになりました。
箱といい本体といい、まんまBeatsの雰囲気ですが、後発なのにパクリきれてない。SONYのデザイン力についてBeatsのそれよりは少々落ちると断じていいんじゃないかな。少々ですけど。
短評
このYouTube動画を見て欲しくなったのですが、この動画の冒頭で電車の騒音を消しているところの効果のほどはほとんど嘘じゃないです。このぐらい騒音消して静かにしてくれます。
使ってみての短評について、他に持ってないので比較対象がBeats Studio Wirelessしかありませんが以下の通りです。
- Beatsよりややこちらの方が軽い。頭頂や耳周辺の圧迫感は同等ぐらい。
- 音はこちらの方が高音も低音も断然いいと思います。Beatsは無線接続時の音が悪いように思うので普段は有線接続で使ってましたが、こちらは無線で十分以上に音質いいと満足してるのでずっと無線で使ってます。
- ノイズキャンセラーはBeatsはもともと弱め機能で、本格的なこちらに当然の軍配あげ。無音時にうっすらとホワイトノイズが残りますがBeatsの比でなくとても静かです。エアコンや外の騒音など全てカット。しかし人の声だけは通ります。どうやら製品としてそういう調整がされているらしい。
- 操作はBeatsがハウジング全体がボタンになってるというオシャレ設計のため、再生の送りや戻しの時にダブル・トリプルの押し込みが必要なのですが、こちらはスライドなので間違いなく便利。たくさんボタンがあるためオシャレ度は落ちますけど、あまり見えるところじゃないし関係ないかな?
- バッテリー持ちは多分、こちらの方が長そう。届いてからまともに充電しないまま、使用三日目になります。毎日6時間以上ぐらいは使ってるんじゃないかな?朝からオフィスで作業しながら5時間音楽聴いてて、夕方になると日本とのビデオ会議で1時間以上。
- バッテリー残量を調べる術がこちらにはない?説明書にも書いてない。Beatsはボタンを軽く押すと5つのLEDインジケータがグラデーション点灯してうまいこと残量表示してくれ、エフェクトが素敵だし便利だった。
- 試しに充電のミニUSBケーブル繋いだら、電源落ちて使えなくなった。Beatsは充電しながら使えたのでこれはSONYの残念な点。
- どちらも有線接続するとハウジングの操作ボタン類が効かなくなる。Beatsはその代りにケーブルにボタンが付いているけどSONYはボタンなし。ちょっとだけSONY残念な点。でも無線でしか使わないからどうでもいいかな。
- 有線接続すると、Beatsは電源が自動で入ります。この時バッテリー残量ゼロだと音が聞こえない。でもこちらは電源OFFで有線接続しても音も聞こえるしマイクも使える!これは地味だけど大加点ポイントだなあ。有線接続では電源オンとオフどちらも可能で、オンにするとノイズキャンセラーを有効にすることができます。
さて、左右ハウジングの上部に穴(写真では黒く見えるところ)がアームに隠れて開いてます。ここから音が漏れる。ノイズキャンセラが強く効くので再生音量を小さくすればいいとクチコミサイトにはありますが、比較論としてBeatsの方が音漏れしない。まあ、私には日本で暮らしてた時のように通勤電車で聴くとかの人混みシチュエイションが全くないので問題ではないんですけど。
総合的に、Beatsより満足。買ってよかった。毎日ビデオ会議があるからマイク付きのヘッドホンは必需品なのです。そして毎日使うところに気に入ったものが手に入ったので、癒された。7/20米国発、7/21〜8/4に日本滞在ですが往復の飛行機での機内静音ぶりが今から楽しみ。
material-uiのSVGアイコンを作る
Googleはマテリアルデザインのガイドラインを提供するほかに、CSS+ JavaScriptのライブラリ(Material Design Lite)やフォント(Roboto/Notoなど)も用意し、アイコンもまたまとまった数のものがあります。
このアイコンは最も使われるだろう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)へ戻ってきてなければいけません。他、ベジェ曲線を書くこともできます。
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;
こんな感じ。この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 がそれぞれ何かと使える。
素のnodeでES6の多くが動く
ECMAScript 2015 (ES6) | Node.js
今、BabelでES6とJSXをトランスパイルしてますが、そのBabelを起動するGulp(gulpfile.js)についてはES5で書いてました。しかしすでにnode v5.xではV8エンジンの対応状況が進んできているためにES6の仕様の多くが動いちゃうんですね。
$ node --v8-options | grep "in progress" --harmony_modules (enable "harmony modules" (in progress)) --harmony_regexps (enable "harmony regular expression extensions" (in progress)) --harmony_proxies (enable "harmony proxies" (in progress)) --harmony_sloppy_function (enable "harmony sloppy function block scoping" (in progress)) --harmony_sloppy_let (enable "harmony let in sloppy mode" (in progress)) --harmony_unicode_regexps (enable "harmony unicode regexps" (in progress)) --harmony_reflect (enable "harmony Reflect API" (in progress)) --harmony_destructuring (enable "harmony destructuring" (in progress))
ただ、modulesがまだダメだった。import/exportはまだin progress。でもlet/constやArrow Functionはレディなのでモジュールシステムだけrequireにしておけば他は大体ES6に書けました。ここでは出てこないけどES6-classもすでに使えるわけね。
'use strict'; const gulp = require('gulp'); const jasmine = require('gulp-jasmine'); const reporter = require('jasmine-terminal-reporter'); const jsdom = require('jsdom').jsdom; require('babel-register'); gulp.task('test', () => { global.document = jsdom(''); global.window = global.document.defaultView; for (let key in global.window) { if (global.window.hasOwnProperty(key) && !global[key]) { global[key] = global.window[key]; } } gulp.src('src/**/__tests__/*.js') .pipe(jasmine({reporter: new reporter()})); });
そもそもObject.assign()とかES6とは知らずに使ってた。
Reactコンポーネント内からkeyが取れない仕様だった
すでに0.12での仕様変更でしたから、私がReactに触れてからはずっとそうなってたのですが、自分で書いたコードの謎バグに悩まされて初めて知りました。this.props.ref及びthis.props.keyはコンポーネント内から参照できない。。。常にundefined。
gulp-jasmineでES6で書いたReactコンポーネントとReactステートレスコンポネントさらにはExpressルーターを同時にテストできるようにする
一通りハマって模索した結果、それぞれしっかり原因判明やりきって解決するまでの質ではないのですが、ES6 で書いたReactコンポーネントとステートレスコンポーネントをテストするとともに、ES6で書いたExpressルーターもまとめてテストできるプロジェクト設定・構成が一つ用意できました。
まず前提。
- ES6で書いて、Babelでトランスパイルしている
- jestで作ったテスト環境では、SuperTestを用いたExpressルーターテストがどうにも動かなかった
- jest.autoMockOff()をかけても、あちらこちらでモックになっちまう。
- 先にBabelトランスパイルしてから、出力ファイルでjest実行してみてもなぜか改善せず
- jestは難しいことを簡単にできて素敵に思っていたけど、意外に簡単と思ってたところでエラーが出始め。解決がどうにも難しくてjestにギブアップしてしまった
- Reactステートレスコンポーネントはテストが動かない。
- 普通のReactコンポーネントはテスト可能
- 後で判明するけどこれはjestのせいじゃなかった
ぐるぐる回ってみて、ちょっとjestでは層が深くて追いきれなくなってきたのでJasmine2でくみなおしました。もちろんいきなり結論には辿りつけずにいろいろ入れたり外したりしましたよ。。。以下は、シンプル化したpackage.jsonです。Reactテストには、gulp-jasmineとreact-addons-test-utilsとjsdomが関わります。Expressテストの方ではsupertest。ビルド及びテストの前処理は、gulp-babelとbabel-preset-*。
{ "name": "sample", "version": "0.0.1", "private": true, "dependencies": { "express": "^4.13.4", "react": "^0.14.7", "react-dom": "^0.14.7" }, "devDependencies": { "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-jasmine": "^2.2.1", "jsdom": "^8.0.2", "react-addons-test-utils": "^0.14.7", "supertest": "^1.2.0" }, "babel": { "presets": [ "es2015", "react" ] }, "scripts": { "build": "gulp build", "start": "cd dest; node app.js", "test": "gulp test" } }
gulpでトランスパイルもしくはテスト実行という流れで、今後、もうチョイと便利に改良していく予定。gulpfile.jsだけはES5で書いてます。
// gulpfile.js 'use strict'; var gulp = require('gulp'); var babel = require('gulp-babel'); var jasmine = require('gulp-jasmine'); var reporter = require('jasmine-terminal-reporter'); var jsdom = require('jsdom').jsdom; require('babel-register'); // Jasmineで事前トランスパイルなしに動かすために必要 gulp.task('build', function() { gulp.src(['src/**/*.js', '!src/**/__tests__/*.js']) //テストは出力しない .pipe(babel()) .pipe(gulp.dest('dest')); }); gulp.task('test', function () { global.document = jsdom(''); // 空白文字列でOK。好きなHTMLを書いても支障なし。 global.window = global.document.defaultView; // defaultViewにするのは最近の仕様変更。 for (var key in global.window) { if (global.window.hasOwnProperty(key) && !global[key]) { global[key] = global.window[key]; //念のため上書きなしで参照をコピーしておく } } gulp.src('src/**/__tests__/*.js') //テストの配置方法は直前まで試していたjestの名残りです。 .pipe(jasmine({reporter: new reporter()})); });
トランスパイルの方はいいとして、テストの方について。Reactのテストはやり方によってはDOMが要らないやり方もあるみたいですが、jestを通じて知ったTestUtilsのrenderIntoDocumentを使う直感的なビヘイビアテストをするために、JsDOMでヘッドレスなDOM環境を作ります。その際、テストのルートたるgulpタスクで作っちゃった。コード中で「typeof window === 'undefined'」とサーバ環境判定していたりするとダメですが、これだけは自分で書かないと決めりゃいいかな。jestはざっくりとその辺はうまくやってるっぽい(jest/JSDOMEnvironment.js at master · facebook/jest · GitHub と jest/NodeEnvironment.js at master · facebook/jest · GitHub)。
ちなみに、React Test Utilsのドキュメントの該当箇所「NOTE」を読んでも全くわからなかったため相当費やしましたよ。。。解決のためにReactだけでなくjestやJsDOMの見当違いなソース箇所ばかり読んでた(Test Utilities | React)。
node環境のグローバルスコープの名前であるglobalのプロパティとしてdocumentを作り、windowを付け足します。その後にfor...inループではwindowからglobalへ上書きしないようにしながら参照をコピーしました。本当はnavigatorだけ手当てすれば良いみたいですけど念のためです。次いでgulp-jasmineでsrcフォルダのテストを一切実行します。jestの名残でテストのフォルダは各所の__tests__フォルダとなってます。
buildタスク、testタスクともにBabelのプリセット設定はpackage.jsonを使ってくれてますのでES6とJSXどちらも対応しています。
'use strict'; import React from 'react'; class CheckboxWithLabel extends React.Component { constructor(props) { super(props); this.state = {isChecked: false}; } handleChange() { this.setState({isChecked: !this.state.isChecked}); } render() { const sender = this; return ( <label> <input type="checkbox" checked={this.state.isChecked} onChange={() => sender.handleChange()} /> {this.state.isChecked ? this.props.labelOn : this.props.labelOff} </label> ); } } CheckboxWithLabel.propTypes = { labelOn: React.PropTypes.string.isRequired, labelOff: React.PropTypes.string.isRequired }; export default CheckboxWithLabel;
上記のテスト対象はjestでのチュートリアルのものです。ESLintに怒られたのでちょっと整えました。
'use strict'; import React from 'react'; import ReactDOM from 'react-dom'; import TestUtils from 'react-addons-test-utils'; import Express from 'express'; import SuperTest from 'supertest'; import CheckboxWithLabel from '../CheckboxWithLabel'; import {ResponsiveCol} from '../BootstrapResponsive'; import WorkableRouter from '../WorkableRouter'; describe('React Components and Express' , () => { it('changes the text after click', () => { var checkbox = TestUtils.renderIntoDocument( <CheckboxWithLabel labelOn="On" labelOff="Off" /> ); var checkboxNode = ReactDOM.findDOMNode(checkbox); expect(checkboxNode.textContent).toEqual('Off'); TestUtils.Simulate.change( TestUtils.findRenderedDOMComponentWithTag(checkbox, 'input') ); expect(checkboxNode.textContent).toEqual('On'); }); // 下はReactステートレスコンポーネントのために動かない。 it('hidden setting', () => { const col = TestUtils.renderIntoDocument( <ResponsiveCol xs={{hidden: true}} sm={{hidden: true}} md={{hidden: false}}> <div></div> </ResponsiveCol> ); const colNode = ReactDOM.findDOMNode(col); expect(colNode.getAttribute('class').trim()).toEqual('hidden-xs hidden-sm'); }); it('get jobs', (done) => { const app = Express(); app.use(WorkableRouter); SuperTest(app) .get('/jobs') .expect(200, done); }); });
もちろんテストもjestから。これと2/10の記事に書いたReactステートレスコンポーネントのテストも並べておきます。さらにSuperTestでルータの非同期テストも。
F.. events.js:154 throw er; // Unhandled 'error' event ^ Failures: Error: Tests failed 1) React Components and Express hidden setting 1.1) TypeError: Cannot read property 'getAttribute' of null 3 specs, 1 failure Finished in 0.4 seconds
結果は、ES6-classのReactコンポーネントはOK、SuperTestはOK。でもReactステートレスコンポーネントはNG。TestUtils.renderIntoDocumentでnullを返してます。しばしトランスパイルされた結果を眺めていて気がつきました。そこで下記の工夫。
it('hidden setting', () => { const Component = ResponsiveCol({xs: {hidden: true}, sm: {hidden: true}, md: {hidden: false}, children: ''}); const col = TestUtils.renderIntoDocument(Component); const colNode = ReactDOM.findDOMNode(col); expect(colNode.getAttribute('class').trim()).toEqual('hidden-xs hidden-sm'); });
3種とも通りました!ステートレスコンポーネントはこう書けばいいのね。ステートレスコンポーネントは引数にプロパティを渡す単なる関数で、戻りはReact ElementになるのでそのままrenderIntoDocumentに渡してあげればよかった!まあrenderIntoDocumentの実装が追いついていないってことで確定だと思います。
... 3 specs, 0 failures Finished in 0.3 seconds
そろそろgulpでちゃんとしたワークフローを組むべきかな。gulpのgithubにレシピ集があった( gulp/docs/recipes at master · gulpjs/gulp · GitHub)。いろいろ参考になる。
react-router v2リリース
しばらくrc版を用いて開発していましたが、react-router v2が昨日か一昨日ぐらいにリリースされていました。ドキュメントも新しくv2になってます。
react-router/ComponentLifecycle.md at latest · rackt/react-router · GitHub
いつからあったかは定かではないですが、コンポーネントライフサイクルに関するドキュメントが良い情報。これはv2に限らず以前のバージョンでも同じ。パス遷移した時に各ページを構成するコンポーネントのどんなライフサイクルイベントが発火されるかの説明です。説明されるまでもなく慣れると体験的に知ってることなのですが、初めにこのドキュメントを読んでたら近道だったように思います。
react-router/testing.md at latest · rackt/react-router · GitHub
テストも。Jestのユースケースとして初見で参考になる。