feat(amis): 增加tabs、table cell的testid (#9541)

This commit is contained in:
yangwei9012 2024-01-29 10:34:47 +08:00 committed by GitHub
parent da01d47b0e
commit 51131b64c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 95 additions and 20 deletions

View File

@ -2325,4 +2325,12 @@ export class TestIdBuilder {
[TEST_ID_KEY]: data ? filter(this.testId, data) : this.testId [TEST_ID_KEY]: data ? filter(this.testId, data) : this.testId
}; };
} }
getTestIdValue(data?: object) {
if (this.testId == null) {
return undefined;
}
return data ? filter(this.testId, data) : this.testId;
}
} }

View File

@ -5,7 +5,13 @@
*/ */
import React from 'react'; import React from 'react';
import {ClassName, localeable, LocaleProps, Schema} from 'amis-core'; import {
ClassName,
localeable,
LocaleProps,
Schema,
TestIdBuilder
} from 'amis-core';
import Transition, {ENTERED, ENTERING} from 'react-transition-group/Transition'; import Transition, {ENTERED, ENTERING} from 'react-transition-group/Transition';
import {themeable, ThemeProps, noop} from 'amis-core'; import {themeable, ThemeProps, noop} from 'amis-core';
import {uncontrollable} from 'amis-core'; import {uncontrollable} from 'amis-core';
@ -59,6 +65,7 @@ export interface TabProps extends ThemeProps {
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
swipeable?: boolean; swipeable?: boolean;
onSelect?: (eventKey: string | number) => void; onSelect?: (eventKey: string | number) => void;
testIdBuilder?: TestIdBuilder;
} }
class TabComponent extends React.PureComponent<TabProps> { class TabComponent extends React.PureComponent<TabProps> {
@ -113,7 +120,8 @@ class TabComponent extends React.PureComponent<TabProps> {
children, children,
className, className,
swipeable, swipeable,
mobileUI mobileUI,
testIdBuilder
} = this.props; } = this.props;
return ( return (
@ -140,6 +148,7 @@ class TabComponent extends React.PureComponent<TabProps> {
onTouchMove={swipeable && mobileUI ? this.onTouchMove : noop} onTouchMove={swipeable && mobileUI ? this.onTouchMove : noop}
onTouchEnd={swipeable && mobileUI ? this.onTouchEnd : noop} onTouchEnd={swipeable && mobileUI ? this.onTouchEnd : noop}
onTouchCancel={swipeable && mobileUI ? this.onTouchEnd : noop} onTouchCancel={swipeable && mobileUI ? this.onTouchEnd : noop}
{...testIdBuilder?.getTestId()}
> >
{children} {children}
</div> </div>
@ -181,6 +190,7 @@ export interface TabsProps extends ThemeProps, LocaleProps {
collapseBtnLabel?: string; collapseBtnLabel?: string;
popOverContainer?: any; popOverContainer?: any;
children?: React.ReactNode | Array<React.ReactNode>; children?: React.ReactNode | Array<React.ReactNode>;
testIdBuilder?: TestIdBuilder;
} }
export interface IDragInfo { export interface IDragInfo {
@ -586,7 +596,8 @@ export class Tabs extends React.Component<TabsProps, any> {
draggable, draggable,
showTip, showTip,
showTipClassName, showTipClassName,
editable editable,
testIdBuilder
} = this.props; } = this.props;
const { const {
@ -646,7 +657,9 @@ export class Tabs extends React.Component<TabsProps, any> {
)} )}
</a> </a>
); );
const tabTestIdBuidr = testIdBuilder?.getChild(
`tab-${typeof title === 'string' ? title : index}`
);
return ( return (
<li <li
className={cx( className={cx(
@ -662,6 +675,7 @@ export class Tabs extends React.Component<TabsProps, any> {
typeof title === 'string' && typeof title === 'string' &&
this.handleStartEdit(index, title); this.handleStartEdit(index, title);
}} }}
{...tabTestIdBuidr?.getChild('link').getTestId()}
> >
{showTip ? ( {showTip ? (
<TooltipWrapper <TooltipWrapper
@ -684,6 +698,7 @@ export class Tabs extends React.Component<TabsProps, any> {
this.props.onClose && this.props.onClose &&
this.props.onClose(index, eventKey ?? index); this.props.onClose(index, eventKey ?? index);
}} }}
{...tabTestIdBuidr?.getChild('close').getTestId()}
> >
<Icon icon="close" className={cx('Tabs-link-close-icon')} /> <Icon icon="close" className={cx('Tabs-link-close-icon')} />
</span> </span>
@ -722,7 +737,7 @@ export class Tabs extends React.Component<TabsProps, any> {
} }
renderArrow(type: 'left' | 'right') { renderArrow(type: 'left' | 'right') {
const {mode: dMode, tabsMode} = this.props; const {mode: dMode, tabsMode, testIdBuilder} = this.props;
const mode = tabsMode || dMode; const mode = tabsMode || dMode;
if (['vertical', 'sidebar'].includes(mode)) { if (['vertical', 'sidebar'].includes(mode)) {
return; return;
@ -738,6 +753,7 @@ export class Tabs extends React.Component<TabsProps, any> {
'Tabs-linksContainer-arrow--' + type, 'Tabs-linksContainer-arrow--' + type,
disabled && 'Tabs-linksContainer-arrow--disabled' disabled && 'Tabs-linksContainer-arrow--disabled'
)} )}
{...testIdBuilder?.getChild(`arrow-${type}`).getTestId()}
> >
<Icon icon="right-arrow-bold" className="icon" /> <Icon icon="right-arrow-bold" className="icon" />
</div> </div>
@ -835,7 +851,8 @@ export class Tabs extends React.Component<TabsProps, any> {
draggable, draggable,
sidePosition, sidePosition,
addBtnText, addBtnText,
mobileUI mobileUI,
testIdBuilder
} = this.props; } = this.props;
const {isOverflow} = this.state; const {isOverflow} = this.state;
@ -851,6 +868,7 @@ export class Tabs extends React.Component<TabsProps, any> {
<div <div
className={cx('Tabs-addable')} className={cx('Tabs-addable')}
onClick={() => this.handleAddBtn()} onClick={() => this.handleAddBtn()}
{...testIdBuilder?.getChild('add-tab').getTestId()}
> >
<Icon icon="plus" className={cx('Tabs-addable-icon')} /> <Icon icon="plus" className={cx('Tabs-addable-icon')} />
{addBtnText} {addBtnText}
@ -871,6 +889,7 @@ export class Tabs extends React.Component<TabsProps, any> {
className className
)} )}
style={style} style={style}
{...testIdBuilder?.getTestId()}
> >
{!['vertical', 'sidebar', 'chrome'].includes(mode) ? ( {!['vertical', 'sidebar', 'chrome'].includes(mode) ? (
<div <div
@ -885,6 +904,7 @@ export class Tabs extends React.Component<TabsProps, any> {
'Tabs-linksContainer', 'Tabs-linksContainer',
isOverflow && 'Tabs-linksContainer--overflow' isOverflow && 'Tabs-linksContainer--overflow'
)} )}
{...testIdBuilder?.getChild('links').getTestId()}
> >
{!mobileUI ? this.renderArrow('left') : null} {!mobileUI ? this.renderArrow('left') : null}
<div className={cx('Tabs-linksContainer-main')}> <div className={cx('Tabs-linksContainer-main')}>
@ -911,6 +931,7 @@ export class Tabs extends React.Component<TabsProps, any> {
'is-mobile': mobileUI 'is-mobile': mobileUI
})} })}
role="tablist" role="tablist"
{...testIdBuilder?.getChild('links').getTestId()}
> >
{this.renderNavs()} {this.renderNavs()}
{additionBtns} {additionBtns}
@ -925,7 +946,11 @@ export class Tabs extends React.Component<TabsProps, any> {
})} })}
</div> </div>
{draggable && ( {draggable && (
<div className={cx('Tabs-drag-tip')} ref={this.dragTipRef} /> <div
className={cx('Tabs-drag-tip')}
ref={this.dragTipRef}
{...testIdBuilder?.getChild('drag').getTestId()}
/>
)} )}
</div> </div>
); );

