AngularJSのカステムディレクティブで別のディレクティブをrequireしている場合のテストの書き方

前の記事のでいえばchildItemディレクティブのテストの書き方

コード再掲

#parentChild.coffee


"use strict"

#parent
#controller

angular.module("myApp")
.controller("ParentItemCtrl", ($scope, Child) ->
  #.......
  @removeChildItem = (childId, index)->
    Child.destroy({childId: childId}, ->
      $scope.parent.cildren.splice(index, 1)
    )
)

#parent directive

angular.module("myApp")
.directive("parentItem", ->
  restrict: "EA"
  templateUrl: "templates/parent_item.html"
  controller: "ParentItemCtrl"
  scope:
    parent: "=item"
)

#child directive

angular.module("myApp")
.directive("childItem", ($modal)->
  restrict: "EA"
  templateUrl: "templates/child_item.html"
  require: "^parentItem"
  scope:
    child: "=item"
  link: (scope, element, attrs, parentItemCtrl) ->
    scope.open = ()->
      modalInstance = $modal.open({
        templateUrl: "/remplates/modal_confirm.html"
      })

      modalInstance.result.then(->
        parentItemCtrl.removeChildItem(scope.child.id, scope.$index) 
      )
)

こんな感じでテストを書いた

#child_item_spec.coffee
"use strict"

describe "Directive: childItem", ->

  beforeEach module "myApp", "test.templates"

  scope = {}

  beforeEach inject ($controller, $rootScope) ->
    scope = $rootScope.$new()

  it "テンプレートに所定のデータが表示されること", inject ($compile) ->
    scope.child = {
      "id": 406
      "content": "child4"
      "created_at": "2014-09-23T11:16:23.609+09:00"
      "user":
        "id": 5018
        "profile":
          "name": "あい うえお"
    }
    element = angular.element '''<child-item item="child"></child-item>'''
    parentItemController = {
      removeChildItem: -> return
    }
    spyOn(parentItemController, "removeChildItem").andCallThrough()
    element.data("$parentItemController", parentItemController)
    element = $compile(element)(scope)
    childItemScope = element.find("child-item").scope()
    scope.$digest()
    expect(element.find(".child-user-name").text()).toEqual "あい うえお"

ChromeのDeveloper Toolsで確認するとわかるが、'$' + ディレクティブの名前 + 'Controller'という名前でcontrollerを取得しているので、それにあわせてmockを用意してあげればOK.
ちなみにディレクティブのhtmlは$templateCacheを使っていて、beforeEach module "myApp", "test.templates"の部分で、テスト用にテンプレートをJSにまとめたものを読み込んでいる。こちらについては別のエントリに書く。→書いた

参考

AngularJSで$templateCacheを使っている場合のテスト環境

ちなみにgenerator-angularで作成したプロジェクト。

developmentやbuildではgrunt-angular-templatesを使っているが、テストには、karma-ng-html2js-preprocessorを使った。

インストール

% npm install karma-ng-html2js-preprocessor --save-dev

karma.confに設定を追加

#karma.conf.coffee

    files: [
      # ...省略...
      "app/templates/*.html"  #<- テンプレートファイルを追加
      "test/mock/**/*.coffee"
      "test/spec/**/*.coffee"
    ]

    plugins: [
      "karma-phantomjs-launcher"
      "karma-jasmine"
      "karma-coffee-preprocessor"
      "karma-spec-reporter"
      "karma-coverage"
      "karma-ng-html2js-preprocessor"  #<- 追加
    ]

    preprocessors:
      "app/templates/*.html": ["ng-html2js"] # <- 追加
      "app/scripts/*.coffee": "coffee"
      "app/scripts/*/**/*.coffee": "coverage"
      "test/**/*.coffee": "coffee"

  # 追加
    ngHtml2JsPreprocessor:
      stripPrefix: "app/"
      moduleName: "test.templates" #<- 本番とは別に任意に設定した

あとはtemplateCacheのテンプレートを使っている部分のテストで beforeEach module "myApp", "test.templates"とか読みこめばOK.

参考

AngularJSで$templateCacheを使ってまとめたテンプレートJSの中のCSSファイルや画像ファイルのリンクにhashをつける

grunt-filerevを使って、CSSファイルや画像ファイルにhashがbuild時につくようにしている場合に、grunt-angular-templatesを使う場合はどうするか

generator-angularで作ったAngularJSプロジェクトのbuildタスクではgrunt-useminの処理中、grunt-filerevの処理が入って、CSSファイルや画像ファイル、JSファイルにhashがつく。 要するにRailsのAssett Pipelineで処理したときによくみるあれと同様。

こんなやつ

index.html

<link rel="stylesheet" href="styles/main.b1c3621e.css">
<script src="scripts/scripts.a241652b.js">

grunt-angular-templatesを入れて何も考えずに素直にタスクに組み入れると、テンプレートキャッシュの中に画像などへの参照があった場合に、当然、このhashを付与する流れに入っていないので、hashが付与されないリンクのままでbuildされ、リンク切れという事になってしまう。

↓useminでのgrunt-filerevを考慮せずタスクに追加

#Gruntfile.coffee
 
#...省略...
 # 追加
 grunt.loadNpmTasks("grunt-angular-templates")

 #...省略...
   # 追加
    ngtemplates:
      gambaApp:
        cwd: "<%= yeoman.app %>"
        src: "templates/*.html"
        dest: ".tmp/scripts/templates.js"
        options:
          collapseWhitespace: true
          conservativeCollapse: true
          collapseBooleanAttributes: true
          removeCommentsFromCDATA: true
          removeOptionalTags: true
          usemin: "scripts/scripts.js"

 #...省略...

  grunt.registerTask "build", [
    "clean:dist"
    "wiredep"
    "useminPrepare"
    "ngtemplates"     #<- 追加
    "concurrent:dist"
    "autoprefixer"
    "concat"
    "ngAnnotate"
    "copy:dist"
    "cdnify"
    "cssmin"
    "uglify"
    "filerev"
    "usemin"
    "htmlmin"
  ]

index.html

   <!-- build:js({.tmp,app}) scripts/scripts.js -->
   <script src="scripts/app.js"></script>
  <script src="scripts/templates.js"></script>
  <!- 追加↑ -->

ので、useminタスクに以下のように追加した。

#Gruntfile.coffee

  # Performs rewrites based on filerev and the useminPrepare configuration
    usemin:
      js: ["<%= yeoman.dist %>/scripts/scripts*.js"] #<- 追加
      html: ["<%= yeoman.dist %>/{,*/}*.html"]
      css: ["<%= yeoman.dist %>/styles/{,*/}*.css"]
      options:
        assetsDirs: [
          "<%= yeoman.dist %>"
          "<%= yeoman.dist %>/images"
        ]
       # patternsを追加して、画像のリンクにhashがつくようにする
        patterns:
          js: [
            [/(hogefuga\.jpg)/g, "Replacing reference to hogefuga.jpg"]
          ]

自分で一からGruntfileでタスクを準備していった場合は、こういった部分も考慮にいれて使うパッケージを検討、タスクの順番や構成を組むだろうが、あとから追加していこうとすると、最初から色々用意したあったタスクやnpmのパッケージとの兼ね合いで若干面倒。
色々なnpmを使って、タスクをカスタマイズしまくるような場合はgenerator-angularなどを使用せず、面倒でも一からタスクを吟味して組んでいった方が幸せになれるかもしれない。