2013-04-14 36 views
1

我有兩個RequireJS模塊,一個用於從外部服務獲取數據,一個負責將回調傳遞給第一個模塊。jasmine-fixtures - 從未見過的DOM變化

這是第一個非常基本的模塊:

define(["jquery"], function($) { 

    return { 
     /** 
     * Retrieves all the companies that do not employs the provided employee 
     * @param employeeId ID of the employee 
     * @param successCallback callback executed on successful request completion 
     * @return matching companies 
     */ 
     fetchCompanies: function(employeeId, successCallback) { 
      var url = '/employees/' + employeeId + '/nonEmployers'; 
      return $.getJSON(url, successCallback); 
     } 
    }; 
}); 

而且最有趣的一個,這將產生一個新的下拉並注入到指定的DOM元素(這是被測的一個):

define([ 
    'jquery', 
    'vendor/underscore', 
    'modules/non-employers', 
    'text!tpl/employeeOption.tpl'], function($, _, nonEmployers, employeeTemplate) { 

    var updateCompanies = function(selectedEmployeeId, companyDropDownSelector) { 
     nonEmployers.fetchCompanies(selectedEmployeeId, function(data) { 
      var template = _.template(employeeTemplate), 
       newContents = _.reduce(data, function(string,element) { 
        return string + template({ 
         value: element.id, 
         display: element.name 
        }); 
       }, "<option value='-1'>select a client...</option>\n"); 
      $(companyDropDownSelector).html(newContents); 
     }); 
    }; 

    return { 
     /** 
     * Updates the dropdown identified by companyDropDownSelector 
     * with the companies that are non employing the selected employee 
     * @param employeeDropDownSelector selector of the employee dropdown 
     * @param companyDropDownSelector selector of the company dropdown 
     */ 
     observeEmployees: function(employeeDropDownSelector, companyDropDownSelector) { 
      $(employeeDropDownSelector).change(function() { 
       var selectedEmployeeId = $(employeeDropDownSelector + " option:selected").val(); 
       if (selectedEmployeeId > 0) { 
        updateCompanies(selectedEmployeeId, companyDropDownSelector); 
       } 
      }); 
     } 
    }; 
}); 

我想考這最後的模塊,使用茉莉花裝置和使用waitsFor,異步檢查的建立測試DOM結構已被修改。但是,總是達到超時。

