Backbone Views / Jasmine / Requirejs - 无法读取 属性 未定义的替换

Backbone Views / Jasmine / Requirejs - Cannot read property replace of undefined

使用 Jasmine,我已经为我的模型和控制器编写了测试并且都通过了。但是,如果我测试我的观点,我 运行 会遇到很多问题。

  1. 如果我从 base.js 中的定义函数中删除 views/hf_appView,PostViewSpec 测试将通过。这只是一个非常简单且人为设计的示例,但它说明了我在尝试测试其他视图时也不断遇到的错误(在我的浏览器的开发人员工具控制台中)。

  2. 参见下面的第二个示例,其中我根本无法开始工作。我这样做是为了看看不同的视图模块是否可以工作,因为基本模块可能有问题,但运气不好。

  3. 即使在传递的集合规范中,我随后在定义函数中添加了对 views/hf_postView 的引用,只是为了看看测试会发生什么,然后突然之间我得到了相同的结果如下所示的 #1 和 2 错误。

根据下面的错误,我的一个理论是:可能附加到视图的 #listTpl 没有通过,因为 DOM 还没有准备好视图被解析?如果是这样,我该如何解决?

同样,模型和控制器通过了,但是一旦我开始包含视图模块,我就遇到了很多麻烦。错误是:

错误Chrome

Cannot read property 'replace' of undefined

Safari 出错

[Warning] Invalid CSS property declaration at: * (jasmine.css, line 16)
[Error] TypeError: undefined is not an object (evaluating 'n.replace')
    template (underscore-min.js, line 5)
    (anonymous function) (hf_postView.js, line 12)
    execCb (require.js, line 1658)
    check (require.js, line 874)
    (anonymous function) (require.js, line 624)
    each (require.js, line 57)
    breakCycle (require.js, line 613)
    (anonymous function) (require.js, line 626)
    each (require.js, line 57)
    breakCycle (require.js, line 613)
    (anonymous function) (require.js, line 626)
    each (require.js, line 57)
    breakCycle (require.js, line 613)
    (anonymous function) (require.js, line 626)
    each (require.js, line 57)
    breakCycle (require.js, line 613)
    (anonymous function) (require.js, line 626)
    each (require.js, line 57)
    breakCycle (require.js, line 613)
    (anonymous function) (require.js, line 700)
    each (require.js, line 57)
    checkLoaded (require.js, line 699)
    completeLoad (require.js, line 1576)
    onScriptLoad (require.js, line 1679)

它在 hf_postView.js 的第 12 行引用的匿名函数是

template: _.template($('#listTpl').html()), 

-------------------------------------

SpecRunner.html

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8"/>
  <title>Jasmine Test Runner</title>
  <link rel="stylesheet" type="text/css" href="../../test/jasmine/lib/jasmine.css">
</head>
<body>

SpecRunner.js - 部分!!

specs.push('spec/PostModelSpec');
specs.push('spec/PostCollectionSpec');
specs.push('spec/PostViewSpec');

require(['boot'], function(){
  require(specs, function(){
    window.onload();
  });
});

PostViewSpec.js

define(['views/base'], function(Base) {

  describe("View :: PostView", function() {
    it('View :: Should have tag name', function() {
      var base = new Base();
      base.render()
      expect(base.tagName).toEqual('div');
    });

  }); //describe
});//define

views/base.js

define([
  'jquery',
  'underscore',
  'backbone',
  'views/hf_appView', //****REMOVE this and the hfAppView below, and the test passes ******//
  'collections/hf_collections'], function($, _, Backbone, hfAppView, hfposts){

    return Base = Backbone.View.extend({
      el: '#securities',

      render: function() {
         //add code here
      }
    });

});

-------------------------------------------- -----

PostViewSpec.js - 第二个测试示例 - 使用不同的视图模块根本不起作用。

define(['jquery', 'underscore', 'backbone', 'views/hf_postView'], function($, _, Backbone, hfPostView) {

    it('View :: Should have tag name', function() {
      var base = new hfPostView();
      base.render()
      expect(base.tagName).toEqual('li');
    });

  }); //describe
});//define

