jasmine-fixture - 从未见过的DOM变化

时间:2013-04-14 16:13:15

标签: javascript unit-testing dom jasmine fixtures

我有两个RequireJS模块,一个用于从外部服务获取数据,一个负责将回调传递给第一个模块。

这是第一个非常基本的模块:

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);
                }
            });
        }
    };
});

我正在尝试使用Jasmine-fixture和使用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).change替换$(employeeDropDownSelector).on('change',和/或用{domReady包裹activities.observeEmployees调用(和$('#employees').trigger('change');)会产生相同的结果

P.P.S。:这个错误是原因 - &gt; 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]

P.P.P.S。:似乎HtmlUnit不支持CSS3选择器(WTF?),甚至强迫最新发布的版本作为jasmine-maven-plugin依赖也不会改变任何东西......

有没有办法改变jasmine plugin runner?

2 个答案:

答案 0 :(得分:1)

好的伙计们。

找到解决方案:

  1. 升级(如果尚未)到jasmine-maven-plugin v1.3.1.1(或更高版本)
  2. configure phantomjs而不是这个糟糕的HtmlUnit(将PhantomJS binaries添加到您的项目中)
  3. 如果您在代码中使用了':focus'选择器,请小心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>'); }); }); }); }); }); 之内。
  5. 最后,一切都应该好。

    现在看看考试情况如何:

    {{1}}

答案 1 :(得分: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。或者,如果这个。$公司已经附加到页面上,则根本不需要做任何事情。