View File

@ -7,7 +7,8 @@ import {
ThemeProps, ThemeProps,
resolveVariable, resolveVariable,
buildTrackExpression, buildTrackExpression,
evalTrackExpression evalTrackExpression,
TestIdBuilder
} from 'amis-core'; } from 'amis-core';
import {BadgeObject, Checkbox, Icon, Spinner} from 'amis-ui'; import {BadgeObject, Checkbox, Icon, Spinner} from 'amis-ui';
import React from 'react'; import React from 'react';
@ -33,6 +34,7 @@ export interface CellProps extends ThemeProps {
quickEditFormRef: any; quickEditFormRef: any;
onImageEnlarge?: any; onImageEnlarge?: any;
translate: (key: string, ...args: Array<any>) => string; translate: (key: string, ...args: Array<any>) => string;
testIdBuilder: TestIdBuilder;
} }
export default function Cell({ export default function Cell({
@ -53,7 +55,8 @@ export default function Cell({
popOverContainer, popOverContainer,
quickEditFormRef, quickEditFormRef,
onImageEnlarge, onImageEnlarge,
translate: __ translate: __,
testIdBuilder
}: CellProps) { }: CellProps) {
if (column.name && item.rowSpans[column.name] === 0) { if (column.name && item.rowSpans[column.name] === 0) {
return null; return null;
@ -77,6 +80,7 @@ export default function Cell({
<td <td
style={style} style={style}
className={cx(column.pristine.className, stickyClassName)} className={cx(column.pristine.className, stickyClassName)}
{...testIdBuilder.getTestId()}
> >
<Checkbox <Checkbox
classPrefix={ns} classPrefix={ns}
@ -85,6 +89,7 @@ export default function Cell({
checked={item.checked || item.partial} checked={item.checked || item.partial}
disabled={item.checkdisable || !item.checkable} disabled={item.checkdisable || !item.checkable}
onChange={onCheckboxChange} onChange={onCheckboxChange}
testIdBuilder={testIdBuilder.getChild('chekbx')}
/> />
</td> </td>
); );
@ -95,6 +100,7 @@ export default function Cell({
className={cx(column.pristine.className, stickyClassName, { className={cx(column.pristine.className, stickyClassName, {
'is-dragDisabled': !item.draggable 'is-dragDisabled': !item.draggable
})} })}
{...testIdBuilder.getChild('drag').getTestId()}
> >
{item.draggable ? <Icon icon="drag" className="icon" /> : null} {item.draggable ? <Icon icon="drag" className="icon" /> : null}
</td> </td>
@ -111,6 +117,9 @@ export default function Cell({
// data-tooltip="展开/收起" // data-tooltip="展开/收起"
// data-position="top" // data-position="top"
onClick={item.toggleExpanded} onClick={item.toggleExpanded}
{...testIdBuilder
.getChild(item.expanded ? 'fold' : 'expand')
.getTestId()}
> >
<Icon icon="right-arrow-bold" className="icon" /> <Icon icon="right-arrow-bold" className="icon" />
</a> </a>
@ -142,6 +151,7 @@ export default function Cell({
key="retryBtn" key="retryBtn"
onClick={item.resetDefered} onClick={item.resetDefered}
data-tooltip={__('Options.retry', {reason: item.error})} data-tooltip={__('Options.retry', {reason: item.error})}
{...testIdBuilder.getChild('retry').getTestId()}
> >
<Icon icon="retry" className="icon" /> <Icon icon="retry" className="icon" />
</a> </a>
@ -152,6 +162,9 @@ export default function Cell({
// data-tooltip="展开/收起" // data-tooltip="展开/收起"
// data-position="top" // data-position="top"
onClick={item.toggleExpanded} onClick={item.toggleExpanded}
{...testIdBuilder
.getChild(item.expanded ? 'fold' : 'expand')
.getTestId()}
> >
<Icon icon="right-arrow-bold" className="icon" /> <Icon icon="right-arrow-bold" className="icon" />
</a> </a>
@ -174,6 +187,7 @@ export default function Cell({
draggable draggable
onDragStart={onDragStart} onDragStart={onDragStart}
className={cx('Table-dragBtn')} className={cx('Table-dragBtn')}
{...testIdBuilder.getChild('drag').getTestId()}
> >
<Icon icon="drag" className="icon" /> <Icon icon="drag" className="icon" />
</a> </a>
@ -245,7 +259,8 @@ export default function Cell({
{ {
...column.pristine, ...column.pristine,
column: column.pristine, column: column.pristine,
type: 'cell' type: 'cell',
testid: testIdBuilder.getTestIdValue()
}, },
subProps subProps
); );

View File

@ -72,7 +72,8 @@ export class TableBody extends React.Component<TableBodyProps> {
renderRows( renderRows(
rows: Array<any>, rows: Array<any>,
columns = this.props.columns, columns = this.props.columns,
rowProps: any = {} rowProps: any = {},
indexPath?: string
): any { ): any {
const { const {
rowClassName, rowClassName,
@ -99,16 +100,20 @@ export class TableBody extends React.Component<TableBodyProps> {
return rows.map((item: IRow, rowIndex: number) => { return rows.map((item: IRow, rowIndex: number) => {
const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null; const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null;
const rowPath = `${indexPath ? indexPath + '/' : ''}${rowIndex}`;
const rowTestBuidr = testIdBuilder?.getChild(`row-${rowPath}`);
const doms = [ const doms = [
<TableRow <TableRow
{...itemProps} {...itemProps}
testIdBuilder={testIdBuilder?.getChild(`row${rowIndex}`)} testIdBuilder={rowTestBuidr}
store={store} store={store}
itemAction={itemAction} itemAction={itemAction}
classnames={cx} classnames={cx}
checkOnItemClick={checkOnItemClick} checkOnItemClick={checkOnItemClick}
key={item.id} key={item.id}
itemIndex={rowIndex} itemIndex={rowIndex}
rowPath={rowPath}
item={item} item={item}
itemClassName={cx( itemClassName={cx(
rowClassNameExpr rowClassNameExpr
@ -147,6 +152,7 @@ export class TableBody extends React.Component<TableBodyProps> {
checkOnItemClick={checkOnItemClick} checkOnItemClick={checkOnItemClick}
key={`foot-${item.id}`} key={`foot-${item.id}`}
itemIndex={rowIndex} itemIndex={rowIndex}
rowPath={rowPath}
item={item} item={item}
itemClassName={cx( itemClassName={cx(
rowClassNameExpr rowClassNameExpr
@ -167,16 +173,22 @@ export class TableBody extends React.Component<TableBodyProps> {
onQuickChange={onQuickChange} onQuickChange={onQuickChange}
ignoreFootableContent={ignoreFootableContent} ignoreFootableContent={ignoreFootableContent}
{...rowProps} {...rowProps}
testIdBuilder={rowTestBuidr}
/> />
); );
} }
} else if (item.children.length && item.expanded) { } else if (item.children.length && item.expanded) {
// 嵌套表格 // 嵌套表格
doms.push( doms.push(
...this.renderRows(item.children, columns, { ...this.renderRows(
...rowProps, item.children,
parent: item columns,
}) {
...rowProps,
parent: item
},
rowPath
)
); );
} }
return doms; return doms;

View File

@ -45,6 +45,7 @@ interface TableRowProps extends Pick<RendererProps, 'render'> {
checkOnItemClick?: boolean; checkOnItemClick?: boolean;
ignoreFootableContent?: boolean; ignoreFootableContent?: boolean;
testIdBuilder?: TestIdBuilder; testIdBuilder?: TestIdBuilder;
rowPath: string; // 整体行的路径,树形时需要父行序号/当前展开层级下的行序号
[propName: string]: any; [propName: string]: any;
} }
@ -205,6 +206,7 @@ export class TableRow extends React.PureComponent<
trRef, trRef,
isNested, isNested,
testIdBuilder, testIdBuilder,
rowPath,
...rest ...rest
} = this.props; } = this.props;
@ -267,6 +269,7 @@ export class TableRow extends React.PureComponent<
width: null, width: null,
rowIndex: itemIndex, rowIndex: itemIndex,
colIndex: column.index, colIndex: column.index,
rowPath,
key: column.index, key: column.index,
onAction: this.handleAction, onAction: this.handleAction,
onQuickChange: this.handleQuickChange, onQuickChange: this.handleQuickChange,
@ -328,11 +331,11 @@ export class TableRow extends React.PureComponent<
...rest, ...rest,
rowIndex: itemIndex, rowIndex: itemIndex,
colIndex: column.index, colIndex: column.index,
rowPath,
key: column.id, key: column.id,
onAction: this.handleAction, onAction: this.handleAction,
onQuickChange: this.handleQuickChange, onQuickChange: this.handleQuickChange,
onChange: this.handleChange, onChange: this.handleChange
testIdBuilder: testIdBuilder?.getChild(`col${column.index}`)
}) })
) : column.name && item.rowSpans[column.name] === 0 ? null : ( ) : column.name && item.rowSpans[column.name] === 0 ? null : (
<td key={column.id}> <td key={column.id}>

View File

@ -2076,7 +2076,8 @@ export default class Table extends React.Component<TableProps, object> {
classnames: cx, classnames: cx,
canAccessSuperData, canAccessSuperData,
itemBadge, itemBadge,
translate translate,
testIdBuilder
} = this.props; } = this.props;
return ( return (
@ -2100,6 +2101,9 @@ export default class Table extends React.Component<TableProps, object> {
quickEditFormRef={this.subFormRef} quickEditFormRef={this.subFormRef}
onImageEnlarge={this.handleImageEnlarge} onImageEnlarge={this.handleImageEnlarge}
translate={translate} translate={translate}
testIdBuilder={testIdBuilder.getChild(
`cell-${props.rowPath}-${column.index}`
)}
/> />
); );
} }

View File

@ -763,7 +763,8 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
collapseBtnLabel, collapseBtnLabel,
disabled, disabled,
mobileUI, mobileUI,
swipeable swipeable,
testIdBuilder
} = this.props; } = this.props;
const mode = tabsMode || dMode; const mode = tabsMode || dMode;
@ -809,6 +810,9 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
: unmountOnExit : unmountOnExit
} }
onSelect={this.handleSelect} onSelect={this.handleSelect}
testIdBuilder={testIdBuilder.getChild(
`tab-${typeof tab.title === 'string' ? tab.title : index}`
)}
> >
{render( {render(
`item/${index}`, `item/${index}`,
@ -850,6 +854,9 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
: unmountOnExit : unmountOnExit
} }
onSelect={this.handleSelect} onSelect={this.handleSelect}
testIdBuilder={testIdBuilder.getChild(
`tab-${typeof tab.title === 'string' ? tab.title : index}`
)}
> >
{this.renderTab {this.renderTab
? this.renderTab(tab, this.props, index) ? this.renderTab(tab, this.props, index)
@ -897,6 +904,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
collapseOnExceed={collapseOnExceed} collapseOnExceed={collapseOnExceed}
collapseBtnLabel={collapseBtnLabel} collapseBtnLabel={collapseBtnLabel}
mobileUI={mobileUI} mobileUI={mobileUI}
testIdBuilder={testIdBuilder}
> >
{children} {children}
</CTabs> </CTabs>