0.11 でモジュール化とユニットテスト

jspec が優秀なスペックテストをできるので、ヘルパーを用意。 require と log 関数だけはよく使うので用意してあげます。require は配布時とテスト時の違いを吸収できるような変な書き方をしているけれど。Rhino だと load でJSを読み込めますが、require と互換性を保つために、readFile をして eval します。

// spec/couch-server-main.js
sandbox = this;
function require(name, parent){
   // Application Context:
   //    root should be vendor/crayon/
   //    name is a key without an extension.
   //
   // Test Context:
   //    root should be .
   //    name is a filename with ".js" extension
   if( name.lastIndexOf("vendor/crayon/") == 0 ){
      name = name.substr("vendor/crayon/".length);
   }
   name = name + ".js";
   print("loading " + name);
   var exports = {};
   var source = readFile(name);
   var s = "function(exports, require){" + source + "}";
   var func = eval(s);
   func.apply(sandbox, [exports, function(name){
      return require(name, parent, source);
   }]);
   return exports;
}

function log(msg){
   print(msg);
}


こんな感じで、モジュール関数を用意します。

// lib/escape.js
/**
 * Escapes html special charactors.
 */
function html_escape(s){
    return s.toString().replace(/&/g, "&")
      .replace(/\"/g, """)
      .replace(/\'/g, "'")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
};
exports.html_escape = html_escape;
exports.h = html_escape;
/**
 * Escapes json special charactors.
 */
function json_escape(s){
   return s.toString().replace(/&/g, "\u0026")
      .replace(/</g, "\u003C")
      .replace(/>/g, "\u003E");
}
exports.json_excape = json_escape;
exports.j = json_escape;

で最後にスペックテストを記述します。before_each で require してモジュールを使えるようにします。あとはRSpecっぽいJSpecの記法にしたがってかくだけです。

// spec/spec.escape.js
JSpec.describe("escape",  function(){
   before_each(function(){
      m = require('lib/escape');
   });

   describe("html_escape", function(){
      it("should escape '\"'", function(){
         var escaped = m.h('a="a"');
         expect(escaped).should(be, 'a=&quot;a&quot;');
      });

      it("should escape \"'\" ", function(){
         var escaped = m.h('a=\'a\'');
         expect(escaped).should(be, 'a=&#039;a&#039;');
      });

      it("should escape '<'", function(){
         var escaped = m.h('a<a<');
         expect(escaped).should(be, 'a&lt;a&lt;');
      });

      it("should escape '>'", function(){
         var escaped = m.h('a>a>');
         expect(escaped).should(be, 'a&gt;a&gt;');
      });
   });

   describe("json_escape", function(){
      it("should escape '>'", function(){
         var escaped = m.j('a>a>');
         expect(escaped).should(be, 'a\u003Ea\u003E');
      });

      it("should escape '<'", function(){
         var escaped = m.j('a<a<');
         expect(escaped).should(be, 'a\u003Ca\u003C');
      });

      it("should escape '&", function(){
         var escaped = m.j('a&a&');
         expect(escaped).should(be, 'a\u0026a\u0026');
      });
   });
});

んで、最後に、spec runner 用のファイルを用意してあげて、

load("spec/jspec.js");
load("spec/couchdb-server-main.js");

JSpec
  .exec('spec/spec.crayon.js')
  .exec('spec/spec.escape.js')
  .exec('spec/spec.template.js')
  .run({ formatter: JSpec.formatters.Terminal })
  .report()

で、実行。

imac:crayon yssk22$ jspec run --rhino

 Passes: 15 Failures: 0

 Crayon extractOptions
  should return empty args and null option on no arguments..
  should return null option on no options..
  should return object on some options..

 escape html_escape
  should escape '"'.
  should escape "'" .
  should escape '<'.
  should escape '>'.

 escape json_escape
  should escape '>'.
  should escape '<'.
  should escape '&.

 template render
  should returns string without bindings.
  should returns string with bindings.

これで安心してアプリケーションがかけますね。