Siestaでユーザー入力をシミュレート

Siestaに関する第2弾です。Siestaでユーザー入力のシミュレートをやってみます。

![](http://extjs.sunvisor.net/wp-content/uploads/2012/11/siesta.png)

## ユーザー操作のシミュレート

Siestaでは、ユーザーの操作をシミュレートできます。
Siesta.Test.Simulateネームスペースのmixinたちのメソッドや、
Siesta.Test.Actionクラスがそれをやるみたいですね。

アクションは、実行後にコールバックを呼ぶという非同期の呼び出しになります。

### t.chain メソッド

ちょっとそれますが、
非同期のテストを順番に実行する時には、コールバックのネストが深くなってしまいます。
そんな時にはt.chainで、非同期のものも順番にテストを実行させることができます。

    t.chain(
        function (next) {
            t.type(userNameField, 'username', next)
        },
        function (next) {
            t.type(passwordField, 'secret', next)
        },
        function (next) {
            t.click(loginButton, next)
        },
        function () {
            // done
        }
    });

SiestaのAPI Docのサンプルには、文末の`;`が抜けているのが多くてキモいです、
このサンプルではつけてます。

関数をいっぱい渡すわけですね。
このtypeなどの関数は、Siesta.TestのAPI見ても出てきません。
typeだと、Siesta.Test.Simulate.Keyboardというmixinのメソッドです。
これらSimulateのメソッドで、ユーザー入力のシミュレートをします。
関数に渡るのはコールバック関数で、
それが次の関数ということになるのですね。
関数の中では、非同期呼び出しをする時のコールバックに渡された引数nextを渡してやるということです。

関数じゃなくてオブジェクトリテラルで指定することも可能です。

    t.chain({
        action  : 'type',
        target  : userNameField, 
        text    : 'username'
    },{
        action  : 'type',
        target  : passwordField, 
        text    : 'secret'
    },{
        action  : 'click',
        target  : loginButton 
    });

関数とオブジェクト両方混ぜて使っても大丈夫です。

### Actionの指定

このオブジェクトの指定の仕方について。

+ **`action`**:  
  実行するアクションの名前、`type`とか`click`とかがあります。
  Siesta.Test.Actionクラスのサブクラス名の先頭を小文字にしたものを指定するみたいです。
+ **`target`**:  
  アクションの対象となるエレメントを指定。後述。
+ **`text`**:
  `type`アクションの場合のオプションで、入力するテキスト。
  アクション毎に指定できるオプションがあります。
  Siesta.Test.ActionのサブクラスのAPIドキュメントを参照してください。

### target に設定できるもの

Siestaでは、いろいろな方法で、アクションの対象となるエレメントを指定できるようになっています。

まず、Siesta.Test.Browserで使えるもの

+ HTMLエレメント
+ `[ x, y ]`形式の配列で座標を指定。
+ DOMクエリーを表すCSSセレクター。

Ext JSやSencha Touchでは次のものも使えます。

+ Ext.dom.Element のインスタンス
+ Ext.Componentのインスタンス。
+ コンポジットクエリー文字列。
+ コンポーネントクエリー文字列。先頭に `'>>'` をつけること。
  
コンポジットクエリーというのはSiestaの用語で、コンポーネントクエリーの後に `'=>'` で繋げて、
セレクターを書いてエレメントを指定する構文です。

文字列で指定するものが3つありますが、文字列が `'>>'` で始まっていれば、コンポーネントクエリー、
文字列中に`'=>'`があれば、コンポジットクエリー、それ以外はDOMクエリーということになります。

## シミュレートしてみる