如果你能發現什麼是錯在下面的測試中,我將感激不盡(要點:https://gist.github.com/fbiville/6223bb346476ca88f55d):

define(["jquery", "modules/non-employers", "modules/pages/activities"], function($, nonEmployers, activities) { 
    describe("activities test suite", function() { 
     var $form, $employeesDropDown, $companiesDropDown; 

     beforeEach(function() { 
      $form = affix('form[id=testForm]'); 
      $employeesDropDown = $form.affix('select[id=employees]'); 
      $employeesDropDown.affix('option[selected=selected]'); 
      $employeesDropDown.affix('option[value=1]'); 
      $companiesDropDown = $form.affix('select[id=companies]'); 
      $companiesDropDown.affix('option'); 
     }); 

     it("should update the company dropdown", function() { 
      spyOn(nonEmployers, "fetchCompanies").andCallFake(function(employeeId, callback) { 
       callback([{id: 42, name: "ACME"}, {id: 100, name: "OUI"}]); 
      }); 

      activities.observeEmployees('#employees', '#companies'); 
      $('#employees').trigger('change'); 

      waitsFor(function() { 
       var companiesContents = $('#companies').html(), 
        result = expect(companiesContents).toContain('<option value="42">ACME</option>'); 

       return result && expect(companiesContents).toContain('<option value="100">OUI</option>'); 
      }, 'DOM has never been updated', 10000); 
     }); 
    }); 
}); 

提前感謝!

羅爾夫

P.S .:由$(employeeDropDownSelector).on('change',和/或包裹activities.observeEmployees呼叫(和$('#employees').trigger('change');)與domready中替換$(employeeDropDownSelector).change產生同樣的結果

P.P.S:該錯誤的原因 - >SEVERE: runtimeError: message=[An invalid or illegal selector was specified (selector: '[id='employees'] :selected' error: Invalid selector: *[id="employees"] *:selected).] sourceName=[http://localhost:59811/src/vendor/require-jquery.js] line=[6002] lineSource=[null] lineOffset=[0]

PPPS:好像HtmlUnit無法支持CSS3選擇器,甚至迫使最新發布的版本爲茉莉Maven的插件依賴性不會改變什麼...

有什麼辦法(WTF?)改變茉莉花插件亞軍?

回答

1

好傢伙。

發現的解決方案:

  1. 升級(如果沒有的話),以茉莉花Maven的插件v1.3.1.1(或更高版本)
  2. configure phantomjs,而不是這個蹩腳的HtmlUnit(添加PhantomJS binaries到您的項目)
  3. 如果你有使用「:焦點」選擇在你的代碼,謹防this bug,與$(mySelector).get(0) == document.activeElement
  4. 也代替它,不要忘記run(function() { /* expect */ })來包裝你的代碼塊,如果他們被定位後,依靠您的waitsFor條件。

最後,一切都會好的。

瞭解如何現在測試:

define(["jquery", 
    "modules/nonEmployers", 
    "modules/pages/activities"], function($, nonEmployers, activities) { 

    describe("activities test suite", function() { 
     var $form, $employeesDropDown, $companiesDropDown; 

     beforeEach(function() { 
      $form = affix('form[id=testForm]'); 
      $employeesDropDown = $form.affix('select[id=employees]'); 
      $employeesDropDown.affix('option[selected=selected]'); 
      $employeesDropDown.affix('option[value=1]'); 
      $companiesDropDown = $form.affix('select[id=companies]'); 
      $companiesDropDown.affix('option'); 

      spyOn(nonEmployers, "fetchCompanies").andCallFake(function(employeeId, callback) { 
       callback([{id: 42, name: "ACME"}, {id: 100, name: "OUI"}]); 
      }); 
     }); 


     it("should update the company dropdown", function() { 

      $(document).ready(function() { 
       activities.observeEmployees('#employees', '#companies'); 
       $('#employees option[selected=selected]').removeAttr("selected"); 
       $('#employees option[value=1]').attr("selected", "selected"); 
       $('#employees').trigger('change'); 

       waitsFor(function() { 
        var dropDown = $('#companies').html(); 
        return dropDown.indexOf('ACME') > 0 && dropDown.indexOf('OUI') > 0; 
       }, 'DOM has never been updated', 500); 

       runs(function() { 
        var dropDown = $('#companies').html(); 
        expect(dropDown).toContain('<option value="42">ACME</option>'); 
        expect(dropDown).toContain('<option value="100">OUI</option>'); 
       }); 
      }); 
     }); 
    }); 
}); 
1

創建模塊,這種方式真的很難。我建議不要使用燈具,也不要在任何地方渲染。相反,使用分離的DOM元素來完成所有工作要容易得多。

試想一下,如果你的代碼看起來更接近這一點:

define([ 
    'jquery', 
    'vendor/underscore', 
    'modules/non-employers', 
    'text!tpl/employeeOption.tpl'], function($, _, nonEmployers, employeeTemplate) { 

    return { 
     init: function() { 
      this.$companies = $('<select class="js-companies"></select>'); 
     }, 
     render: function(data) { 
      var template = _.template(employeeTemplate), 
       newContents = _.reduce(data, function(string,element) { 
        return string + template({ 
         value: element.id, 
         display: element.name 
        }); 
       }, "<option value='-1'>select a client...</option>\n"); 
      this.$companies.empty().append(newContents); 
      return this; 
     }); 
     observeEmployees: function(employeeDropDownSelector) { 
      $(employeeDropDownSelector).change(function() { 
       var selectedEmployeeId = $(employeeDropDownSelector + " option:selected").val(); 
       if (selectedEmployeeId > 0) { 
        nonEmployers.fetchCompanies(selectedEmployeeId, function(data) { 
         this.render(data); 
        } 
       } 
      }); 
     } 
    }; 
}); 

以上是不完整的。這只是給你一個解決你的問題的另一種方式的想法。現在不用夾具,您只需檢查this.$companies即可完成。我認爲主要的問題是你的功能不夠簡單。每個功能的關注應該是非常具體的。你的updateCompanies函數正在做一些事情,比如創建一個模板,獲取數據然後將它傳遞給一個匿名函數,這個匿名函數不能被窺探,匿名函數在一個對象上迭代,然後你改變一些已經存在的DOM元素。這聽起來很累。所有該功能應該做的是看一些預編譯模板發送一個對象。模板應該使用{{each}}在對象上循環,然後返回。然後你的函數清空並附加newContents並自行返回,所以下一個函數可以選擇它應該做什麼。$ companies。或者如果這個。$ companies已經被添加到頁面上,那麼根本沒有什麼需要完成的。