This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch test-helper
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 21b80f316427905f368ab73dd16c2ac8d30e8c7d
Author: 100pah <[email protected]>
AuthorDate: Fri Mar 28 17:48:09 2025 +0800

    (test) enhance testHelper.js: support range input and select input.
---
 test/lib/testHelper.js | 320 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 297 insertions(+), 23 deletions(-)

diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js
index c570529d3..16d990d40 100644
--- a/test/lib/testHelper.js
+++ b/test/lib/testHelper.js
@@ -47,8 +47,7 @@
             var myRandom = new seedrandom('echarts-random');
             // Fixed random generator
             Math.random = function () {
-                const val = myRandom();
-                return val;
+                return myRandom();
             };
         });
     }
@@ -62,6 +61,7 @@
      *        Can use '**abc**', means <strong>abc</strong>.
      * @param {Option} opt.option
      * @param {Object} [opt.info] info object to display.
+     *        info can be updated by 
`chart.__testHelper.updateInfo(someInfoObj, 'some_info_key');`
      * @param {string} [opt.infoKey='option']
      * @param {Object|Array} [opt.dataTable]
      * @param {Array.<Object|Array>} [opt.dataTables] Multiple dataTables.
@@ -72,8 +72,50 @@
      * @param {boolean} [opt.lazyUpdate]
      * @param {boolean} [opt.notMerge]
      * @param {boolean} [opt.autoResize=true]