views/hf_postView

define([
  'jquery',
  'underscore',
  'backbone',
  'models/hf_models',
  'views/hf_appView',
  'utils'], function($, _, Backbone, PostModel, hfAppView, utils){

    return hfPostView = Backbone.View.extend({
      tagName: 'li',
      className: 'securities',
      template: _.template($('#listTpl').html()),
      events: {
        'click .delete': 'deletePost'
      },

      initialize: function() {
        this.listenTo(this.model, 'destroy', this.remove);
        if(!this.model) {
          throw new Error('Must have HFPOST model');
        }
      },

      render: function() {
        this.$el.html(this.template(   
          this.model.toJSON()
        ));
        return this; 
      },

      deletePost: function() {
        var confirmed = confirm("Are you sure?");
        if (confirmed) {
          this.model.destroy();
          utils.showAlert('Entry was deleted', '', 'alert-warning');
        }
      } //delete post
    }); //Backbone View
});

-------------------------------------------- -----

PostCollectionSpec.js - 只有当我也删除对 hf_postView 的引用时,这才有效。

define(['collections/hf_collections', 'views/hf_postView'], function(hfposts, hfPostView) {

  describe('Controller :: Post Controller', function () {
    var posts;
    beforeEach(function() {
      posts = hfposts;
    });

    it('url should be /api', function() {
      expect(posts.url).toEqual('/api');
    });

    it('should create new model', function() {
      var post1 = hfposts.set({name: 'thisPost'});
      expect(post1.get("name")).toEqual('thisPost');
    });

  }); //describe
}); //define

如果您传递 _.template(...) 一个空的 DOM 元素,就会发生这种情况。使用未缩小的 underscore.js,这样做 ...

_.template($('#thisDoesNotExist'), { foo: "bar" });

...导致...

 Uncaught TypeError: Cannot read property 'replace' of null underscore.js:1304 _.template

underscore.js 的第 1304 行是这样做的:

text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
    source += text.slice(index, offset).replace(escaper, escapeChar);
    // ... and so on

您尝试评估 n.replace 的错误是该 text.replace(...) 调用的缩小版本。

这是因为您的视图单元测试试图通过 $('#listTpl') 从页面加载某些内容(我假设)在视图本身之外。 ID 为 "listTpl" 的 DOM 元素在托管单元测试的页面上不存在,因此它 returns 什么都没有。无赖。

如何解决?由于您已经在使用 RequireJS,我建议您也使用 the text plugin。这将允许您完全在托管应用程序视图的页面之外定义模板文件,这对于测试来说非常好。在现有的 RequireJS 应用程序中获取 运行 也非常容易,因为没有额外的下载或配置;只需为您想要的依赖项使用 text! 前缀。

将模板移出到单独的文件后,您的视图可能如下所示:

define([
    'jquery',
    'underscore',
    'backbone',
    'models/hf_models',
    'views/hf_appView',
    // Assume you created a separate directory for templates ...
    'text!templates/list.template',
    'utils'], function($, _, Backbone, PostModel, hfAppView, listTemplate, utils){

        return hfPostView = Backbone.View.extend({
            tagName: 'li',
            className: 'securities',
            // Woohoo!  The text plugin provides our template content!
            template: _.template(listTemplate),
            events: {
                'click .delete': 'deletePost'
            },
        // ... and so on ...

在那之后,你的单元测试不必改变。只要求 views/hf_postView 将自动引入模板文本和任何其他依赖项。

好的衡量标准:我非常喜欢测试 Backbone 视图,方法是断言尽可能接近真实应用程序将看到的内容。我的一些 View 测试如下所示:

describe('after the View has rendered', function() {
    var view;

    beforeEach(function() {
        view = new MyView({
            model: mockModel
        });
        view.render();
    });

    it('initially has a "Submit" button', function() {
        var content = view.$el.html();
        var matched = content.indexOf('<button>Submit</button>');
        expect(match).to.be.greaterThan(-1);
    });
});

不完全是这样,但你明白了。