1

我有两个 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-fixtures 和使用 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);
        });
    });
});

提前致谢!

罗尔夫

PS:用 domReady 替换$(employeeDropDownSelector).change$(employeeDropDownSelector).on('change',/或包装activities.observeEmployees调用(和$('#employees').trigger('change');)会产生相同的结果

PPS:这个错误是原因 -> 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 选择器(WTF?),甚至强制将最新发布的版本作为 jasmine-maven-plugin 依赖项不会改变任何东西......

有没有办法改变 jasmine 插件运行器?

4

2 回答 2

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 并返回它自己,以便下一个函数可以选择它应该如何处理 this.$companies。或者如果 this.$companies 已经附加到页面上,则根本不需要做任何事情。

于 2013-04-19T03:14:39.437 回答
1

好了朋友们。

找到的解决方案:

  1. 升级(如果尚未)到 jasmine-maven-plugin v1.3.1.1(或更高版本)
  2. 配置 phantomjs而不是这个糟糕的 HtmlUnit(将PhantomJS 二进制文件添加到您的项目中)
  3. 如果您在代码中使用了 ':focus' 选择器,请注意此错误,请将其替换为$(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>');
                });
            });
        });
    });
});
于 2013-04-18T17:36:14.627 回答