UNPKG

ecui

Version:

Enterprise Classic User Interface.

340 lines (300 loc) 11.1 kB
/* ECUI Test - ECUI 集成测试框架,支持异步方式的测试。 */ (function () { /** * 根据 id 获取 DOM 对象 * @public * * @param {string} id DOM 的 id * @return {HTMLElement} DOM 对象 */ function g(id) { return document.getElementById(id); } /** * 一组测试数据信息类 * @public * * @param {string} name 一组测试数据的名称 * @param {Object} entries 一组测试数据的原始信息,信息的格式为 {K: V, ...},其中 K 表示单个测试数据名称,V 表示单个测试函数 */ function Suite(name, entries) { /** * 测试用例信息类 * @public * * @param {string} name 测试数据的名称 * @param {Function} target 测试函数 */ function Case(name, target) { // index 当前用例在总用例中的编号,wait 是否处于等待状态标志,用于异步控制,exception 异常对象,如果正常此值为空 var index = totalCase++, wait, exception; this.end = function () { // 单个用例的后置方法,释放单个用例占用的资源 after && after(); g('progress').innerHTML = Math.floor(++testedCase / totalCase * 100); g('case_' + index).className = exception ? 'exception' : 'success'; if (exception) { // 运行中产生异常 var s = exception.failure, el = ecui.dom.create(); if (s) { el.innerHTML = s == 1 ? '<div>当前值:<p>' + ecui.string.encodeHTML(exception.actual.toString()) + '</p>期待值:<p>' + ecui.string.encodeHTML(exception.expect.toString()) : '期待不是 \'' + ecui.string.encodeHTML(exception.actual.toString()) + '\'<p>'; s = 'total_failures'; } else { el.innerHTML = exception.message + '(' + exception.lineNumber + ')'; s = 'total_errors'; } g('case_' + index).appendChild(el); g(s).innerHTML = parseInt(g(s).innerHTML) + 1; caseException = true; } // 执行下个用例 new ecui.util.timer(parent.next, 0, parent); }; /** * 生成测试用例的日志 HTML 结构 * @public */ this.log = function () { return '<li id="case_' + index + '"><h4>' + ecui.string.encodeHTML(name) + '</h4><pre class="examples-code"><code>' + ecui.string.encodeHTML(target.toString()) + '</code></pre></li>'; }; /** * 运行当前测试用例 * @public */ this.run = function () { wait = false; try { target.call(this); // 没有处于异步等待状态,测试用例运行结束 wait || this.end(); } catch (e) { exception = e; this.end(); } }; /** * 设置测试用例为等待状态,等待异步处理返回 * @public * * @param {Function} func 测试用例下阶段代码片断的函数 * @param {number} delay 等待时间 */ this.wait = function (func, delay) { wait = true; target = func; new ecui.util.timer(this.run, delay, this); }; } // parent 测试用例组,before 用例前置函数,after 用例后置函数,cases 用例数组,index 用例组的编号,testedCaseInSuite 当前激活的用例在用例组中的编号,caseException 用例是否产生过异常 var parent = this, before, after, cases = [], index = suites.length, testedCaseInSuite = 0, caseException = false; for (var key in entries) { if (key == 'before') { before = entries[key]; } else if (key == 'after') { after = entries[key]; } else { cases.push(new Case(key, entries[key])); } } /** * 生成测试用例组的日志 HTML 结构 * @public */ this.log = function () { var html = '<li id="suite_' + index + '"><h3>' + ecui.string.encodeHTML(name) + '</h3><ul id="suite_' + index + '_cases" class="examples">'; for (var i = 0; i < cases.length; i++) { html += cases[i].log(); } return html + '</ul></li>'; }; /** * 执行下一个用例,如果组内已经没有用例,将自动执行下一个组的第一个用例 * @public */ this.next = function () { var o = cases[testedCaseInSuite++]; if (o) { before && before(); o.run(); } else { g('suite_' + index).className = caseException ? 'exception' : 'success'; caseException || (g('suite_' + index + '_cases').style.display = 'none'); setTimeout(NEXT_GROUP, 0); } }; } /** * 判断两个对象是否相等 * @private * * @param {Object} src 源对象 * @param {Object} des 目标对象 * @return {boolean} 比较结果,是否相等 */ function EQUALS(src, des) { if (src === des) { return true; } if (typeof src != typeof des || typeof src != 'object' || src.nodeType) { return false; } var srcKeys = [], desKeys = []; for (var key in src) { srcKeys.push(key); } for (var key in des) { desKeys.push(key); } if (srcKeys.length != desKeys.length) { return false; } for (var i = 0; i < srcKeys.length; i++) { if (!EQUALS(src[srcKeys[i]], des[srcKeys[i]])) { return false; } } return true; } /** * 构造包装对象,用于比较两个对象是否相等 * @private * * @param {Object} value 需要包装的值 */ function Value(value) { var type = Object.prototype.toString.call(value); /** * 判断被包装的对象是否等于另一个对象 * @public * * @param {Object} expectValue 期待的值 */ this.should_be = function (expectValue) { if (type != Object.prototype.toString.call(expectValue) || !EQUALS(value, expectValue)) { throw {failure: 1, expect: (expectValue === null || expectValue === undefined) ? String(expectValue) : expectValue, actual: (value === null || value === undefined) ? String(value) : value}; } }; /** * 判断被包装的对象是否等于 true * @public */ this.should_be_true = function () { this.should_be(true); }; /** * 判断被包装的对象是否等于 false * @public */ this.should_be_false = function () { this.should_be(false); }; /** * 判断被包装的对象是否不等于另一个对象 * @public * * @param {Object} expectValue 不期待的值 */ this.should_not_be = function (expectValue) { if (type == Object.prototype.toString.call(expectValue) && EQUALS(value, expectValue)) { throw {failure: 2, expect: expectValue, actual: value}; } }; } /** * 执行下一个用例组中的用例 * @private */ function NEXT_GROUP() { var o = suites[testedSuite++]; o && o.next(); g('total_elapsed').innerHTML = (new Date().getTime() - startTime) / 1000; if (!o && window.onPageFinish) { onPageFinish(); } } var suites = [], testedCase = 0, testedSuite = 0, totalCase = 0, startTime; ecui.test = { /** * 添加一个测试用例组 * @public * * @param {string} name 测试用例组名称 * @param {Object} entries 测试用例组的描述对象 */ describe: function (name, entries) { suites.push(new Suite(name, entries)); }, /** * 添加一个测试用例组 * @public * * @param {string} name 测试用例组名称 * @param {Object} entries 测试用例组的描述对象 */ value_of: function (value) { return new Value(value); } }; /** * 标题点击事件 * @private */ function H3_CLICK() { var style = this.nextSibling.style; style.display = style.display ? '' : 'none'; } ecui.util.attachEvent(window, 'load', function () { var el = ecui.dom.create(), html = '<div id="title" class="success"><h1>ECUI Test</h1><ul>' + '<li>共 <span id="total_cases">' + totalCase + '</span> 项</li>' + '<li><span id="total_failures">0</span> 失败</li>' + '<li><span id="total_errors">0</span> 错误</li>' + '<li><span id="progress">0</span>% 完成</li>' + '<li><span id="total_elapsed">0</span> 秒</li>' + '</ul></div><div id="log"><ul class="specs">'; el.id = 'jsspec_container'; for (var i = 0; i < suites.length; i++) { html += suites[i].log(); } el.innerHTML = html + '</ul></div>'; html = el.getElementsByTagName('h3'); for (i = 0; i < html.length; i++) { html[i].onclick = H3_CLICK; } document.body.appendChild(el); startTime = new Date().getTime(); NEXT_GROUP(); }); })(); describe = ecui.test.describe; value_of = ecui.test.value_of;