From 7a2c9cc7afcc709f96cba7b90b397d09b56dbe43 Mon Sep 17 00:00:00 2001 From: youluna Date: Wed, 27 Feb 2019 01:25:17 +0800 Subject: [PATCH] fix(Tree): throw error when children contain null or string --- src/tree-select/tree-select.jsx | 202 +++++++----- src/tree/view/tree.jsx | 260 ++++++++++----- test/tree/a11y-spec.js | 81 +++-- test/tree/index-spec.js | 538 +++++++++++++++++++++++--------- 4 files changed, 751 insertions(+), 330 deletions(-) diff --git a/src/tree-select/tree-select.jsx b/src/tree-select/tree-select.jsx index aadbec911d..3cbfe16423 100644 --- a/src/tree-select/tree-select.jsx +++ b/src/tree-select/tree-select.jsx @@ -1,4 +1,9 @@ -import React, { Component, Children, isValidElement, cloneElement } from 'react'; +import React, { + Component, + Children, + isValidElement, + cloneElement, +} from 'react'; import PropTypes from 'prop-types'; import Select from '../select'; import Tree from '../tree'; @@ -7,7 +12,7 @@ import { getAllCheckedKeys, filterChildKey, filterParentKey, - isDescendantOrSelf + isDescendantOrSelf, } from '../tree/view/util'; import { func, obj, KEYCODE } from '../util'; @@ -71,11 +76,17 @@ export default class TreeSelect extends Component { /** * (受控)当前值 */ - value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), + value: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string), + ]), /** * (非受控)默认值 */ - defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), + defaultValue: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.string), + ]), /** * 选中值改变时触发的回调函数 * @param {String|Array} value 选中的值,单选时返回单个值,多选时返回数组 @@ -159,7 +170,7 @@ export default class TreeSelect extends Component { /** * 透传到 Popup 的属性对象 */ - popupProps: PropTypes.object + popupProps: PropTypes.object, }; static defaultProps = { @@ -185,7 +196,7 @@ export default class TreeSelect extends Component { treeDefaultExpandedKeys: [], treeProps: {}, defaultVisible: false, - onVisibleChange: noop + onVisibleChange: noop, }; constructor(props, context) { @@ -194,10 +205,12 @@ export default class TreeSelect extends Component { const { defaultVisible, visible, defaultValue, value } = props; this.state = { visible: typeof visible === 'undefined' ? defaultVisible : visible, - value: normalizeToArray(typeof value === 'undefined' ? defaultValue : value), + value: normalizeToArray( + typeof value === 'undefined' ? defaultValue : value + ), searchedValue: '', expandedKeys: [], - autoExpandParent: false + autoExpandParent: false, }; bindCtx(this, [ @@ -211,7 +224,7 @@ export default class TreeSelect extends Component { 'handleExpand', 'handleKeyDown', 'saveTreeRef', - 'saveSelectRef' + 'saveSelectRef', ]); this.updateCache(props); @@ -239,32 +252,42 @@ export default class TreeSelect extends Component { this._v2n = {}; if ('dataSource' in props) { - const loop = (data, prefix = '0') => data.map((item, index) => { - const { value, children } = item; - const pos = `${prefix}-${index}`; - const key = item.key || pos; - const newItem = { ...item, key, pos }; - if (children && children.length) { - newItem.children = loop(children, pos); - } + const loop = (data, prefix = '0') => + data.map((item, index) => { + const { value, children } = item; + const pos = `${prefix}-${index}`; + const key = item.key || pos; + const newItem = { ...item, key, pos }; + if (children && children.length) { + newItem.children = loop(children, pos); + } - this._k2n[key] = this._p2n[pos] = this._v2n[value] = newItem; - return newItem; - }); + this._k2n[key] = this._p2n[pos] = this._v2n[ + value + ] = newItem; + return newItem; + }); loop(props.dataSource); } else if ('children' in props) { - const loop = (children, prefix = '0') => Children.map(children, (node, index) => { - const { value, children } = node.props; - const pos = `${prefix}-${index}`; - const key = node.key || pos; - const newItem = { ...node.props, key, pos }; - if (children && Children.count(children)) { - newItem.children = loop(children, pos); - } + const loop = (children, prefix = '0') => + Children.map(children, (node, index) => { + if (!React.isValidElement(node)) { + return; + } - this._k2n[key] = this._p2n[pos] = this._v2n[value] = newItem; - return newItem; - }); + const { value, children } = node.props; + const pos = `${prefix}-${index}`; + const key = node.key || pos; + const newItem = { ...node.props, key, pos }; + if (children && Children.count(children)) { + newItem.children = loop(children, pos); + } + + this._k2n[key] = this._p2n[pos] = this._v2n[ + value + ] = newItem; + return newItem; + }); loop(props.children); } } @@ -312,7 +335,7 @@ export default class TreeSelect extends Component { const d = { value: v, label, - pos + pos, }; if (forSelect) { d.disabled = disabled || checkboxDisabled; @@ -337,7 +360,7 @@ export default class TreeSelect extends Component { handleVisibleChange(visible, type) { if (!('visible' in this.props)) { this.setState({ - visible + visible, }); } @@ -356,7 +379,7 @@ export default class TreeSelect extends Component { const value = this.getValueByKeys(selectedKeys); if (!('value' in this.props)) { this.setState({ - value + value, }); } if (!multiple) { @@ -364,9 +387,7 @@ export default class TreeSelect extends Component { } const data = this.getData(value); - multiple ? - onChange(value, data) : - onChange(value[0], data[0]); + multiple ? onChange(value, data) : onChange(value[0], data[0]); } else { this.handleVisibleChange(false, 'fromTree'); } @@ -378,7 +399,7 @@ export default class TreeSelect extends Component { const value = this.getValueByKeys(checkedKeys); if (!('value' in this.props)) { this.setState({ - value + value, }); } @@ -387,10 +408,19 @@ export default class TreeSelect extends Component { handleRemove(removedItem) { const { value: removedValue } = removedItem; - const { treeCheckable, treeCheckStrictly, treeCheckedStrategy, onChange } = this.props; + const { + treeCheckable, + treeCheckStrictly, + treeCheckedStrategy, + onChange, + } = this.props; let value; - if (treeCheckable && !treeCheckStrictly && treeCheckedStrategy === 'all') { + if ( + treeCheckable && + !treeCheckStrictly && + treeCheckedStrategy === 'all' + ) { const removedPos = this._v2n[removedValue].pos; value = this.state.value.filter(v => { const p = this._v2n[v].pos; @@ -412,10 +442,9 @@ export default class TreeSelect extends Component { value = this.state.value.filter(v => v !== removedValue); } - if (!('value' in this.props)) { this.setState({ - value + value, }); } @@ -446,7 +475,7 @@ export default class TreeSelect extends Component { this.setState({ searchedValue, expandedKeys: searchedKeys, - autoExpandParent: true + autoExpandParent: true, }); this.searchedKeys = searchedKeys; this.retainedKeys = retainedKeys; @@ -457,7 +486,7 @@ export default class TreeSelect extends Component { handleSearchClear(triggerType) { this.setState({ searchedValue: '', - expandedKeys: [] + expandedKeys: [], }); this.props.onSearchClear(triggerType); } @@ -465,7 +494,7 @@ export default class TreeSelect extends Component { handleExpand(expandedKeys) { this.setState({ expandedKeys, - autoExpandParent: false + autoExpandParent: false, }); } @@ -487,7 +516,8 @@ export default class TreeSelect extends Component { this.tree.setFocusKey(); e.preventDefault(); break; - default: break; + default: + break; } } @@ -497,7 +527,7 @@ export default class TreeSelect extends Component { if (hasClear && (!multiple || !treeCheckable)) { if (!('value' in this.props)) { this.setState({ - value: [] + value: [], }); } @@ -516,8 +546,10 @@ export default class TreeSelect extends Component { }; loop(label); - if (labelString.length >= searchedValue.length && - labelString.indexOf(searchedValue) > -1) { + if ( + labelString.length >= searchedValue.length && + labelString.indexOf(searchedValue) > -1 + ) { return true; } @@ -531,12 +563,14 @@ export default class TreeSelect extends Component { if (this.searchedKeys.indexOf(child.key) > -1) { retainedNodes.push(child); } else if (this.retainedKeys.indexOf(child.key) > -1) { - const retainedNode = child.props.children ? - cloneElement(child, {}, loop(child.props.children)) : - child; + const retainedNode = child.props.children + ? cloneElement(child, {}, loop(child.props.children)) + : child; retainedNodes.push(retainedNode); } else { - const hideNode = cloneElement(child, { style: { display: 'none' } }); + const hideNode = cloneElement(child, { + style: { display: 'none' }, + }); retainedNodes.push(hideNode); } }); @@ -558,15 +592,20 @@ export default class TreeSelect extends Component { if (hide) { others.style = { display: 'none' }; } - retainedNodes.push(( + retainedNodes.push( - {children && children.length ? loop(children, isParentMatched, pos) : null} + {children && children.length + ? loop(children, isParentMatched, pos) + : null} - )); + ); }; if (searching) { - if (this.searchedKeys.indexOf(key) > -1 || isParentMatched) { + if ( + this.searchedKeys.indexOf(key) > -1 || + isParentMatched + ) { addNode(true); } else if (this.retainedKeys.indexOf(key) > -1) { addNode(false); @@ -605,13 +644,13 @@ export default class TreeSelect extends Component { dataSource, children, readOnly, - notFoundContent + notFoundContent, } = this.props; const { value, searchedValue, expandedKeys, - autoExpandParent + autoExpandParent, } = this.state; const treeProps = { @@ -619,14 +658,16 @@ export default class TreeSelect extends Component { ref: this.saveTreeRef, loadData: treeLoadData, defaultExpandAll: treeDefaultExpandAll, - defaultExpandedKeys: treeDefaultExpandedKeys + defaultExpandedKeys: treeDefaultExpandedKeys, }; const keys = this.getKeysByValue(value); if (treeCheckable) { treeProps.checkable = treeCheckable; treeProps.checkStrictly = treeCheckStrictly; - treeProps.checkedStrategy = treeCheckStrictly ? 'all' : treeCheckedStrategy; + treeProps.checkedStrategy = treeCheckStrictly + ? 'all' + : treeCheckedStrategy; treeProps.checkedKeys = keys; if (!readOnly) { treeProps.onCheck = this.handleCheck; @@ -649,9 +690,9 @@ export default class TreeSelect extends Component { }; if (this.searchedKeys.length) { - newChildren = dataSource ? - this.createNodesByData(dataSource, true) : - this.searchNodes(children); + newChildren = dataSource + ? this.createNodesByData(dataSource, true) + : this.searchNodes(children); } else { notFound = true; } @@ -675,11 +716,15 @@ export default class TreeSelect extends Component { return (
- {notFound ? -
{notFoundContent}
: + {notFound ? ( +
+ {notFoundContent} +
+ ) : ( {newChildren} - } + + )}
); } @@ -704,22 +749,26 @@ export default class TreeSelect extends Component { treeCheckStrictly, className, popupContainer, - popupProps + popupProps, } = this.props; - const others = pickOthers(Object.keys(TreeSelect.propTypes), this.props); - const { - value, - visible - } = this.state; + const others = pickOthers( + Object.keys(TreeSelect.propTypes), + this.props + ); + const { value, visible } = this.state; - const valueForSelect = treeCheckable && !treeCheckStrictly ? this.getValueForSelect(value) : value; + const valueForSelect = + treeCheckable && !treeCheckStrictly + ? this.getValueForSelect(value) + : value; let data = this.getData(valueForSelect, true); if (!multiple && !treeCheckable) { data = data[0]; } return ( -