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

sushuang pushed a commit to branch fix/contain-label-name
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 0cae1137200b078d64e439169ef81b9a74e3d623
Author: 100pah <[email protected]>
AuthorDate: Sun Apr 6 18:53:36 2025 +0800

    (test) Sync test facilities update manually.
---
 test/lib/reset.css                |  52 +++-
 test/lib/testHelper.js            | 547 ++++++++++++++++++++++++++++++--------
 test/runTest/cli.js               |   2 +-
 test/runTest/recorder/recorder.js |   2 +-
 4 files changed, 478 insertions(+), 125 deletions(-)

diff --git a/test/lib/reset.css b/test/lib/reset.css
index d12d7fa16..d17312f5a 100644
--- a/test/lib/reset.css
+++ b/test/lib/reset.css
@@ -51,38 +51,74 @@ body > .main {
     padding-left: 2px;
     padding-right: 2px;
 }
-.test-buttons button {
+
+.test-inputs button {
     margin: 10px 5px;
 }
-.test-buttons-slider {
+.test-inputs-fix-height {
+    position: relative;
+    overflow: scroll;
+    box-shadow: inset -3px -3px 3px rgba(0, 0, 0, 0.3);
+}
+.test-inputs-style-compact button {
+    margin-top: 2px;
+    margin-bottom: 2px;
+}
+.test-inputs-slider {
     white-space: nowrap;
     display: inline-block;
     margin: 10px 5px;
     font-size: 14px;
+    padding: 0;
+}
+.test-inputs-style-compact .test-inputs-slider {
+    margin-top: 2px;
+    margin-bottom: 2px;
+}
+.test-inputs-slider-input {
+    width: 129px;
+    height: 16px;
+}
+.test-inputs-style-compact .test-inputs-slider-input {
+    width: 90px;
 }
-.test-buttons-slider span {
+.test-inputs-slider-sub {
+    margin-left: -10px;
+}
+.test-inputs-slider span {
     vertical-align: middle;
     margin: 0 3px;
 }
-.test-buttons-slider input {
+.test-inputs-slider-disabled span {
+    color: #aaa;
+}
+.test-inputs-slider input {
     vertical-align: middle;
     margin: 0 3px;
 }
-.test-buttons-select {
+.test-inputs-select {
     white-space: nowrap;
     display: inline-block;
     font-size: 14px;
     margin: 10px 5px;
     margin-right: 10px;
 }
-.test-buttons-select span {
+.test-inputs-style-compact .test-inputs-select {
+    margin-top: 2px;
+    margin-bottom: 2px;
+}
+.test-inputs-select span {
     vertical-align: middle;
     margin: 0 3px;
 }
-.test-buttons-select input {
+.test-inputs-select select {
     vertical-align: middle;
-    margin: 0 3px;
+    margin: 0;
+    padding: 0;
+    font-size: 13.3333px;
+    height: 19px;
 }
+
 .test-chart-block {
     position: relative;
 }
diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js
index 16d990d40..ae58e64ff 100644
--- a/test/lib/testHelper.js
+++ b/test/lib/testHelper.js
@@ -72,18 +72,28 @@
      * @param {boolean} [opt.lazyUpdate]
      * @param {boolean} [opt.notMerge]
      * @param {boolean} [opt.autoResize=true]
-     * @param {Array.<Object>|Object|Function} [opt.buttons] They are the 
same: `opt.button`, `opt.inputs`, `opt.input`
+     * @param {string} [opt.inputsStyle='normal'] Optional, can be 'normal', 
'compact'.
+     *  Can be either `inputsStyle` or `buttonsStyle`.
+     * @param {number} [opt.inputsHeight] Default not fix height. If 
specified, a scroll
+     *  bar will be displayed if overflow the height. In visual test, once a 
height changed
+     *  by adding something, the subsequent position will be changed, leading 
to test failures.
+     *  Fixing the height helps avoid this.
+     *  Can be either `inputsHeight` or `buttonsHeight`.
+     * @param {Array.<Object>|Object|Function} [opt.inputs]
+     *  They are the same: `opt.buttons` `opt.button`, `opt.inputs`, 
`opt.input`
      *  It can be a function that return buttons configuration, like:
-     *      buttons: chart => { return [{text: 'xxx', onclick: fn}, ...]; }
+     *      inputs: chart => { return [{text: 'xxx', onclick: fn}, ...]; }
      *  Item can be these types:
      *  [{
-     *      // A button(default).
+     *      // 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',
+     *      // A range slider (HTML <input type="range">).
+     *      type: 'range', // Or 'slider'
+     *      id: 'some_id', // Optional. Can be used in `getState` and 
`setState`.
+     *      stateGroup: 'some_state_group', // Optional. Can be used in 
`getState` and `setState`.
      *      text: 'xxx', // Optional
      *      min: 0, // Optional
      *      max: 100, // Optional
@@ -96,13 +106,30 @@
      *  }, {
      *      // 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: []`.
+     *      id: 'some_id', // Optional. Can be used in `getState` and 
`setState`.
+     *      stateGroup: 'some_state_group', // Optional. Can be used in 
`getState` and `setState`.
+     *      // Either `values` or `options` can be used.
+     *      // Items in `values` or `options[i].value` can be any type, like 
`true`, `123`, etc.
+     *      values: ['a', 'b', 'c'],
+     *      options: [
+     *          {text: 'a', value: 123},
+     *          {value: {some: {some: 456}}}, // `text` can be omitted and 
auto generated by `value`.
+     *          {text: 'c', input: ...}, // `input` can be used as shown below.
+     *          ...
+     *      ],
+     *      // `options[i]` can nest other input type, currently only support 
`type: range`:
+     *      options: [
+     *          {value: undefined},
+     *          {text: 'c', input: {
+     *              type: 'range',
+     *              // ... Other properties of `range` input except `onchange` 
and `text`.
+     *              // When this option is not selected, the range input will 
be disabled.
+     *          }}
+     *      ],
      *      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.
+     *      value: 'cval', // 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`
@@ -116,6 +143,14 @@
      *  },
      *  // ...
      *  ]
+     *  The value of the inputs can be update by:
+     *      chart.__testHelper.setState({'some_id_1': 'value1', 'some_id_2': 
'value2'});
+     *      // Only set state from input that has 'some_state_group_1'.
+     *      chart.__testHelper.setState({...}, 'some_state_group_1');
+     *      // Get: {some_id_1: 'value1', some_id_2: 'value2'}
+     *      chart.__testHelper.getState();
+     *      // Only get state from input that has 'some_state_group_1'.
+     *      chart.__testHelper.getState('some_state_group_1');
      * @param {boolean} [opt.recordCanvas] 'test/lib/canteen.js' is required.
      * @param {boolean} [opt.recordVideo]
      * @param {string} [opt.renderer] 'canvas' or 'svg'
@@ -132,7 +167,7 @@
         var title = document.createElement('div');
         var left = document.createElement('div');
         var chartContainer = document.createElement('div');
-        var buttonsContainer = document.createElement('div');
+        var inputsContainer = document.createElement('div');
         var dataTableContainer = document.createElement('div');
         var infoContainer = document.createElement('div');
         var recordCanvasContainer = document.createElement('div');
@@ -140,16 +175,31 @@
 
         title.setAttribute('title', dom.getAttribute('id'));
 
+        var inputsHeight = testHelper.retrieveValue(opt.inputsHeight, 
opt.buttonsHeight, null);
+        if (inputsHeight != null) {
+            inputsHeight = parseFloat(inputsHeight);
+        }
+
         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';
         recordVideoContainer.className = 'record-video';
 
+        inputsContainer.className = [
+            'test-inputs',
+            'test-inputs-style-' + (opt.inputsStyle || opt.buttonsStyle || 
'normal'),
+            (inputsHeight != null ? 'test-inputs-fix-height' : '')
+        ].join(' ');
+        if (inputsHeight != null) {
+            inputsContainer.style.cssText = [
+                'height:' + inputsHeight + 'px',
+            ].join(';') + ';';
+        }
+
         if (opt.info) {
             dom.className += ' test-chart-block-has-right';
             infoContainer.className += ' test-chart-block-right';
@@ -157,7 +207,7 @@
 
         left.appendChild(recordCanvasContainer);
         left.appendChild(recordVideoContainer);
-        left.appendChild(buttonsContainer);
+        left.appendChild(inputsContainer);
         left.appendChild(dataTableContainer);
         left.appendChild(chartContainer);
         dom.appendChild(infoContainer);
@@ -192,7 +242,10 @@
             dataTableContainer.innerHTML = tableHTML.join('');
         }
 
-        chart && initButtons(chart, opt, buttonsContainer, errMsgPrefix)
+        var inputsResult;
+        if (chart) {
+            inputsResult = initInputs(chart, opt, inputsContainer, 
errMsgPrefix);
+        }
 
         if (opt.info) {
             updateInfo(opt.info, opt.infoKey);
@@ -209,13 +262,15 @@
         }
 
         chart.__testHelper = {
-            updateInfo: updateInfo
+            updateInfo: updateInfo,
+            setState: inputsResult && inputsResult.setState,
+            getState: inputsResult && inputsResult.getState
         };
 
         return chart;
     };
 
-    function initButtons(chart, opt, buttonsContainer, errMsgPrefix) {
+    function initInputs(chart, opt, inputsContainer, errMsgPrefix) {
         var NAMES_ON_INPUT_CHANGE = makeFlexibleNames([
             'input', 'on-input', 'on-change', 'select', 'on-select'
         ]);
@@ -229,73 +284,152 @@
             'br', 'line-break', 'break', 'wrap', 'new-line', 'end-of-line',
             'carriage-return', 'line-feed', 'line-separator', 'next-line'
         ]);
+        // key: id, value: {setState, getState}
+        var _inputsDict = {};
+
+        init();
+
+        return {setState: setState, getState: getState};
 
-        var buttons = testHelper.retrieveValue(opt.buttons, opt.button, 
opt.input, opt.inputs);
-        if (typeof buttons === 'function') {
-            buttons = buttons(chart);
+
+        function init() {
+            var inputsDefineList = retrieveInputDefineList();
+
+            for (var i = 0; i < inputsDefineList.length; i++) {
+                var singleCreated = createInputByDefine(inputsDefineList[i]);
+                if (!singleCreated) {
+                    continue;
+                }
+                for (var j = 0; j < singleCreated.elList.length; j++) {
+                    inputsContainer.appendChild(singleCreated.elList[j]);
+                }
+                var id = retrieveId(inputsDefineList[i], 'id');
+                var stateGroup = retrieveId(inputsDefineList[i], 'stateGroup');
+                if (stateGroup != null) {
+                    if (id == null) {
+                        id = generateId('test_inputs_');
+                    }
+                    singleCreated.stateGroup = stateGroup;
+                }
+                if (id != null) {
+                    if (_inputsDict[singleCreated.id]) {
+                        throw new Error(errMsgPrefix + 'Duplicate input id: ' 
+ singleCreated.id);
+                    }
+                    singleCreated.id = id;
+                    _inputsDict[singleCreated.id] = singleCreated;
+                }
+            }
         }
-        if (!(buttons instanceof Array)) {
-            buttons = buttons ? [buttons] : [];
+
+        function setState(state, stateGroup) {
+            for (var id in state) {
+                if (state.hasOwnProperty(id)) {
+                    if (_inputsDict[id] == null) {
+                        throw new Error(errMsgPrefix + 'No input with id: ' + 
id);
+                    }
+                    if (!stateGroup || _inputsDict[id].stateGroup === 
stateGroup) {
+                        _inputsDict[id].setState(state[id]);
+                    }
+                }
+            }
         }
-        if (!buttons.length) {
-            return;
+
+        function getState(stateGroup) {
+            var result = {};
+            for (var id in _inputsDict) {
+                if (_inputsDict.hasOwnProperty(id)
+                    && (!stateGroup || _inputsDict[id].stateGroup === 
stateGroup)
+                ) {
+                    result[id] = _inputsDict[id].getState();
+                }
+            }
+            return result;
         }
 
-        for (var i = 0; i < buttons.length; i++) {
-            processBtnDefine(buttons[i]);
+        function retrieveInputDefineList() {
+            var defineList = testHelper.retrieveValue(opt.buttons, opt.button, 
opt.input, opt.inputs);
+            if (typeof defineList === 'function') {
+                defineList = defineList(chart);
+            }
+            if (!(defineList instanceof Array)) {
+                defineList = defineList ? [defineList] : [];
+            }
+            return defineList;
         }
 
-        function getBtnTextHTML(btnDefine, defaultText) {
-            return 
testHelper.encodeHTML(testHelper.retrieveValue(btnDefine.name, btnDefine.text, 
defaultText));
+        function getBtnTextHTML(inputDefine, defaultText) {
+            return 
testHelper.encodeHTML(testHelper.retrieveValue(inputDefine.name, 
inputDefine.text, defaultText));
         }
-        function getBtnDefineAttr(btnDefine, attr, defaultValue) {
-            return btnDefine[attr] != null ? btnDefine[attr] : defaultValue;
+        function getBtnDefineAttr(inputDefine, attr, defaultValue) {
+            return inputDefine[attr] != null ? inputDefine[attr] : 
defaultValue;
         }
-        function getBtnEventListener(btnDefine, names) {
+        function getBtnEventListener(inputDefine, names) {
             for (var idx = 0; idx < names.length; idx++) {
-                if (btnDefine[names[idx]]) {
-                    return btnDefine[names[idx]];
+                if (inputDefine[names[idx]]) {
+                    return inputDefine[names[idx]];
+                }
+            }
+        }
+
+        function retrieveId(inputDefine, idPropName) {
+            if (inputDefine && inputDefine[idPropName] != null) {
+                var type = getType(inputDefine[idPropName]);
+                if (type !== 'string' && type != 'number') {
+                    throw new Error(errMsgPrefix + 'id must be string or 
number.');
                 }
+                return inputDefine[idPropName] + '';
             }
         }
 
-        function processBtnDefine(btnDefine) {
-            if (!btnDefine) {
+        function createInputByDefine(inputDefine) {
+            if (!inputDefine) {
                 return;
             }
-            var inputType = btnDefine.hasOwnProperty('type') ? btnDefine.type 
: 'button';
+            var inputType = inputDefine.hasOwnProperty('type') ? 
inputDefine.type : 'button';
 
             if (arrayIndexOf(NAMES_TYPE_RANGE, inputType) >= 0) {
-                createRangeInput(btnDefine, buttonsContainer);
+                var rangeInputCreated = createRangeInput(inputDefine);
+                return {
+                    elList: [rangeInputCreated.el],
+                    getState: rangeInputCreated.getState,
+                    setState: rangeInputCreated.setState
+                };
             }
             else if (arrayIndexOf(NAMES_TYPE_SELECT, inputType) >= 0) {
-                createSelectInput(btnDefine, buttonsContainer);
+                return createSelectInput(inputDefine);
             }
             else if (arrayIndexOf(NAMES_TYPE_BR, inputType) >= 0) {
-                createBr(btnDefine, buttonsContainer);
+                return {
+                    elList: [createBr(inputDefine)]
+                };
             }
             else if (arrayIndexOf(NAMES_TYPE_BUTTON, inputType) >= 0) {
-                createButtonInput(btnDefine, buttonsContainer);
+                return {
+                    elList: [createButtonInput(inputDefine)]
+                };
             }
             else {
                 throw new Error(errMsgPrefix + 'Unsupported button type: ' + 
inputType);
             }
         }
 
-        function createRangeInput(btnDefine, buttonsContainer) {
+        function createRangeInput(inputDefine, internallyForceDef) {
             var sliderWrapperEl = document.createElement('span');
-            sliderWrapperEl.className = 'test-buttons-slider';
-            buttonsContainer.appendChild(sliderWrapperEl);
+            resetWrapperCSS(false);
 
             var sliderTextEl = document.createElement('span');
-            sliderTextEl.className = 'test-buttons-slider-text';
-            sliderTextEl.innerHTML = getBtnTextHTML(btnDefine, '');
+            sliderTextEl.className = 'test-inputs-slider-text';
+            sliderTextEl.innerHTML = internallyForceDef
+                ? getBtnTextHTML(internallyForceDef, '')
+                : getBtnTextHTML(inputDefine, '');
             sliderWrapperEl.appendChild(sliderTextEl);
 
             var sliderInputEl = document.createElement('input');
-            sliderInputEl.className = 'test-buttons-slider-input';
+            sliderInputEl.className = 'test-inputs-slider-input';
             sliderInputEl.setAttribute('type', 'range');
-            var sliderListener = getBtnEventListener(btnDefine, 
NAMES_ON_INPUT_CHANGE);
+            var sliderListener = internallyForceDef
+                ? getBtnEventListener(internallyForceDef, 
NAMES_ON_INPUT_CHANGE)
+                : getBtnEventListener(inputDefine, NAMES_ON_INPUT_CHANGE);
             if (!sliderListener) {
                 throw new Error(errMsgPrefix + 'No listener (either ' + 
NAMES_ON_INPUT_CHANGE.join(', ') + ') specified for slider.');
             }
@@ -304,105 +438,271 @@
                 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));
+            sliderInputEl.setAttribute('min', getBtnDefineAttr(inputDefine, 
'min', 0));
+            sliderInputEl.setAttribute('max', getBtnDefineAttr(inputDefine, 
'max', 100));
+            sliderInputEl.setAttribute('value', getBtnDefineAttr(inputDefine, 
'value', 30));
+            sliderInputEl.setAttribute('step', getBtnDefineAttr(inputDefine, 
'step', 1));
             sliderWrapperEl.appendChild(sliderInputEl);
 
             var sliderValueEl = document.createElement('span');
-            sliderValueEl.className = 'test-buttons-slider-value';
+            sliderValueEl.className = 'test-inputs-slider-value';
             function updateSliderValueEl() {
                 var val = sliderInputEl.value;
+                updateText(val);
+            }
+            function updateText(val) {
                 sliderValueEl.innerHTML = testHelper.encodeHTML(val);
             }
             updateSliderValueEl();
             sliderWrapperEl.appendChild(sliderValueEl);
+
+            function resetWrapperCSS(disabled) {
+                sliderWrapperEl.className = 'test-inputs-slider'
+                    + (internallyForceDef ? ' test-inputs-slider-sub' : '')
+                    + (disabled ? ' test-inputs-slider-disabled' : '');
+            }
+
+            return {
+                el: sliderWrapperEl,
+                setState: function (state) {
+                    if (state == null || !isFinite(+state.value)) {
+                        throw new Error(errMsgPrefix + 'Invalid state: ' + 
printObject(state) + ' for range');
+                    }
+                    sliderInputEl.value = state.value;
+                    updateText(state.value);
+                },
+                getState: function () {
+                    return {value: +sliderInputEl.value};
+                },
+                disable: function (disabled) {
+                    sliderInputEl.disabled = disabled;
+                    resetWrapperCSS(disabled);
+                }
+            };
         }
 
-        function createSelectInput(btnDefine, buttonsContainer) {
-            var selectWrapperEl = document.createElement('span');
-            selectWrapperEl.className = 'test-buttons-select';
-            buttonsContainer.appendChild(selectWrapperEl);
+        function createSelectInput(inputDefine) {
+            var selectCtx = {
+                _optionList: [],
+                _selectEl: null,
+                _optionIdxToSubInput: [],
+                _elList: []
+            };
+
+            createElementsForSelect();
+
+            var _selectListener = getBtnEventListener(inputDefine, 
NAMES_ON_INPUT_CHANGE);
+            if (!_selectListener) {
+                throw new Error(errMsgPrefix + 'No listener (either ' + 
NAMES_ON_INPUT_CHANGE.join(', ') + ') specified for select.');
+            }
+
+            initOptionsForSelect(inputDefine);
+
+            selectCtx._selectEl.addEventListener('change', function () {
+                var optionIdx = getOptionIndex(selectCtx._selectEl);
+                disableSubInputs(optionIdx);
+                handleSelectChange(getValueByOptionIndex(optionIdx));
+            });
+
+            setInitValue(inputDefine);
+
+            return {
+                elList: selectCtx._elList,
+                getState: getStateForSelect,
+                setState: setStateForSelect
+            };
 
-            var textEl = document.createElement('span');
-            textEl.className = 'test-buttons-select-text';
-            textEl.innerHTML = getBtnTextHTML(btnDefine, '');
-            selectWrapperEl.appendChild(textEl);
+            function createElementsForSelect() {
+                var selectWrapperEl = document.createElement('span');
+                selectWrapperEl.className = 'test-inputs-select';
 
-            var selectEl = document.createElement('select');
-            selectEl.className = 'test-buttons-select-input';
-            selectWrapperEl.appendChild(selectEl);
+                var textEl = document.createElement('span');
+                textEl.className = 'test-inputs-select-text';
+                textEl.innerHTML = getBtnTextHTML(inputDefine, '');
+                selectWrapperEl.appendChild(textEl);
 
-            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.');
+                var selectEl = document.createElement('select');
+                selectEl.className = 'test-inputs-select-select';
+                selectWrapperEl.appendChild(selectEl);
+
+                selectCtx._elList.push(selectWrapperEl);
+                selectCtx._selectEl = selectEl;
             }
-            for (var idx = 0; idx < optionDefList.length; idx++) {
-                var optionDef = optionDefList[idx];
+
+            function initOptionsForSelect(inputDefine) {
                 // 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};
+                if (inputDefine.options) {
+                    for (var optionIdx = 0; optionIdx < 
inputDefine.options.length; optionIdx++) {
+                        var optionDef = inputDefine.options[optionIdx];
+                        if (
+                            !isObject(optionDef)
+                            || (
+                                !optionDef.hasOwnProperty('value')
+                                && !isObject(optionDef.input)
+                            )
+                        ) {
+                            throw new Error(
+                                'Can only be {type: "select", options: {value: 
any, text?: string, input?: SubInput}[]}'
+                            );
+                        }
+                        var text = getType(optionDef.text) === 'string'
+                            ? optionDef.text
+                            : makeTextByValue(optionDef);
+                        selectCtx._optionList.push({
+                            value: optionDef.value,
+                            input: optionDef.input,
+                            text: text
+                        });
+                    }
                 }
-                else {
-                    optionDef = {
-                        text: printObject(optionDef, {
-                            arrayLineBreak: false, objectLineBreak: false, 
indent: 0, lineBreak: ''
-                        }),
-                        value: optionDef
-                    };
+                else if (inputDefine.values) {
+                    for (var optionIdx = 0; optionIdx < 
inputDefine.values.length; optionIdx++) {
+                        var value = inputDefine.values[optionIdx];
+                        selectCtx._optionList.push({
+                            value: value,
+                            text: makeTextByValue({value: value})
+                        });
+                    }
                 }
-                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);
+                if (!selectCtx._optionList.length) {
+                    throw new Error(errMsgPrefix + 'No options specified for 
select.');
                 }
-                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;
+
+                for (var optionIdx = 0; optionIdx < 
selectCtx._optionList.length; optionIdx++) {
+                    var optionDef = selectCtx._optionList[optionIdx];
+                    selectCtx._optionList[optionIdx] = 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.
+                    //  (The input raw value can be 
null/undefined/array/object/... everything).
+                    optionEl.value = optionIdx;
+                    selectCtx._selectEl.appendChild(optionEl);
+
+                    if (optionDef.input) {
+                        if (arrayIndexOf(NAMES_TYPE_RANGE, 
optionDef.input.type) < 0) {
+                            throw new Error(errMsgPrefix + 'Sub input only 
supported for range input.');
+                        }
+                        var createdRangeInput = 
createRangeInput(optionDef.input, {
+                            text: '',
+                            onchange: function () {
+                                handleSelectChange(this.value)
+                            }
+                        });
+                        selectCtx._elList.push(createdRangeInput.el);
+                        selectCtx._optionIdxToSubInput[optionIdx] = 
createdRangeInput;
                     }
                 }
-                if (!found) {
-                    throw new Error(errMsgPrefix + 'Value not found in select 
options: ' + btnDefine.value);
+            }
+
+            function getStateForSelect() {
+                var subInputState = {};
+                for (var optionIdx = 0; optionIdx < 
selectCtx._optionIdxToSubInput.length; optionIdx++) {
+                    if (selectCtx._optionIdxToSubInput[optionIdx]) {
+                        subInputState[optionIdx] = 
selectCtx._optionIdxToSubInput[optionIdx].getState();
+                    }
                 }
+                return {
+                    valueIndex: getOptionIndex(selectCtx._selectEl),
+                    subInputState: subInputState
+                };
             }
-            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.');
+
+            function setStateForSelect(state) {
+                if (state == null
+                    || getType(state.valueIndex) !== 'number'
+                    || !isObject(state.subInputState)
+                ) {
+                    throw new Error(errMsgPrefix + 'Invalid state: ' + 
printObject(state) + ' for select');
+                }
+                resetOptionIndex(state.valueIndex);
+                for (var optionIdx in state.subInputState) {
+                    if (state.subInputState.hasOwnProperty(optionIdx)) {
+                        var subInput = 
selectCtx._optionIdxToSubInput[optionIdx];
+                        if (subInput) {
+                            subInput.setState(state.subInputState[optionIdx]);
+                        }
+                    }
+                }
+            }
+
+            function setInitValue(inputDefine) {
+                var initOptionIdx = 0;
+                if (inputDefine.hasOwnProperty('valueIndex')) {
+                    var valueIndex = inputDefine.valueIndex;
+                    if (valueIndex < 0 || valueIndex >= 
selectCtx._optionList.length) {
+                        throw new Error(errMsgPrefix + 'Invalid valueIndex: ' 
+ valueIndex);
+                    }
+                    selectCtx._selectEl.value = 
selectCtx._optionList[valueIndex].value;
+                    initOptionIdx = valueIndex;
+                }
+                else if (inputDefine.hasOwnProperty('value')) {
+                    var found = false;
+                    for (var idx = 0; idx < selectCtx._optionList.length; 
idx++) {
+                        if (!selectCtx._optionList[idx].input && 
selectCtx._optionList[idx].value === inputDefine.value) {
+                            found = true;
+                            initOptionIdx = idx;
+                        }
+                    }
+                    if (!found) {
+                        throw new Error(errMsgPrefix + 'Value not found in 
select options: ' + inputDefine.value);
+                    }
+                }
+                resetOptionIndex(initOptionIdx);
+            }
+
+            function resetOptionIndex(optionIdx) {
+                disableSubInputs(optionIdx);
+                selectCtx._selectEl.value = optionIdx;
+            }
+
+            function getOptionIndex(optionIndexHost) {
+                return +optionIndexHost.value;
+            }
+
+            function getValueByOptionIndex(optionIdx) {
+                return selectCtx._optionList[optionIdx].input
+                    ? 
selectCtx._optionIdxToSubInput[optionIdx].getState().value
+                    : selectCtx._optionList[optionIdx].value;
+            }
+
+            function handleSelectChange(value) {
+                var target = {value: value};
+                _selectListener.call(target, {target: target});
+            }
+
+            function disableSubInputs(currOptionIdx) {
+                for (var i = 0; i < selectCtx._optionIdxToSubInput.length; 
i++) {
+                    var subInput = selectCtx._optionIdxToSubInput[i];
+                    if (subInput) {
+                        subInput.disable(i !== currOptionIdx);
+                    }
+                }
+            }
+
+            function makeTextByValue(optionDef) {
+                if (optionDef.hasOwnProperty('value')) {
+                    return printObject(optionDef.value, {
+                        arrayLineBreak: false, objectLineBreak: false, indent: 
0, lineBreak: ''
+                    });
+                }
+                else if (optionDef.input) {
+                    return 'range input';
+                }
             }
-            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 createBr(inputDefine) {
+            return document.createElement('br');
         }
 
-        function createButtonInput(btnDefine, buttonsContainer) {
+        function createButtonInput(inputDefine) {
             var btn = document.createElement('button');
-            btn.innerHTML = getBtnTextHTML(btnDefine, 'button');
-            btn.addEventListener('click', getBtnEventListener(btnDefine, 
NAMES_ON_CLICK));
-            buttonsContainer.appendChild(btn);
+            btn.innerHTML = getBtnTextHTML(inputDefine, 'button');
+            btn.addEventListener('click', getBtnEventListener(inputDefine, 
NAMES_ON_CLICK));
+            return btn;
         }
     }
 
@@ -1325,6 +1625,23 @@
         return names;
     }
 
+    function objectNoOtherNotNullUndefinedPropExcept(obj, exceptProps) {
+        if (!obj) {
+            return false;
+        }
+        for (var key in obj) {
+            if (obj.hasOwnProperty(key) && arrayIndexOf(exceptProps, key) < 0 
&& obj[key] != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    var _idBase = 1;
+    function generateId(prefix) {
+        return prefix + '' + (_idBase++);
+    }
+
     function VideoRecorder(chart) {
         this.start = startRecording;
         this.stop = stopRecording;
diff --git a/test/runTest/cli.js b/test/runTest/cli.js
index 0448f6821..cd9360ce3 100644
--- a/test/runTest/cli.js
+++ b/test/runTest/cli.js
@@ -413,7 +413,7 @@ async function runTest(browser, testOpt, runtimeCode, 
expectedSource, expectedVe
     }
     else {
         // Only run once
-        await runTestPage(browser, testOpt, 'local', runtimeCode, true);
+        await runTestPage(browser, testOpt, expectedSource, 'local', 
runtimeCode, true);
     }
 }
 
diff --git a/test/runTest/recorder/recorder.js 
b/test/runTest/recorder/recorder.js
index 66f18dee7..e4289563e 100644
--- a/test/runTest/recorder/recorder.js
+++ b/test/runTest/recorder/recorder.js
@@ -40,7 +40,7 @@ function getUniqueSelector(el) {
     let selector = '';
     if (el.id) {
         // id has highest priority.
-        return el.id;
+        return '#' + el.id;
     }
     else {
         selector = el.tagName.toLowerCase();


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


Reply via email to