-     * @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 {Array.<Object>|Object|Function} [opt.buttons] They are the 
same: `opt.button`, `opt.inputs`, `opt.input`
+     *  It can be a function that return buttons configuration, like:
+     *      buttons: chart => { return [{text: 'xxx', onclick: fn}, ...]; }
+     *  Item can be these types:
+     *  [{
+     *      // A button(default).
+     *      text: 'xxx',
+     *      // They are the same: `onclick`, `click` (capital insensitive)
+     *      onclick: fn
+     *  }, {
+     *      // A range slider (HTML <input type="range">). Can also be `type: 
'slider'`.
+     *      type: 'range',
+     *      text: 'xxx', // Optional
+     *      min: 0, // Optional
+     *      max: 100, // Optional
+     *      value: 30, // Optional. Must be a number.
+     *      step: 1, // Optional
+     *      // They are the same: `oninput` `input`
+     *      //                    `onchange` `change`
+     *      //                    `onselect` `select` (capital insensitive)
+     *      onchange: function () { console.log(this.value); }
+     *  }, {
+     *      // A select (HTML <select>...</select>).
+     *      type: 'select', // Or `selection`
+     *      options: ['a', 'b', 'c'], // Or [{text: 'a', value: 123}, ...].
+     *                                // Can be any type, like `true`, `123`, 
etc.
+     *                                // They are the same: `options: []`, 
`values: []`, `items: []`.
+     *      valueIndex: 0, // Optional. The initial value index. By default, 
the first option.
+     *      value: 'c', // Optional. The initial value. By default, the first 
option.
+     *                  // Can be any type, like `true`, `123`, etc.
+     *                  // But can only be JS primitive type, as `===` is used 
internally.
+     *      text: 'xxx', // Optional.
+     *      // They are the same: `oninput` `input`
+     *      //                    `onchange` `change`
+     *      //                    `onselect` `select` (capital insensitive)
+     *      onchange: function () { console.log(this.value); }
+     *  }, {
+     *      // A line break.
+     *      // They are the same: `br` `lineBreak` `break` `wrap` `newLine` 
`endOfLine` `carriageReturn`
+     *      //                    `lineFeed` `lineSeparator` `nextLine` 
(capital insensitive)
+     *      type: 'br',
+     *  },
+     *  // ...
+     *  ]
      * @param {boolean} [opt.recordCanvas] 'test/lib/canteen.js' is required.
      * @param {boolean} [opt.recordVideo]
      * @param {string} [opt.renderer] 'canvas' or 'svg'
@@ -85,6 +127,8 @@
             return;
         }
 
+        var errMsgPrefix = '[testHelper dom: ' + domOrId + ']';
+
         var title = document.createElement('div');
         var left = document.createElement('div');
         var chartContainer = document.createElement('div');
@@ -134,7 +178,7 @@
                 + '</div>';
         }
 
-        chart = testHelper.createChart(echarts, chartContainer, opt.option, 
opt, opt.setOptionOpts);
+        chart = testHelper.createChart(echarts, chartContainer, opt.option, 
opt, opt.setOptionOpts, errMsgPrefix);
 
         var dataTables = opt.dataTables;
         if (!dataTables && opt.dataTable) {
@@ -148,21 +192,7 @@
             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);
-                }
-            }
-        }
+        chart && initButtons(chart, opt, buttonsContainer, errMsgPrefix)
 
         if (opt.info) {
             updateInfo(opt.info, opt.infoKey);
@@ -185,6 +215,197 @@
         return chart;
     };
 
+    function initButtons(chart, opt, buttonsContainer, errMsgPrefix) {
+        var NAMES_ON_INPUT_CHANGE = makeFlexibleNames([
+            'input', 'on-input', 'on-change', 'select', 'on-select'
+        ]);
+        var NAMES_ON_CLICK = makeFlexibleNames([
+            'on-click', 'click'
+        ]);
+        var NAMES_TYPE_BUTTON = makeFlexibleNames(['button', 'btn']);
+        var NAMES_TYPE_RANGE = makeFlexibleNames(['range', 'slider']);
+        var NAMES_TYPE_SELECT = makeFlexibleNames(['select', 'selection']);
+        var NAMES_TYPE_BR = makeFlexibleNames([
+            'br', 'line-break', 'break', 'wrap', 'new-line', 'end-of-line',
+            'carriage-return', 'line-feed', 'line-separator', 'next-line'
+        ]);
+
+        var buttons = testHelper.retrieveValue(opt.buttons, opt.button, 
opt.input, opt.inputs);
+        if (typeof buttons === 'function') {
+            buttons = buttons(chart);
+        }
+        if (!(buttons instanceof Array)) {
+            buttons = buttons ? [buttons] : [];
+        }
+        if (!buttons.length) {
+            return;
+        }
+
+        for (var i = 0; i < buttons.length; i++) {
+            processBtnDefine(buttons[i]);
+        }
+
+        function getBtnTextHTML(btnDefine, defaultText) {
+            return 
testHelper.encodeHTML(testHelper.retrieveValue(btnDefine.name, btnDefine.text, 
defaultText));
+        }
+        function getBtnDefineAttr(btnDefine, attr, defaultValue) {
+            return btnDefine[attr] != null ? btnDefine[attr] : defaultValue;
+        }
+        function getBtnEventListener(btnDefine, names) {
+            for (var idx = 0; idx < names.length; idx++) {
+                if (btnDefine[names[idx]]) {
+                    return btnDefine[names[idx]];
+                }
+            }
+        }
+
+        function processBtnDefine(btnDefine) {
+            if (!btnDefine) {
+                return;
+            }
+            var inputType = btnDefine.hasOwnProperty('type') ? btnDefine.type 
: 'button';
+
+            if (arrayIndexOf(NAMES_TYPE_RANGE, inputType) >= 0) {
+                createRangeInput(btnDefine, buttonsContainer);
+            }
+            else if (arrayIndexOf(NAMES_TYPE_SELECT, inputType) >= 0) {
+                createSelectInput(btnDefine, buttonsContainer);
+            }
+            else if (arrayIndexOf(NAMES_TYPE_BR, inputType) >= 0) {
+                createBr(btnDefine, buttonsContainer);
+            }
+            else if (arrayIndexOf(NAMES_TYPE_BUTTON, inputType) >= 0) {
+                createButtonInput(btnDefine, buttonsContainer);
+            }
+            else {
+                throw new Error(errMsgPrefix + 'Unsupported button type: ' + 
inputType);
+            }
+        }
+
+        function createRangeInput(btnDefine, buttonsContainer) {
+            var sliderWrapperEl = document.createElement('span');
+            sliderWrapperEl.className = 'test-buttons-slider';
+            buttonsContainer.appendChild(sliderWrapperEl);
+
+            var sliderTextEl = document.createElement('span');
+            sliderTextEl.className = 'test-buttons-slider-text';
+            sliderTextEl.innerHTML = getBtnTextHTML(btnDefine, '');
+            sliderWrapperEl.appendChild(sliderTextEl);
+
+            var sliderInputEl = document.createElement('input');
+            sliderInputEl.className = 'test-buttons-slider-input';
+            sliderInputEl.setAttribute('type', 'range');
+            var sliderListener = getBtnEventListener(btnDefine, 
NAMES_ON_INPUT_CHANGE);
+            if (!sliderListener) {
+                throw new Error(errMsgPrefix + 'No listener (either ' + 
NAMES_ON_INPUT_CHANGE.join(', ') + ') specified for slider.');
+            }
+            sliderInputEl.addEventListener('input', function () {
+                updateSliderValueEl();
+                var target = {value: this.value};
+                sliderListener.call(target, {target: target});
+            });
+            sliderInputEl.setAttribute('min', getBtnDefineAttr(btnDefine, 
'min', 0));
+            sliderInputEl.setAttribute('max', getBtnDefineAttr(btnDefine, 
'max', 100));
+            sliderInputEl.setAttribute('value', getBtnDefineAttr(btnDefine, 
'value', 30));
+            sliderInputEl.setAttribute('step', getBtnDefineAttr(btnDefine, 
'step', 1));
+            sliderWrapperEl.appendChild(sliderInputEl);
+
+            var sliderValueEl = document.createElement('span');
+            sliderValueEl.className = 'test-buttons-slider-value';
+            function updateSliderValueEl() {
+                var val = sliderInputEl.value;
+                sliderValueEl.innerHTML = testHelper.encodeHTML(val);
+            }
+            updateSliderValueEl();
+            sliderWrapperEl.appendChild(sliderValueEl);
+        }
+
+        function createSelectInput(btnDefine, buttonsContainer) {
+            var selectWrapperEl = document.createElement('span');
+            selectWrapperEl.className = 'test-buttons-select';
+            buttonsContainer.appendChild(selectWrapperEl);
+
+            var textEl = document.createElement('span');
+            textEl.className = 'test-buttons-select-text';
+            textEl.innerHTML = getBtnTextHTML(btnDefine, '');
+            selectWrapperEl.appendChild(textEl);
+
+            var selectEl = document.createElement('select');
+            selectEl.className = 'test-buttons-select-input';
+            selectWrapperEl.appendChild(selectEl);
+
+            var optionDefList = testHelper.retrieveValue(btnDefine.options, 
btnDefine.values, btnDefine.items, []);
+            optionDefList = optionDefList.slice();
+            if (!optionDefList.length) {
+                throw new Error(errMsgPrefix + 'No options specified for 
select.');
+            }
+            for (var idx = 0; idx < optionDefList.length; idx++) {
+                var optionDef = optionDefList[idx];
+                // optionDef can be {text, value} or just value
+                //  (value can be null/undefined/array/object/... everything).
+                // Convinient but might cause ambiguity when a value happens 
to be {text, value}, but rarely happen.
+                if (isObject(optionDef) && optionDef.hasOwnProperty('text') && 
optionDef.hasOwnProperty('value')) {
+                    optionDef = {text: optionDef.text, value: optionDef.value};
+                }
+                else {
+                    optionDef = {
+                        text: printObject(optionDef, {
+                            arrayLineBreak: false, objectLineBreak: false, 
indent: 0, lineBreak: ''
+                        }),
+                        value: optionDef
+                    };
+                }
+                optionDefList[idx] = optionDef;
+                var optionEl = document.createElement('option');
+                optionEl.innerHTML = testHelper.encodeHTML(optionDef.text);
+                // HTML select.value is always string. But it would be more 
convenient to
+                // convert it to user's raw input value type.
+                optionEl.value = idx;
+                selectEl.appendChild(optionEl);
+            }
+            if (btnDefine.hasOwnProperty('valueIndex')) {
+                var valueIndex = btnDefine.valueIndex;
+                if (valueIndex < 0 || valueIndex >= optionDefList.length) {
+                    throw new Error(errMsgPrefix + 'Invalid valueIndex: ' + 
valueIndex);
+                }
+                selectEl.value = optionDefList[valueIndex].value;
+            }
+            else if (btnDefine.hasOwnProperty('value')) {
+                var found = false;
+                for (var idx = 0; idx < optionDefList.length; idx++) {
+                    if (optionDefList[idx].value === btnDefine.value) {
+                        selectEl.value = idx;
+                        found = true;
+                    }
+                }
+                if (!found) {
+                    throw new Error(errMsgPrefix + 'Value not found in select 
options: ' + btnDefine.value);
+                }
+            }
+            var selectListener = getBtnEventListener(btnDefine, 
NAMES_ON_INPUT_CHANGE);
+            if (!selectListener) {
+                throw new Error(errMsgPrefix + 'No listener (either ' + 
NAMES_ON_INPUT_CHANGE.join(', ') + ') specified for select.');
+            }
+            selectEl.addEventListener('change', function () {
+                var idx = this.value;
+                var target = {value: optionDefList[idx].value};
+                selectListener.call(target, {target: target});
+            });
+        }
+
+        function createBr(btnDefine, buttonsContainer) {
+            var brEl = document.createElement('br');
+            buttonsContainer.appendChild(brEl);
+        }
+
+        function createButtonInput(btnDefine, buttonsContainer) {
+            var btn = document.createElement('button');
+            btn.innerHTML = getBtnTextHTML(btnDefine, 'button');
+            btn.addEventListener('click', getBtnEventListener(btnDefine, 
NAMES_ON_CLICK));
+            buttonsContainer.appendChild(btn);
+        }
+    }
+
     function initRecordCanvas(opt, chart, recordCanvasContainer) {
         if (!opt.recordCanvas) {
             return;
@@ -250,7 +471,7 @@
 
         button.onclick = function () {
             isRecording ? recorder.stop() : recorder.start();
-            button.innerHTML = `${isRecording ? 'Start' : 'Stop'} Recording`;
+            button.innerHTML = (isRecording ? 'Start' : 'Stop') + ' Recording';
 
             isRecording = !isRecording;
         }
@@ -269,8 +490,9 @@
      * @param {number} opt.height
      * @param {boolean} opt.draggable
      * @param {string} opt.renderer 'canvas' or 'svg'
+     * @param {string} errMsgPrefix
      */
-    testHelper.createChart = function (echarts, domOrId, option, opt) {
+    testHelper.createChart = function (echarts, domOrId, option, opt, 
errMsgPrefix) {
         if (typeof opt === 'number') {
             opt = {height: opt};
         }
@@ -297,7 +519,7 @@
             if (opt.draggable) {
                 if (!window.draggable) {
                     throw new Error(
-                        'Pleasse add the script in HTML: \n'
+                        errMsgPrefix + 'Pleasse add the script in HTML: \n'
                         + '<script src="lib/draggable.js"></script>'
                     );
                 }
@@ -564,6 +786,20 @@
             .replace(/'/g, '&#39;');
     };
 
+    /**
+     * @usage
+     * var result = retrieveValue(val, defaultVal);
+     * var result = retrieveValue(val1, val2, defaultVal);
+     */
+    testHelper.retrieveValue = function() {
+        for (var i = 0, len = arguments.length; i < len; i++) {
+            var val = arguments[i];
+            if (val != null) {
+                return val;
+            }
+        }
+    };
+
     /**
      * @public
      * @return {string} Current url dir.
@@ -1051,6 +1287,44 @@
         return type === 'function' || (!!value && type === 'object');
     }
 
+    function arrayIndexOf(arr, value) {
+        if (arr.indexOf) {
+            return arr.indexOf(value);
+        }
+        for (var i = 0; i < arr.length; i++) {
+            if (arr[i] === value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    function makeFlexibleNames(dashedNames) {
+        var nameMap = {};
+        for (var i = 0; i < dashedNames.length; i++) {
+            var name = dashedNames[i];
+            var tmpNames = [];
+            tmpNames.push(name);
+            tmpNames.push(name.replace(/-/g, ''));
+            tmpNames.push(name.replace(/-/g, '_'));
+            tmpNames.push(name.replace(/-([a-zA-Z0-9])/g, function (_, wf) {
+                return wf.toUpperCase();
+            }));
+            for (var j = 0; j < tmpNames.length; j++) {
+                nameMap[tmpNames[j]] = 1;
+                nameMap[tmpNames[j].toUpperCase()] = 1;
+                nameMap[tmpNames[j].toLowerCase()] = 1;
+            }
+        }
+        var names = [];
+        for (var name in nameMap) {
+            if (nameMap.hasOwnProperty(name)) {
+                names.push(name);
+            }
+        }
+        return names;
+    }
+
     function VideoRecorder(chart) {
         this.start = startRecording;
         this.stop = stopRecording;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to