ちょっと長いですけど、フォームの入力をシミュレートするテストを書いてみました。
この元になるフォームには、テキストフィールドやコンボボックスなどがあり、保存ボタンがあります。
保存ボタンには、イベントリスナーが設定済みで、そのリスナーの中で `saveCommand` という独自イベントが発火する仕組みになっています。

    StartTest(function(t) {
        var nameValue = '鬼瓦　権三',
            emailValue = 'gonzo@onigawara.com',
            ageValue = '40';
            
        t.requireOk(
            [
                'EX.model.Employee', 
                'EX.model.Department', 
                'EX.store.Departments', 
                'EX.view.Form'
            ], 
            function() {
                var rec, form,
                    nameFld, deptFld, emailFld, genderFld, ageFld, 
                    saveButton, async = t.beginAsync();

                rec = Ext.create('EX.model.Employee');
                Ext.create('EX.store.Departments', {
                    storeId: 'Departments',
                    addMode: false
                });

                form = Ext.create('EX.view.Form', {
                    width: 500,
                    renderTo: Ext.getBody()
                });

                nameFld = form.down('textfield[name=name]');
                deptFld = form.down('combobox[name=department_id]');
                emailFld = form.down('textfield[name=email]');
                genderFld = form.down('combobox[name=gender]');
                ageFld = form.down('numberfield[name=age]');

                saveButton = form.down('button[text=保存]');

                // イベントリスナー設定
                form.on('saveCommand', function(form, rec, mode) {
                    var values = form.getValues();

                    t.diag('保存ボタンがクリックされた');
                    t.is(values.name, nameValue, 'nameフィールドの一致');
                    t.is(values.email, emailValue, 'emailフィールドの一致');
                    t.is(values.department_id, '3', 'department_idフィールドの一致');
                    t.is(values.gender, '男', 'genderフィールドの一致');
                    t.is(values.age, ageValue, 'ageフィールドの一致');
                    t.endAsync(async);
                });
                form.loadRecord(rec);
                // イベントをリッスンしてボタンをクリック
                t.chain({
                    // テキストフィールドに入力
                    action: 'type',
                    target: nameFld,
                    text: nameValue
                }, {
                    // コンボのリストを開く
                    action      : 'click',
                    target      : deptFld.el.query('.x-trigger-cell')[0]
                }, 
                function (next) {
                    // コンボのアイテムを選択
                    t.click(deptFld.getPicker().getNode(2), next);
                }, {
                    // テキストフィールドに入力
                    action: 'type',
                    target: emailFld,
                    text: emailValue
                }, {
                    // コンボのリストを開く
                    action      : 'click',
                    target      : genderFld.el.query('.x-trigger-cell')[0]
                },
                function (next) {
                    // コンボのアイテムを選択
                    t.click(genderFld.getPicker().getNode(0), next);
                }, {
                    // 数値フィールドに入力
                    action: 'type',
                    target: ageFld,
                    text: ageValue
                }, {
                    // 保存ボタンをクリック
                    action: 'click',
                    target: saveButton
                });
            }
        );
    });

### type

typeアクションで、テキストフィールドに値を入力しています。
targetの指定は、本来であればコンポーネントの入力エレメントを取り出して渡さなければならないところですが、
Siesta.Test.ExtJSクラスを利用している場合は、コンポーネントを渡しちゃってもいいそうです。
ですので、ここでは、

    nameFld = form.down('textfield[name=name]');

として取り出したコンポーネントを渡しています。

### コンボボックスのアイテム選択

これは、サンプルの中から取りだしたテクニックです。

    {
        // コンボのリストを開く
        action      : 'click',
        target      : deptFld.el.query('.x-trigger-cell')[0]
    }, 
    function (next) {
        // コンボのアイテムを選択
        t.click(deptFld.getPicker().getNode(2), next);
    },

2つのアクションで実行しています。

+ コンボボックスのトリガーボタンのクリック
+ プルダウンしたアイテムのクリック
  
です。これで任意のプルダウンの選択という動作をシミュレートできます。

### 保存ボタンのクリック

t.chainで指定した一連の操作の最後は、保存ボタンをクリックです。
このテストでは、 saveCommandイベントのリスナーを設定しています。
そして、endSyncを使って、このリスナーが実行されるまで、テストを有効にしています。
リスナーに渡された内容を見て、値の比較のアサーションを定義しています。

このテストを実行すると、次の動画のような動きをします。
ちょっとわかりにくいかも知れませんが、画面上部のテスト実行ボタンをクリックするまでの動きは、僕が行った操作ですが、その後の動きは、Siestaによるシミュレーションです。

[YouTubeの動画](http://www.youtube.com/embed/i1WEect0XQw)