blob: ec36270de68a39ba4e46a441452596c586fa6150 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function (context) {
var DEFAULT_DATA_TABLE_LIMIT = 8;
var objToString = Object.prototype.toString;
var TYPED_ARRAY = {
'[object Int8Array]': 1,
'[object Uint8Array]': 1,
'[object Uint8ClampedArray]': 1,
'[object Int16Array]': 1,
'[object Uint16Array]': 1,
'[object Int32Array]': 1,
'[object Uint32Array]': 1,
'[object Float32Array]': 1,
'[object Float64Array]': 1
};
var testHelper = {};
/**
* @param {Object} opt
* @param {string|Array.<string>} [opt.title] If array, each item is on a single line.
* Can use '**abc**', means <strong>abc</strong>.
* @param {Option} opt.option
* @param {Object} [opt.info] info object to display.
* @param {string} [opt.infoKey='option']
* @param {Object|Array} [opt.dataTable]
* @param {Array.<Object|Array>} [opt.dataTables] Multiple dataTables.
* @param {number} [opt.dataTableLimit=DEFAULT_DATA_TABLE_LIMIT]
* @param {number} [opt.width]
* @param {number} [opt.height]
* @param {boolean} [opt.draggable]
* @param {boolean} [opt.lazyUpdate]
* @param {boolean} [opt.notMerge]
* @param {boolean} [opt.autoResize=true]
* @param {Object} [opt.scheduleOpt]
* @param {Array.<Object>|Object} [opt.button] {text: ..., onClick: ...}, or an array of them.
* @param {Array.<Object>|Object} [opt.buttons] {text: ..., onClick: ...}, or an array of them.
* @param {boolean} [opt.recordCanvas] 'test/lib/canteen.js' is required.
*/
testHelper.create = function (echarts, domOrId, opt) {
var dom = getDom(domOrId);
if (!dom) {
return;
}
var title = document.createElement('div');
var left = document.createElement('div');
var chartContainer = document.createElement('div');
var buttonsContainer = document.createElement('div');
var dataTableContainer = document.createElement('div');
var infoContainer = document.createElement('div');
var recordCanvasContainer = document.createElement('div');
title.setAttribute('title', dom.getAttribute('id'));
title.className = 'test-title';
dom.className = 'test-chart-block';
left.className = 'test-chart-block-left';
chartContainer.className = 'test-chart';
buttonsContainer.className = 'test-buttons';
dataTableContainer.className = 'test-data-table';
infoContainer.className = 'test-info';
recordCanvasContainer.className = 'record-canvas';
if (opt.info) {
dom.className += ' test-chart-block-has-right';
infoContainer.className += ' test-chart-block-right';
}
left.appendChild(recordCanvasContainer);
left.appendChild(buttonsContainer);
left.appendChild(dataTableContainer);
left.appendChild(chartContainer);
dom.appendChild(infoContainer);
dom.appendChild(left);
dom.parentNode.insertBefore(title, dom);
var chart;
var optTitle = opt.title;
if (optTitle) {
if (optTitle instanceof Array) {
optTitle = optTitle.join('\n');
}
title.innerHTML = '<div class="test-title-inner">'
+ testHelper.encodeHTML(optTitle)
.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>')
.replace(/\n/g, '<br>')
+ '</div>';
}
if (opt.option) {
chart = testHelper.createChart(echarts, chartContainer, opt.option, opt, opt.setOptionOpts);
}
var dataTables = opt.dataTables;
if (!dataTables && opt.dataTable) {
dataTables = [opt.dataTable];
}
if (dataTables) {
var tableHTML = [];
for (var i = 0; i < dataTables.length; i++) {
tableHTML.push(createDataTableHTML(dataTables[i], opt));
}
dataTableContainer.innerHTML = tableHTML.join('');
}
var buttons = opt.buttons || opt.button;
if (!(buttons instanceof Array)) {
buttons = buttons ? [buttons] : [];
}
if (buttons.length) {
for (var i = 0; i < buttons.length; i++) {
var btnDefine = buttons[i];
if (btnDefine) {
var btn = document.createElement('button');
btn.innerHTML = testHelper.encodeHTML(btnDefine.name || btnDefine.text || 'button');
btn.addEventListener('click', btnDefine.onClick || btnDefine.onclick);
buttonsContainer.appendChild(btn);
}
}
}
if (opt.info) {
updateInfo(opt.info, opt.infoKey);
}
function updateInfo(info, infoKey) {
infoContainer.innerHTML = createObjectHTML(info, infoKey || 'option');
}
initRecordCanvas(opt, chart, recordCanvasContainer);
chart.__testHelper = {
updateInfo: updateInfo
};
return chart;
};
function initRecordCanvas(opt, chart, recordCanvasContainer) {
if (!opt.recordCanvas) {
return;
}
recordCanvasContainer.innerHTML = ''
+ '<button>Show Canvas Record</button>'
+ '<button>Clear Canvas Record</button>'
+ '<div class="content-area"><textarea></textarea><br><button>Close</button></div>';
var buttons = recordCanvasContainer.getElementsByTagName('button');
var canvasRecordButton = buttons[0];
var clearButton = buttons[1];
var closeButton = buttons[2];
var recordArea = recordCanvasContainer.getElementsByTagName('textarea')[0];
var contentAraa = recordArea.parentNode;
canvasRecordButton.addEventListener('click', function () {
var content = [];
eachCtx(function (zlevel, ctx) {
content.push('\nLayer zlevel: ' + zlevel, '\n\n');
if (typeof ctx.stack !== 'function') {
alert('Missing: <script src="test/lib/canteen.js"></script>');
return;
}
var stack = ctx.stack();
for (var i = 0; i < stack.length; i++) {
var line = stack[i];
content.push(JSON.stringify(line), ',\n');
}
});
contentAraa.style.display = 'block';
recordArea.value = content.join('');
});
clearButton.addEventListener('click', function () {
eachCtx(function (zlevel, ctx) {
ctx.clear();
});
recordArea.value = 'Cleared.';
});
closeButton.addEventListener('click', function () {
contentAraa.style.display = 'none';
});
function eachCtx(cb) {
var layers = chart.getZr().painter.getLayers();
for (var zlevel in layers) {
if (layers.hasOwnProperty(zlevel)) {
var layer = layers[zlevel];
var canvas = layer.dom;
var ctx = canvas.getContext('2d');
cb(zlevel, ctx);
}
}
}
}
/**
* @param {ECharts} echarts
* @param {HTMLElement|string} domOrId
* @param {Object} option
* @param {boolean|number} opt If number, means height
* @param {boolean} opt.lazyUpdate
* @param {boolean} opt.notMerge
* @param {number} opt.width
* @param {number} opt.height
* @param {boolean} opt.draggable
* @param {boolean} opt.scheduleOpt
*/
testHelper.createChart = function (echarts, domOrId, option, opt) {
if (typeof opt === 'number') {
opt = {height: opt};
}
else {
opt = opt || {};
}
var dom = getDom(domOrId);
if (dom) {
if (opt.width != null) {
dom.style.width = opt.width + 'px';
}
if (opt.height != null) {
dom.style.height = opt.height + 'px';
}
var chart = echarts.init(dom, null, {
schedule: opt.schedule
});
if (opt.draggable) {
if (!window.draggable) {
throw new Error(
'Pleasse add the script in HTML: \n'
+ '<script src="lib/draggable.js"></script>'
);
}
window.draggable.init(dom, chart, {throttle: 70});
}
option && chart.setOption(option, {
lazyUpdate: opt.lazyUpdate,
notMerge: opt.notMerge
});
var isAutoResize = opt.autoResize == null ? true : opt.autoResize;
if (isAutoResize) {
testHelper.resizable(chart);
}
return chart;
}
};
/**
* @usage
* ```js
* testHelper.printAssert(chart, function (assert) {
* // If any error thrown here, a "checked: Fail" will be printed on the chart;
* // Otherwise, "checked: Pass" will be printed on the chart.
* assert(condition1);
* assert(condition2);
* assert(condition3);
* });
* ```
* `testHelper.printAssert` can be called multiple times for one chart instance.
* For each call, one result (fail or pass) will be printed.
*
* @param chartOrDomId {EChartsInstance | string}
* @param checkFn {Function} param: a function `assert`.
*/
testHelper.printAssert = function (chartOrDomId, checkerFn) {
var hostDOMEl;
var chart;
if (typeof chartOrDomId === 'string') {
hostDOMEl = document.getElementById(chartOrDomId);
}
else {
chart = chartOrDomId;
hostDOMEl = chartOrDomId.getDom();
}
var failErr;
function assert(cond) {
if (!cond) {
throw new Error();
}
}
try {
checkerFn(assert);
}
catch (err) {
console.error(err);
failErr = err;
}
var printAssertRecord = hostDOMEl.__printAssertRecord || (hostDOMEl.__printAssertRecord = []);
var resultDom = document.createElement('div');
resultDom.innerHTML = failErr ? 'checked: Fail' : 'checked: Pass';
var fontSize = 40;
resultDom.style.cssText = [
'position: absolute;',
'left: 20px;',
'font-size: ' + fontSize + 'px;',
'z-index: ' + (failErr ? 99999 : 88888) + ';',
'color: ' + (failErr ? 'red' : 'green') + ';',
].join('');
printAssertRecord.push(resultDom);
hostDOMEl.appendChild(resultDom);
relayoutResult();
function relayoutResult() {
var chartHeight = chart ? chart.getHeight() : hostDOMEl.offsetHeight;
var lineHeight = Math.min(fontSize + 10, (chartHeight - 20) / printAssertRecord.length);
for (var i = 0; i < printAssertRecord.length; i++) {
var record = printAssertRecord[i];
record.style.top = (10 + i * lineHeight) + 'px';
}
}
};
var _dummyRequestAnimationFrameMounted = false;
/**
* Usage:
* ```js
* testHelper.controlFrame({pauseAt: 60});
* // Then load echarts.js (must after controlFrame called)
* ```
*
* @param {Object} [opt]
* @param {number} [opt.puaseAt] If specified `pauseAt`, auto pause at the frame.
* @param {Function} [opt.onFrame]
*/
testHelper.controlFrame = function (opt) {
opt = opt || {};
var pauseAt = opt.pauseAt;
pauseAt == null && (pauseAt = 0);
var _running = true;
var _pendingCbList = [];
var _frameNumber = 0;
var _mounted = false;
function getRunBtnText() {
return _running ? 'pause' : 'run';
}
var buttons = [{
text: getRunBtnText(),
onclick: function () {
buttons[0].el.innerHTML = getRunBtnText();
_running ? pause() : run();
}
}, {
text: 'next frame',
onclick: nextFrame
}];
var btnPanel = document.createElement('div');
btnPanel.className = 'control-frame-btn-panel'
var infoEl = document.createElement('div');
infoEl.className = 'control-frame-info';
btnPanel.appendChild(infoEl);
document.body.appendChild(btnPanel);
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var btnEl = button.el = document.createElement('button');
btnEl.innerHTML = button.text;
btnEl.addEventListener('click', button.onclick);
btnPanel.appendChild(btnEl);
}
if (_dummyRequestAnimationFrameMounted) {
throw new Error('Do not support `controlFrame` twice');
}
_dummyRequestAnimationFrameMounted = true;
var raf = window.requestAnimationFrame;
window.requestAnimationFrame = function (cb) {
_pendingCbList.push(cb);
if (_running && !_mounted) {
_mounted = true;
raf(nextFrame);
}
};
function run() {
_running = true;
nextFrame();
}
function pause() {
_running = false;
}
function nextFrame() {
opt.onFrame && opt.onFrame(_frameNumber);
if (pauseAt != null && _frameNumber === pauseAt) {
_running = false;
pauseAt = null;
}
infoEl.innerHTML = 'Frame: ' + _frameNumber + ' ( ' + (_running ? 'Running' : 'Paused') + ' )';
buttons[0].el.innerHTML = getRunBtnText();
_mounted = false;
var pending = _pendingCbList;
_pendingCbList = [];
for (var i = 0; i < pending.length; i++) {
pending[i]();
}
_frameNumber++;
}
}
testHelper.resizable = function (chart) {
var dom = chart.getDom();
var width = dom.clientWidth;
var height = dom.clientHeight;
function resize() {
var newWidth = dom.clientWidth;
var newHeight = dom.clientHeight;
if (width !== newWidth || height !== newHeight) {
chart.resize();
width = newWidth;
height = newHeight;
}
}
if (window.attachEvent) {
// Use builtin resize in IE
window.attachEvent('onresize', chart.resize);
}
else if (window.addEventListener) {
window.addEventListener('resize', resize, false);
}
};
// Clean params specified by `cleanList` and seed a param specifid by `newVal` in URL.
testHelper.setURLParam = function (cleanList, newVal) {
var params = getParamListFromURL();
for (var i = params.length - 1; i >= 0; i--) {
for (var j = 0; j < cleanList.length; j++) {
if (params[i] === cleanList[j]) {
params.splice(i, 1);
}
}
}
newVal && params.push(newVal);
params.sort();
location.search = params.join('&');
};
// Whether has param `val` in URL.
testHelper.hasURLParam = function (val) {
var params = getParamListFromURL();
for (var i = params.length - 1; i >= 0; i--) {
if (params[i] === val) {
return true;
}
}
return false;
};
// Nodejs `path.resolve`.
testHelper.resolve = function () {
var resolvedPath = '';
var resolvedAbsolute;
for (var i = arguments.length - 1; i >= 0 && !resolvedAbsolute; i--) {
var path = arguments[i];
if (path) {
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path[0] === '/';
}
}
if (!resolvedAbsolute) {
throw new Error('At least one absolute path should be input.');
}
// Normalize the path
resolvedPath = normalizePathArray(resolvedPath.split('/'), false).join('/');
return '/' + resolvedPath;
};
testHelper.encodeHTML = function (source) {
return String(source)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
};
/**
* @public
* @return {string} Current url dir.
*/
testHelper.dir = function () {
return location.origin + testHelper.resolve(location.pathname, '..');
};
/**
* Not accurate.
* @param {*} type
* @return {string} 'function', 'array', 'typedArray', 'regexp',
* 'date', 'object', 'boolean', 'number', 'string'
*/
var getType = testHelper.getType = function (value) {
var type = typeof value;
var typeStr = objToString.call(value);
return !!TYPED_ARRAY[objToString.call(value)]
? 'typedArray'
: typeof type === 'function'
? 'function'
: typeStr === '[object Array]'
? 'array'
: typeStr === '[object Number]'
? 'number'
: typeStr === '[object Boolean]'
? 'boolean'
: typeStr === '[object String]'
? 'string'
: typeStr === '[object RegExp]'
? 'regexp'
: typeStr === '[object Date]'
? 'date'
: !!value && type === 'object'
? 'object'
: null;
};
/**
* JSON.stringify(obj, null, 2) will vertically layout array, which takes too much space.
* Can print like:
* [
* {name: 'xxx', value: 123},
* {name: 'xxx', value: 123},
* {name: 'xxx', value: 123}
* ]
* {
* arr: [33, 44, 55],
* str: 'xxx'
* }
*
* @param {*} object
* @param {opt|string} [opt] If string, means key.
* @param {string} [opt.key=''] Top level key, if given, print like: 'someKey: [asdf]'
* @param {string} [opt.objectLineBreak=true]
* @param {string} [opt.arrayLineBreak=false]
* @param {string} [opt.indent=4]
* @param {string} [opt.lineBreak='\n']
* @param {string} [opt.quotationMark='\'']
*/
var printObject = testHelper.printObject = function (obj, opt) {
opt = typeof opt === 'string'
? {key: opt}
: (opt || {});
var indent = opt.indent != null ? opt.indent : 4;
var lineBreak = opt.lineBreak != null ? opt.lineBreak : '\n';
var quotationMark = opt.quotationMark != null ? opt.quotationMark : '\'';
return doPrint(obj, opt.key, 0).str;
function doPrint(obj, key, depth) {
var codeIndent = (new Array(depth * indent + 1)).join(' ');
var subCodeIndent = (new Array((depth + 1) * indent + 1)).join(' ');
var hasLineBreak = false;
var preStr = key != null ? (key + ': ' ) : '';
var str;
var objType = getType(obj);
switch (objType) {
case 'function':
hasLineBreak = true;
str = preStr + quotationMark + obj + quotationMark;
break;
case 'regexp':
case 'date':
str = preStr + quotationMark + obj + quotationMark;
break;
case 'array':
case 'typedArray':
hasLineBreak = opt.arrayLineBreak != null ? opt.arrayLineBreak : false;
// If no break line in array, print in single line, like [12, 23, 34].
// else, each item takes a line.
var childBuilder = [];
for (var i = 0, len = obj.length; i < len; i++) {
var subResult = doPrint(obj[i], null, depth + 1);
childBuilder.push(subResult.str);
if (subResult.hasLineBreak) {
hasLineBreak = true;
}
}
var tail = hasLineBreak ? lineBreak : '';
var delimiter = ',' + (hasLineBreak ? (lineBreak + subCodeIndent) : ' ');
var subPre = hasLineBreak ? subCodeIndent : '';
var endPre = hasLineBreak ? codeIndent : '';
str = ''
+ preStr + '[' + tail
+ subPre + childBuilder.join(delimiter) + tail
+ endPre + ']';
break;
case 'object':
hasLineBreak = opt.objectLineBreak != null ? opt.objectLineBreak : true;
var childBuilder = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
var subResult = doPrint(obj[i], i, depth + 1);
childBuilder.push(subResult.str);
if (subResult.hasLineBreak) {
hasLineBreak = true;
}
}
}
str = ''
+ preStr + '{' + (hasLineBreak ? lineBreak : '')
+ (childBuilder.length
? (hasLineBreak ? subCodeIndent : '') + childBuilder.join(',' + (hasLineBreak ? lineBreak + subCodeIndent: ' ')) + (hasLineBreak ? lineBreak: '')
: ''
)
+ (hasLineBreak ? codeIndent : '') + '}';
break;
case 'boolean':
case 'number':
str = preStr + obj + '';
break;
case 'string':
str = JSON.stringify(obj); // escapse \n\r or others.
str = preStr + quotationMark + str.slice(1, str.length - 1) + quotationMark;
break;
default:
str = preStr + obj + '';
}
return {
str: str,
hasLineBreak: hasLineBreak
};
}
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* var str = testHelper.stringifyElements(chart, {
* attr: ['z', 'z2', 'style.text', 'style.fill', 'style.stroke'],
* filter: el => el.style && el.style.text
* });
* ```
*
* @param {EChart} chart
* @param {Object} [opt]
* @param {string|Array.<string>} [opt.attr] Only print the given attrName;
* For example: 'z2' or ['z2', 'style.fill', 'style.stroke']
* @param {function} [opt.filter] print a subtree only if any satisfied node exists.
* param: el, return: boolean
*/
testHelper.stringifyElements = function (chart, opt) {
if (!chart) {
return;
}
opt = opt || {};
var attrNameList = opt.attr;
if (getType(attrNameList) !== 'array') {
attrNameList = attrNameList ? [attrNameList] : [];
}
var zr = chart.getZr();
var roots = zr.storage.getRoots();
var plainRoots = [];
retrieve(roots, plainRoots);
var elsStr = printObject(plainRoots, {indent: 2});
return elsStr;
// Only retrieve the value of the given attrName.
function retrieve(elList, plainNodes) {
var anySatisfied = false;
for (var i = 0; i < elList.length; i++) {
var el = elList[i];
var thisElSatisfied = !opt.filter || opt.filter(el);
var plainNode = {};
copyElment(plainNode, el);
var textContent = el.getTextContent();
if (textContent) {
plainNode.textContent = {};
copyElment(plainNode.textContent, textContent);
}
var thisSubAnySatisfied = false;
if (el.isGroup) {
plainNode.children = [];
thisSubAnySatisfied = retrieve(el.childrenRef(), plainNode.children);
}
if (thisElSatisfied || thisSubAnySatisfied) {
plainNodes.push(plainNode);
anySatisfied = true;
}
}
return anySatisfied;
}
function copyElment(plainNode, el) {
for (var i = 0; i < attrNameList.length; i++) {
var attrName = attrNameList[i];
var attrParts = attrName.split('.');
var partsLen = attrParts.length;
if (!partsLen) {
continue;
}
var elInner = el;
var plainInner = plainNode;
for (var j = 0; j < partsLen - 1 && elInner; j++) {
var attr = attrParts[j];
elInner = el[attr];
if (elInner) {
plainInner = plainInner[attr] || (plainInner[attr] = {});
}
}
var attr = attrParts[partsLen - 1];
if (elInner && elInner.hasOwnProperty(attr)) {
plainInner[attr] = elInner[attr];
}
}
}
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* testHelper.printElements(chart, {
* attr: ['z', 'z2', 'style.text', 'style.fill', 'style.stroke'],
* filter: el => el.style && el.style.text
* });
* ```
*
* @see `stringifyElements`.
*/
testHelper.printElements = function (chart, opt) {
var elsStr = testHelper.stringifyElements(chart, opt);
console.log(elsStr);
};
/**
* Usage:
* ```js
* // Print all elements that has `style.text`:
* testHelper.retrieveElements(chart, {
* filter: el => el.style && el.style.text
* });
* ```
*
* @param {EChart} chart
* @param {Object} [opt]
* @param {function} [opt.filter] print a subtree only if any satisfied node exists.
* param: el, return: boolean
* @return {Array.<Element>}
*/
testHelper.retrieveElements = function (chart, opt) {
if (!chart) {
return;
}
opt = opt || {};
var attrNameList = opt.attr;
if (getType(attrNameList) !== 'array') {
attrNameList = attrNameList ? [attrNameList] : [];
}
var zr = chart.getZr();
var roots = zr.storage.getRoots();
var result = [];
retrieve(roots);
function retrieve(elList) {
for (var i = 0; i < elList.length; i++) {
var el = elList[i];
if (!opt.filter || opt.filter(el)) {
result.push(el);
}
if (el.isGroup) {
retrieve(el.childrenRef());
}
}
}
return result;
};
// opt: {record: JSON, width: number, height: number}
testHelper.reproduceCanteen = function (opt) {
var canvas = document.createElement('canvas');
canvas.style.width = opt.width + 'px';
canvas.style.height = opt.height + 'px';
var dpr = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = opt.width * dpr;
canvas.height = opt.height * dpr;
var ctx = canvas.getContext('2d');
var record = opt.record;
for (var i = 0; i < record.length; i++) {
var line = record[i];
if (line.attr) {
if (!line.hasOwnProperty('val')) {
alertIllegal(line);
}
ctx[line.attr] = line.val;
}
else if (line.method) {
if (!line.hasOwnProperty('arguments')) {
alertIllegal(line);
}
ctx[line.method].apply(ctx, line.arguments);
}
else {
alertIllegal(line);
}
}
function alertIllegal(line) {
throw new Error('Illegal line: ' + JSON.stringify(line));
}
document.body.appendChild(canvas);
};
function createDataTableHTML(data, opt) {
var sourceFormat = detectSourceFormat(data);
var dataTableLimit = opt.dataTableLimit || DEFAULT_DATA_TABLE_LIMIT;
if (!sourceFormat) {
return '';
}
var html = ['<table><tbody>'];
if (sourceFormat === 'arrayRows') {
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
var line = data[i];
var htmlLine = ['<tr>'];
for (var j = 0; j < line.length; j++) {
var val = i === dataTableLimit ? '...' : line[j];
htmlLine.push('<td>' + testHelper.encodeHTML(val) + '</td>');
}
htmlLine.push('</tr>');
html.push(htmlLine.join(''));
}
}
else if (sourceFormat === 'objectRows') {
for (var i = 0; i < data.length && i <= dataTableLimit; i++) {
var line = data[i];
var htmlLine = ['<tr>'];
for (var key in line) {
if (line.hasOwnProperty(key)) {
var keyText = i === dataTableLimit ? '...' : key;
htmlLine.push('<td class="test-data-table-key">' + testHelper.encodeHTML(keyText) + '</td>');
var val = i === dataTableLimit ? '...' : line[key];
htmlLine.push('<td>' + testHelper.encodeHTML(val) + '</td>');
}
}
htmlLine.push('</tr>');
html.push(htmlLine.join(''));
}
}
else if (sourceFormat === 'keyedColumns') {
for (var key in data) {
var htmlLine = ['<tr>'];
htmlLine.push('<td class="test-data-table-key">' + testHelper.encodeHTML(key) + '</td>');
if (data.hasOwnProperty(key)) {
var col = data[key] || [];
for (var i = 0; i < col.length && i <= dataTableLimit; i++) {
var val = i === dataTableLimit ? '...' : col[i];
htmlLine.push('<td>' + testHelper.encodeHTML(val) + '</td>');
}
}
htmlLine.push('</tr>');
html.push(htmlLine.join(''));
}
}
html.push('</tbody></table>');
return html.join('');
}
function detectSourceFormat(data) {
if (data.length) {
for (var i = 0, len = data.length; i < len; i++) {
var item = data[i];
if (item == null) {
continue;
}
else if (item.length) {
return 'arrayRows';
}
else if (typeof data === 'object') {
return 'objectRows';
}
}
}
else if (typeof data === 'object') {
return 'keyedColumns';
}
}
function createObjectHTML(obj, key) {
var html = isObject(obj)
? testHelper.encodeHTML(printObject(obj, key))
: obj
? obj.toString()
: '';
return [
'<pre class="test-print-object">',
html,
'</pre>'
].join('');
}
var getDom = testHelper.getDom = function (domOrId) {
return getType(domOrId) === 'string' ? document.getElementById(domOrId) : domOrId;
}
// resolves . and .. elements in a path array with directory names there
// must be no slashes or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizePathArray(parts, allowAboveRoot) {
var res = [];
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
// ignore empty parts
if (!p || p === '.') {
continue;
}
if (p === '..') {
if (res.length && res[res.length - 1] !== '..') {
res.pop();
} else if (allowAboveRoot) {
res.push('..');
}
} else {
res.push(p);
}
}
return res;
}
function getParamListFromURL() {
var params = location.search.replace('?', '');
return params ? params.split('&') : [];
}
function isObject(value) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
const type = typeof value;
return type === 'function' || (!!value && type === 'object');
}
context.testHelper = testHelper;
})(window);