补充表单项组件testid (#9646)

This commit is contained in:
Allen 2024-02-22 20:46:13 +08:00 committed by GitHub
parent 51131b64c0
commit c747bf62ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 428 additions and 154 deletions

View File

@ -246,6 +246,7 @@ export default function (schema, schemaProps, showCode, envOverrides) {
};
});
},
enableTestid: true,
...envOverrides
};

View File

@ -478,9 +478,11 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
// 优先使用组件自己的testid或者id这个解决不了table行内的一些子元素
// 每一行都会出现这个testid的元素只在测试工具中直接使用nth拿序号
if (props.testid || props.id || props.testIdBuilder == null) {
props.testIdBuilder = new TestIdBuilder(
rest.env.enableTestid ? props.testid || props.id : null
);
if (!(props.testIdBuilder instanceof TestIdBuilder)) {
props.testIdBuilder = new TestIdBuilder(
rest.env.enableTestid ? props.testid || props.id : null
);
}
}
// 自动解析变量模式,主要是方便直接引入第三方组件库,无需为了支持变量封装一层

View File

@ -2,6 +2,7 @@
import type {JSONSchema7} from 'json-schema';
import {ListenerAction} from './actions/Action';
import {debounceConfig, trackConfig} from './utils/renderer-event';
import type {TestIdBuilder} from './utils/helper';
export interface Option {
/**
@ -689,6 +690,8 @@ export interface BaseSchemaWithoutType {
*
*/
useMobileUI?: boolean;
testIdBuilder?: TestIdBuilder;
}
export type OperatorType =

View File

@ -2303,7 +2303,7 @@ export class TestIdBuilder {
}
// 生成子区域的testid生成器
getChild(childPath: string, data?: object) {
getChild(childPath: string | number, data?: object) {
if (this.testId == null) {
return new TestIdBuilder();
}

View File

@ -8,6 +8,7 @@ import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {RootClose} from 'amis-core';
import {removeHTMLTag} from 'amis-core';
import {Icon} from './icons';
import type {TestIdBuilder} from 'amis-core';
export type ItemPlace = 'start' | 'middle' | 'end';
export type TooltipPositionType = 'top' | 'bottom' | 'left' | 'right';
@ -32,6 +33,7 @@ interface BreadcrumbItemProps {
tooltipContainer?: any;
tooltipPosition?: TooltipPositionType;
classnames: ClassNamesFn;
testIdBuilder?: TestIdBuilder;
[propName: string]: any;
}
@ -39,6 +41,7 @@ interface BreadcrumbProps extends ThemeProps {
tooltipContainer?: any;
tooltipPosition?: TooltipPositionType;
items: Array<BreadcrumbBaseItem>;
testIdBuilder?: TestIdBuilder;
[propName: string]: any;
}
@ -65,6 +68,7 @@ export class Breadcrumb extends React.Component<BreadcrumbProps> {
separatorClassName,
items,
separator,
testIdBuilder,
...restProps
} = this.props;
@ -75,6 +79,9 @@ export class Breadcrumb extends React.Component<BreadcrumbProps> {
const crumbs = items
.map<React.ReactNode>((item, index) => {
const itemTestIdBuilder = testIdBuilder?.getChild(
`item-${item.label || index}`
);
let itemPlace: ItemPlace = 'middle';
if (index === 0) {
itemPlace = 'start';
@ -88,6 +95,7 @@ export class Breadcrumb extends React.Component<BreadcrumbProps> {
item={item}
itemPlace={itemPlace}
key={index}
testIdBuilder={itemTestIdBuilder}
></BreadcrumbItem>
);
})
@ -157,7 +165,12 @@ export class BreadcrumbItem extends React.Component<
item: BreadcrumbBaseItem,
label?: string
) {
const {itemClassName, dropdownItemClassName, classnames: cx} = this.props;
const {
itemClassName,
dropdownItemClassName,
classnames: cx,
testIdBuilder
} = this.props;
const baseItemClassName =
itemType === 'default' ? itemClassName : dropdownItemClassName;
if (showHref) {
@ -165,6 +178,7 @@ export class BreadcrumbItem extends React.Component<
<a
href={item.href}
className={cx('Breadcrumb-item-' + itemType, baseItemClassName)}
{...testIdBuilder?.getTestId()}
>
{item.icon ? (
<Icon
@ -179,7 +193,10 @@ export class BreadcrumbItem extends React.Component<
);
}
return (
<span className={cx('Breadcrumb-item-' + itemType, baseItemClassName)}>
<span
className={cx('Breadcrumb-item-' + itemType, baseItemClassName)}
{...testIdBuilder?.getTestId()}
>
{item.icon ? (
<Icon
cx={cx}

View File

@ -117,8 +117,13 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
readOnly={readOnly}
name={name}
/>
<i />
<span className={cx(labelClassName)}>{children || label}</span>
<i {...testIdBuilder?.getChild('input').getTestId()} />
<span
className={cx(labelClassName)}
{...testIdBuilder?.getChild('label').getTestId()}
>
{children || label}
</span>
{description ? (
<div className={cx('Checkbox-desc')}>{description}</div>
) : null}

View File

@ -27,7 +27,7 @@ import Input from './Input';
import Button from './Button';
import type {Moment} from 'moment';
import type {PlainObject, RendererEnv} from 'amis-core';
import type {PlainObject, RendererEnv, TestIdBuilder} from 'amis-core';
import type {ChangeEventViewMode, MutableUnitOfTime} from './calendar/Calendar';
const availableShortcuts: {[propName: string]: any} = {
@ -334,6 +334,7 @@ export interface DateProps extends LocaleProps, ThemeProps {
// 是否为结束时间
isEndDate?: boolean;
testIdBuilder?: TestIdBuilder;
disabledDate?: (date: moment.Moment) => any;
onClick?: (date: moment.Moment) => any;
@ -949,6 +950,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
mobileCalendarMode,
label,
env,
testIdBuilder,
onClick,
onMouseEnter,
onMouseLeave,
@ -1058,6 +1060,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
testIdBuilder={testIdBuilder?.getChild('calendar')}
/>
</div>
);
@ -1081,6 +1084,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
)}
ref={this.domRef}
onClick={this.handleClick}
{...testIdBuilder?.getTestId()}
>
<Input
className={cx('DatePicker-input')}
@ -1092,17 +1096,25 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
value={this.state.inputValue || ''}
disabled={disabled}
readOnly={mobileUI}
{...testIdBuilder?.getChild('input').getTestId()}
/>
{clearable &&
!disabled &&
normalizeDate(value, valueFormat || format) ? (
<a className={cx(`DatePicker-clear`)} onClick={this.clearValue}>
<a
className={cx(`DatePicker-clear`)}
onClick={this.clearValue}
{...testIdBuilder?.getChild('clear').getTestId()}
>
<Icon icon="input-clear" className="icon" />
</a>
) : null}
<a className={cx(`DatePicker-toggler`)}>
<a
className={cx(`DatePicker-toggler`)}
{...testIdBuilder?.getChild('toggler').getTestId()}
>
<Icon
icon={viewMode === 'time' ? 'clock' : 'date'}
className="icon"
@ -1151,6 +1163,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
testIdBuilder={testIdBuilder?.getChild('calendar')}
// utc={utc}
/>
{isConfirmMode ? (
@ -1209,6 +1222,7 @@ export class DatePicker extends React.Component<DateProps, DatePickerState> {
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
testIdBuilder={testIdBuilder?.getChild('calendar')}
// utc={utc}
/>
</PopUp>

View File

@ -2,6 +2,7 @@ import {ThemeProps, themeable} from 'amis-core';
import React from 'react';
import {Options, Option} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import type {TestIdBuilder} from 'amis-core';
export interface ListMenuProps extends ThemeProps, LocaleProps {
options: Options;
@ -14,6 +15,7 @@ export interface ListMenuProps extends ThemeProps, LocaleProps {
getItemProps: (props: {item: Option; index: number}) => any;
prefix?: JSX.Element;
children?: React.ReactNode | Array<React.ReactNode>;
testIdBuilder?: TestIdBuilder;
}
interface RenderResult {
@ -37,7 +39,8 @@ export class ListMenu extends React.Component<ListMenuProps> {
highlightIndex,
selectedOptions,
mobileUI,
onSelect
onSelect,
testIdBuilder
} = this.props;
if (Array.isArray(option.children) && option.children.length) {
@ -74,6 +77,7 @@ export class ListMenu extends React.Component<ListMenuProps> {
)}
key={index}
onClick={onSelect ? (e: any) => onSelect(e, option) : undefined}
{...testIdBuilder?.getChild(option.value || index).getTestId()}
{...getItemProps({
item: option,
index: index

View File

@ -22,6 +22,7 @@ import {value2array, OptionProps, Option} from './Select';
import chunk from 'lodash/chunk';
import {ClassNamesFn, themeable} from 'amis-core';
import {columnsSplit} from 'amis-core';
import {TestIdBuilder} from 'amis-core';
interface RadioProps extends OptionProps {
id?: string;
@ -42,6 +43,7 @@ interface RadioProps extends OptionProps {
classPrefix: string;
classnames: ClassNamesFn;
renderLabel?: (item: Option, props: RadioProps) => JSX.Element;
testIdBuilder?: TestIdBuilder;
}
const defaultLabelRender = (item: Option, props: RadioProps) => (
@ -123,8 +125,10 @@ export class Radios extends React.Component<RadioProps, any> {
level,
btnActiveLevel,
classPrefix: ns,
testIdBuilder,
renderLabel = defaultLabelRender
} = this.props;
const itemTestIdBuilder = testIdBuilder?.getChild(option.value || index);
if (optionType === 'button') {
const active = !!~valueArray.indexOf(option);
@ -153,6 +157,7 @@ export class Radios extends React.Component<RadioProps, any> {
description={option.description}
inline={inline}
labelClassName={labelClassName}
testIdBuilder={itemTestIdBuilder}
>
{renderLabel(option, this.props)}
</Checkbox>

View File

@ -1,4 +1,4 @@
import {ThemeProps, themeable} from 'amis-core';
import {TestIdBuilder, ThemeProps, themeable} from 'amis-core';
import React from 'react';
import omit from 'lodash/omit';
import isInteger from 'lodash/isInteger';
@ -30,6 +30,7 @@ export interface ResultBoxProps
showInvalidMatch?: boolean;
popOverContainer?: any;
showArrow?: boolean;
testIdBuilder?: TestIdBuilder;
}
export class ResultBox extends React.Component<ResultBoxProps> {
@ -116,7 +117,8 @@ export class ResultBox extends React.Component<ResultBoxProps> {
itemRender,
classnames: cx,
showInvalidMatch,
popOverContainer
popOverContainer,
testIdBuilder
} = this.props;
if (
@ -143,6 +145,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
{label: `+ ${tags.length - maxVisibleCount} ...`}
].map((item, index) => {
const isShowInvalid = showInvalidMatch && item?.__unmatched;
const itemTIB = testIdBuilder?.getChild(item.value || index);
return index === maxVisibleCount ? (
<TooltipWrapper
key={tags.length}
@ -162,11 +165,16 @@ export class ResultBox extends React.Component<ResultBoxProps> {
'is-invalid': showInvalidMatch && item?.__unmatched
})}
key={itemIndex}
{...itemTIB?.getTestId()}
>
<span className={cx('ResultBox-valueLabel')}>
{itemRender(item)}
</span>
<a data-index={itemIndex} onClick={this.removeItem}>
<a
data-index={itemIndex}
onClick={this.removeItem}
{...itemTIB?.getChild('close').getTestId()}
>
<Icon icon="close" className="icon" />
</a>
</div>
@ -197,11 +205,16 @@ export class ResultBox extends React.Component<ResultBoxProps> {
className={cx('ResultBox-value', {
'is-invalid': isShowInvalid
})}
{...itemTIB?.getTestId()}
>
<span className={cx('ResultBox-valueLabel')}>
{itemRender(item)}
</span>
<a data-index={index} onClick={this.removeItem}>
<a
data-index={index}
onClick={this.removeItem}
{...itemTIB?.getChild('close').getTestId()}
>
<Icon icon="close" className="icon" />
</a>
</div>
@ -210,26 +223,36 @@ export class ResultBox extends React.Component<ResultBoxProps> {
});
}
return tags.map((item, index) => (
<TooltipWrapper
container={popOverContainer}
placement={'top'}
tooltip={item['label']}
trigger={'hover'}
key={index}
>
<div
className={cx('ResultBox-value', {
'is-invalid': showInvalidMatch && item?.__unmatched
})}
return tags.map((item, index) => {
const itemTIB = testIdBuilder?.getChild(index);
return (
<TooltipWrapper
container={popOverContainer}
placement={'top'}
tooltip={item['label']}
trigger={'hover'}
key={index}
>
<span className={cx('ResultBox-valueLabel')}>{itemRender(item)}</span>
<a data-index={index} onClick={this.removeItem}>
<Icon icon="close" className="icon" />
</a>
</div>
</TooltipWrapper>
));
<div
className={cx('ResultBox-value', {
'is-invalid': showInvalidMatch && item?.__unmatched
})}
{...itemTIB?.getTestId()}
>
<span className={cx('ResultBox-valueLabel')}>
{itemRender(item)}
</span>
<a
data-index={index}
onClick={this.removeItem}
{...itemTIB?.getChild('close').getTestId()}
>
<Icon icon="close" className="icon" />
</a>
</div>
</TooltipWrapper>
);
});
}
render() {
@ -265,6 +288,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
overflowTagPopover,
showArrow,
popOverContainer,
testIdBuilder,
...rest
} = this.props;
const isFocused = this.state.isFocused;
@ -286,6 +310,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
onKeyPress={allowInput ? undefined : onKeyPress}
onFocus={allowInput ? undefined : onFocus}
onBlur={allowInput ? undefined : onBlur}
{...testIdBuilder?.getTestId()}
>
<div className={cx('ResultBox-value-wrap')}>
{Array.isArray(result) && result.length ? (
@ -320,6 +345,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
)}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
testIdBuilder={testIdBuilder?.getChild('input')}
/>
) : null}
@ -335,6 +361,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
className={cx('ResultBox-clear', {
'ResultBox-clear-with-arrow': hasDropDownArrow
})}
{...testIdBuilder?.getChild('clear').getTestId()}
>
<div className={cx('ResultBox-clear-wrap')}>
<Icon icon="input-clear" className="icon" />

View File

@ -1060,7 +1060,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
}
let label = labelToString(item[labelField]);
let optTestIdBudr = testIdBuilder?.getChild(`option-${index}`);
let optTestIdBudr = testIdBuilder?.getChild(`option-${label || index}`);
return (
<div
@ -1405,7 +1405,10 @@ export class Select extends React.Component<SelectProps, SelectState> {
/>
) : null}
<span className={cx('Select-arrow')}>
<span
className={cx('Select-arrow')}
{...testIdBuilder?.getChild('arrow').getTestId()}
>
<Icon icon="right-arrow-bold" className="icon" />
</span>
{isOpen ? this.renderOuter(options) : null}

View File

@ -1155,8 +1155,8 @@ export class TreeSelector extends React.Component<
const disabled = this.isItemDisabled(item, checked);
const partial = this.isItemChildrenPartialChecked(item, checked);
const checkedInValue = !!~this.state.value.indexOf(item);
const itemTestBildr = testIdBuilder?.getChild(
`item-${item[labelField] || index}`
const itemTestBuilder = testIdBuilder?.getChild(
`item-${item[valueField] || item[labelField] || index}`
);
const checkbox: JSX.Element | null = multiple ? (
@ -1166,7 +1166,7 @@ export class TreeSelector extends React.Component<
checked={checked || partial}
partial={partial}
onChange={this.handleCheck.bind(this, item, !checked)}
{...itemTestBildr?.getChild('chekbx').getTestId()}
{...itemTestBuilder?.getChild('chekbx').getTestId()}
/>
) : showRadio ? (
<Checkbox
@ -1174,7 +1174,7 @@ export class TreeSelector extends React.Component<
disabled={disabled}
checked={checked}
onChange={this.handleSelect.bind(this, item)}
{...itemTestBildr?.getChild('chekbx').getTestId()}
{...itemTestBuilder?.getChild('chekbx').getTestId()}
/>
) : null;
@ -1192,11 +1192,11 @@ export class TreeSelector extends React.Component<
let body = null;
if (isEditing && editingItem === item) {
body = this.renderInput(checkbox, itemTestBildr?.getChild('edit'));
body = this.renderInput(checkbox, itemTestBuilder?.getChild('edit'));
} else if (item.isAdding) {
body = this.renderInput(
<span className={cx('Tree-itemArrowPlaceholder')} />,
itemTestBildr?.getChild('add')
itemTestBuilder?.getChild('add')
);
} else {
const isFolded = !this.isUnfolded(item);
@ -1219,7 +1219,7 @@ export class TreeSelector extends React.Component<
{draggable && (
<a
className={cx('Tree-itemDrager drag-bar')}
{...itemTestBildr?.getChild('drag-bar').getTestId()}
{...itemTestBuilder?.getChild('drag-bar').getTestId()}
>
<Icon icon="drag-bar" className="icon" />
</a>
@ -1239,7 +1239,7 @@ export class TreeSelector extends React.Component<
className={cx('Tree-itemArrow', {
'is-folded': isFolded
})}
{...itemTestBildr
{...itemTestBuilder
?.getChild(isFolded ? 'open' : 'fold')
.getTestId()}
>
@ -1253,7 +1253,7 @@ export class TreeSelector extends React.Component<
<div
className={cx('Tree-itemLabel-item', {'is-mobile': mobileUI})}
{...itemTestBildr?.getChild('content').getTestId()}
{...itemTestBuilder?.getChild('content').getTestId()}
>
{showIcon ? (
<i
@ -1292,7 +1292,7 @@ export class TreeSelector extends React.Component<
: this.handleSelect(item))
}
title={item[labelField]}
{...itemTestBildr?.getChild('text').getTestId()}
{...itemTestBuilder?.getChild('text').getTestId()}
>
{itemRender
? itemRender(item, {
@ -1323,7 +1323,7 @@ export class TreeSelector extends React.Component<
>
<a
onClick={this.handleAdd.bind(this, item)}
{...itemTestBildr?.getChild('add').getTestId()}
{...itemTestBuilder?.getChild('add').getTestId()}
>
<Icon icon="plus" className="icon" />
</a>
@ -1339,7 +1339,7 @@ export class TreeSelector extends React.Component<
>
<a
onClick={this.handleRemove.bind(this, item)}
{...itemTestBildr?.getChild('remove').getTestId()}
{...itemTestBuilder?.getChild('remove').getTestId()}
>
<Icon icon="minus" className="icon" />
</a>
@ -1355,7 +1355,7 @@ export class TreeSelector extends React.Component<
>
<a
onClick={this.handleEdit.bind(this, item)}
{...itemTestBildr?.getChild('edit').getTestId()}
{...itemTestBuilder?.getChild('edit').getTestId()}
>
<Icon icon="new-edit" className="icon" />
</a>
@ -1379,7 +1379,7 @@ export class TreeSelector extends React.Component<
...style,
paddingLeft: `calc(${level} * var(--Tree-indent))`
}}
{...itemTestBildr?.getTestId()}
{...itemTestBuilder?.getTestId()}
>
{body}
</li>

View File

@ -15,7 +15,7 @@ import {PickerOption} from '../PickerColumn';
import 'moment/locale/zh-cn';
import 'moment/locale/de';
import type {RendererEnv} from 'amis-core';
import type {RendererEnv, TestIdBuilder} from 'amis-core';
import type {unitOfTime} from 'moment';
/** 视图模式 */
@ -132,6 +132,7 @@ interface BaseDatePickerProps {
timeConstraints?: any;
timeRangeHeader?: string;
status?: ChangeEventViewStatus;
testIdBuilder?: TestIdBuilder;
}
interface BaseDatePickerState {
@ -456,7 +457,8 @@ class BaseDatePicker extends React.Component<
'mobileUI',
'showToolbar',
'embed',
'env'
'env',
'testIdBuilder'
].forEach(key => (props[key] = (this.props as any)[key]));
return props;
@ -721,8 +723,14 @@ class BaseDatePicker extends React.Component<
}
render() {
const {viewMode, timeFormat, dateFormat, timeRangeHeader, mobileUI} =
this.props;
const {
viewMode,
timeFormat,
dateFormat,
timeRangeHeader,
mobileUI,
testIdBuilder
} = this.props;
const Component = CustomCalendarContainer as any;
const viewProps = this.getComponentProps();
@ -757,6 +765,7 @@ class BaseDatePicker extends React.Component<
? 'rdtTime'
: ''
)}
{...testIdBuilder?.getTestId()}
>
<div
key="dt"

View File

@ -15,7 +15,7 @@ import {
ClassNamesFn,
convertArrayValueToMoment
} from 'amis-core';
import type {RendererEnv} from 'amis-core';
import type {RendererEnv, TestIdBuilder} from 'amis-core';
import Picker from '../Picker';
import {PickerOption} from '../PickerColumn';
import {DateType} from './Calendar';
@ -89,6 +89,7 @@ interface CustomDaysViewProps extends LocaleProps {
getColumns: (types: DateType[], dateBoundary: void) => any;
getDateBoundary: (currentDate: moment.Moment) => any;
timeConstraints?: any;
testIdBuilder?: TestIdBuilder;
}
export class CustomDaysView extends React.Component<CustomDaysViewProps> {
@ -422,7 +423,7 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
renderDay = (props: any, currentDate: moment.Moment) => {
const {todayActiveStyle} = props; /** 只有today才会传入这个属性 */
const {classnames: cx, translate: __, env} = this.props;
const {classnames: cx, translate: __, testIdBuilder} = this.props;
const injectedProps = omit(props, ['todayActiveStyle']);
/** 某些情况下需要用inline style覆盖动态class需要hack important的样式 */
const todayDomRef = (node: HTMLSpanElement | null) => {
@ -611,7 +612,11 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
return (
<td {...injectedProps}>
<span style={todayActiveStyle} ref={todayDomRef}>
<span
style={todayActiveStyle}
ref={todayDomRef}
{...testIdBuilder?.getChild(props.key)?.getTestId()}
>
{currentDate.date()}
</span>
</td>
@ -637,7 +642,8 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
selectedDate,
viewDate,
isEndDate,
classnames: cx
classnames: cx,
testIdBuilder
} = this.props;
const date = selectedDate || (isEndDate ? viewDate.endOf('day') : viewDate);
@ -685,6 +691,7 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
)
)
});
const itemTIB = testIdBuilder?.getChild(type);
return (
<div
className={cx(
@ -718,6 +725,7 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
? option.value === date.format(formatMap[type])
: option.value === options?.[0]?.value
})}
{...itemTIB?.getChild(option.value)?.getTestId()}
onClick={() => {
this.setTime(type, parseInt(option.value, 10));
this.scrollToTop(
@ -834,7 +842,8 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
mobileUI,
embed,
timeFormat,
classnames: cx
classnames: cx,
testIdBuilder
} = this.props;
const locale = date.localeData();
const __ = this.props.translate;
@ -851,6 +860,7 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
<a
className="rdtPrev"
onClick={this.props.subtractTime(1, 'years')}
{...testIdBuilder?.getChild('prev-year').getTestId()}
>
<Icon
icon="right-double-arrow"
@ -860,6 +870,7 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
<a
className="rdtPrev"
onClick={this.props.subtractTime(1, 'months')}
{...testIdBuilder?.getChild('prev-month').getTestId()}
>
<Icon
icon="right-arrow"
@ -871,12 +882,14 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
<a
className="rdtSwitch"
onClick={this.props.showView('years')}
{...testIdBuilder?.getChild('switch-years').getTestId()}
>
{date.format(__('dateformat.year'))}
</a>
<a
className="rdtSwitch"
onClick={this.props.showView('months')}
{...testIdBuilder?.getChild('switch-months').getTestId()}
>
{date.format(__('MMM'))}
</a>
@ -885,10 +898,15 @@ export class CustomDaysView extends React.Component<CustomDaysViewProps> {
<a
className="rdtNext"
onClick={this.props.addTime(1, 'months')}
{...testIdBuilder?.getChild('next-month').getTestId()}
>
<Icon icon="right-arrow" className="icon date-icon-arrow" />
</a>
<a className="rdtNext" onClick={this.props.addTime(1, 'years')}>
<a
className="rdtNext"
onClick={this.props.addTime(1, 'years')}
{...testIdBuilder?.getChild('next-year').getTestId()}
>
<Icon
icon="right-double-arrow"
className="icon date-icon-arrow"

View File

@ -4,6 +4,7 @@ import {LocaleProps, localeable, getRange} from 'amis-core';
import Picker from '../Picker';
import {PickerOption} from '../PickerColumn';
import {DateType} from './Calendar';
import type {TestIdBuilder} from 'amis-core';
export interface OtherProps {
inputFormat?: string;
@ -47,6 +48,7 @@ export interface CustomMonthsViewProps extends LocaleProps {
timeCell: (value: number, type: DateType) => string;
getDateBoundary: (currentDate: moment.Moment) => any;
mobileUI: boolean;
testIdBuilder?: TestIdBuilder;
}
export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
@ -71,6 +73,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
}
renderMonths() {
const {testIdBuilder} = this.props;
let date = this.props.selectedDate,
month = this.props.viewDate.month(),
year = this.props.viewDate.year(),
@ -159,7 +162,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
year: number,
date: moment.Moment
) => {
const {translate: __} = this.props;
const {translate: __, testIdBuilder} = this.props;
const {viewDate: localMoment, ...rest} = props;
const monthStr = localMoment.month(month).format(__('MMM'));
const strLength = 3;
@ -169,7 +172,9 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
return (
<td {...rest}>
<span>{monthStrFixedLength}</span>
<span {...testIdBuilder?.getChild(props.key).getTestId()}>
{monthStrFixedLength}
</span>
</td>
);
};
@ -248,6 +253,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
render() {
const __ = this.props.translate;
const {testIdBuilder} = this.props;
const showYearHead =
!/^mm$/i.test(this.props.inputFormat || '') && !this.props.hideHeader;
const canClick = /yy/i.test(this.props.inputFormat || '');
@ -264,6 +270,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
<th
className="rdtPrev"
onClick={this.props.subtractTime(1, 'years')}
{...testIdBuilder?.getChild('prev-year').getTestId()}
>
&laquo;
</th>
@ -271,6 +278,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
<th
className="rdtSwitch"
onClick={this.props.showView('years')}
{...testIdBuilder?.getChild('switch-year').getTestId()}
>
{this.props.viewDate.format(__('dateformat.year'))}
</th>
@ -283,6 +291,7 @@ export class CustomMonthsView extends React.Component<CustomMonthsViewProps> {
<th
className="rdtNext"
onClick={this.props.addTime(1, 'years')}
{...testIdBuilder?.getChild('next-year').getTestId()}
>
&raquo;
</th>

View File

@ -5,6 +5,7 @@ import Picker from '../Picker';
import {PickerColumnItem} from '../PickerColumn';
import {getRange} from 'amis-core';
import {autobind} from 'amis-core';
import type {TestIdBuilder} from 'amis-core';
export interface QuarterViewProps extends LocaleProps, ThemeProps {
viewDate: moment.Moment;
@ -29,6 +30,7 @@ export interface QuarterViewProps extends LocaleProps, ThemeProps {
hideHeader?: boolean;
onConfirm?: (value: number[], types?: string[]) => void;
onClose?: () => void;
testIdBuilder?: TestIdBuilder;
}
export class QuarterView extends React.Component<QuarterViewProps> {
@ -38,7 +40,7 @@ export class QuarterView extends React.Component<QuarterViewProps> {
};
renderYear() {
const __ = this.props.translate;
const {translate: __, testIdBuilder} = this.props;
const showYearHead = !/^mm$/i.test(this.props.inputFormat || '');
if (!showYearHead) {
@ -54,11 +56,16 @@ export class QuarterView extends React.Component<QuarterViewProps> {
<th
className="rdtPrev"
onClick={this.props.subtractTime(1, 'years')}
{...testIdBuilder?.getChild('prev-year').getTestId()}
>
&laquo;
</th>
{canClick ? (
<th className="rdtSwitch" onClick={this.props.showView('years')}>
<th
className="rdtSwitch"
onClick={this.props.showView('years')}
{...testIdBuilder?.getChild('switch-year').getTestId()}
>
{this.props.viewDate.format(__('dateformat.year'))}
</th>
) : (
@ -67,7 +74,11 @@ export class QuarterView extends React.Component<QuarterViewProps> {
</th>
)}
<th className="rdtNext" onClick={this.props.addTime(1, 'years')}>
<th
className="rdtNext"
onClick={this.props.addTime(1, 'years')}
{...testIdBuilder?.getChild('next-year').getTestId()}
>
&raquo;
</th>
</tr>
@ -135,9 +146,12 @@ export class QuarterView extends React.Component<QuarterViewProps> {
year: number,
date: moment.Moment
) => {
const {testIdBuilder} = this.props;
return (
<td {...props}>
<span>Q{quartar}</span>
<span {...testIdBuilder?.getChild(props.key).getTestId()}>
Q{quartar}
</span>
</td>
);
};

View File

@ -14,6 +14,7 @@ import {PickerColumnItem} from '../PickerColumn';
import Downshift from 'downshift';
import type {Moment} from 'moment';
import type {TestIdBuilder} from 'amis-core';
interface CustomTimeViewProps extends LocaleProps {
viewDate: moment.Moment;
@ -52,6 +53,7 @@ interface CustomTimeViewProps extends LocaleProps {
onChange: (value: moment.Moment) => void;
timeConstraints?: any;
timeRangeHeader?: string;
testIdBuilder?: TestIdBuilder;
}
interface CustomTimeViewState {
@ -689,7 +691,8 @@ export class CustomTimeView extends React.Component<
isEndDate,
classnames: cx,
timeRangeHeader,
mobileUI
mobileUI,
testIdBuilder
} = this.props;
const __ = this.props.translate;
@ -745,6 +748,7 @@ export class CustomTimeView extends React.Component<
)
)
});
const itemTIB = testIdBuilder?.getChild(type);
return (
<div className={cx('CalendarInputWrapper')}>
<div
@ -767,6 +771,7 @@ export class CustomTimeView extends React.Component<
: option.value === options?.[0]?.value &&
!mobileUI
})}
{...itemTIB?.getChild(option.value).getTestId()}
onClick={() => {
this.setTime(type, parseInt(option.value, 10));
this.scrollToTop(
@ -793,7 +798,11 @@ export class CustomTimeView extends React.Component<
inputs.length && inputs.pop();
const quickLists = [
<a key="select-now" onClick={this.selectNowTime}>
<a
key="select-now"
onClick={this.selectNowTime}
{...testIdBuilder?.getChild('select-now').getTestId()}
>
{__('TimeNow')}
</a>
];
@ -809,6 +818,7 @@ export class CustomTimeView extends React.Component<
<a
className={cx('Button', 'Button--primary', 'Button--size-sm')}
onClick={this.confirm}
{...testIdBuilder?.getChild('confirm').getTestId()}
>
{__('confirm')}
</a>

View File

@ -2,6 +2,7 @@ import moment from 'moment';
import React from 'react';
import {LocaleProps, localeable, utils, getRange} from 'amis-core';
import Picker from '../Picker';
import type {TestIdBuilder} from 'amis-core';
interface CustomYearsViewProps extends LocaleProps {
viewDate: moment.Moment;
@ -31,6 +32,7 @@ interface CustomYearsViewProps extends LocaleProps {
currentDate: moment.Moment,
selected?: moment.Moment
) => boolean;
testIdBuilder?: TestIdBuilder;
}
export class CustomYearsView extends React.Component<CustomYearsViewProps> {
@ -124,9 +126,10 @@ export class CustomYearsView extends React.Component<CustomYearsViewProps> {
}
renderYear = (props: any, year: number, date?: moment.Moment) => {
const {testIdBuilder} = this.props;
return (
<td {...props}>
<span>{year}</span>
<span {...testIdBuilder?.getChild(props.key).getTestId()}>{year}</span>
</td>
);
};
@ -179,7 +182,7 @@ export class CustomYearsView extends React.Component<CustomYearsViewProps> {
render() {
let year = this.props.viewDate.year();
year = year - (year % 10);
const __ = this.props.translate;
const {testIdBuilder, translate: __} = this.props;
if (this.props.mobileUI) {
return <div className="rdtYears">{this.renderYearPicker()}</div>;
}
@ -191,13 +194,18 @@ export class CustomYearsView extends React.Component<CustomYearsViewProps> {
<th
className="rdtPrev"
onClick={this.props.subtractTime(10, 'years')}
{...testIdBuilder?.getChild('prev-year').getTestId()}
>
&laquo;
</th>
<th className="rdtSwitch">
{__('year-to-year', {from: year, to: year + 9})}
</th>
<th className="rdtNext" onClick={this.props.addTime(10, 'years')}>
<th
className="rdtNext"
onClick={this.props.addTime(10, 'years')}
{...testIdBuilder?.getChild('next-year').getTestId()}
>
&raquo;
</th>
</tr>

View File

@ -186,7 +186,11 @@ export class SubMenu extends React.Component<SubMenuProps> {
{labelNode}
{labelExtra}
{!stacked && depth === 1 ? (
<span key="expand-toggle" className={cx('Nav-Menu-submenu-arrow')}>
<span
key="expand-toggle"
className={cx('Nav-Menu-submenu-arrow')}
{...testIdBuilder?.getChild('expand-toggle').getTestId()}
>
<Icon icon="right-arrow-bold" className="icon" />
</span>
) : null}

View File

@ -543,8 +543,9 @@ export class Menu extends React.Component<MenuProps, MenuState> {
disabled?: boolean;
[propName: string]: any;
}) {
const {classnames: cx, expandIcon} = this.props;
const navigations = this.state.navigations;
const {classnames: cx, expandIcon, testIdBuilder} = this.props;
const link = findTree(navigations, item => item.id === ctx.eventKey);
return (
<span
key="expand-toggle"
@ -553,6 +554,10 @@ export class Menu extends React.Component<MenuProps, MenuState> {
this.handleToggleExpand(ctx);
e.preventDefault();
}}
{...testIdBuilder
?.getChild(link?.link?.testid || ctx.eventKey)
.getChild('expand-toggle')
.getTestId()}
>
{!React.isValidElement(expandIcon) ? (
<Icon
@ -617,9 +622,7 @@ export class Menu extends React.Component<MenuProps, MenuState> {
badge={badge}
renderLink={renderLink}
depth={level || 1}
testIdBuilder={testIdBuilder?.getChild(
link.testid || index.toString()
)}
testIdBuilder={testIdBuilder?.getChild(link.testid || index)}
popupClassName={popupClassName}
>
{this.renderMenuContent(item.children || [], item.depth + 1)}
@ -641,9 +644,7 @@ export class Menu extends React.Component<MenuProps, MenuState> {
renderLink={renderLink}
badge={badge}
data={data}
testIdBuilder={testIdBuilder?.getChild(
link.testid || index.toString()
)}
testIdBuilder={testIdBuilder?.getChild(link.testid || index)}
depth={level || 1}
order={index}
overflowedIndicator={overflowedIndicator}

View File

@ -129,7 +129,7 @@ import {
SchemaClassName,
SchemaExpression
} from 'amis-core';
import type {FormSchemaBase} from 'amis-core';
import type {FormSchemaBase, TestIdBuilder} from 'amis-core';
import {MultilineTextSchema} from './renderers/MultilineText';
import {DateRangeSchema} from './renderers/DateRange';
import {PasswordSchema} from './renderers/Password';

View File

@ -7,6 +7,7 @@ import {BaseSchema, SchemaIcon, SchemaUrlPath} from '../Schema';
import {filter} from 'amis-core';
import {resolveVariableAndFilter} from 'amis-core';
import {Breadcrumb} from 'amis-ui';
import type {TestIdBuilder} from 'amis-core';
export type BreadcrumbBaseItemSchema = {
/**
@ -101,6 +102,8 @@ export interface BreadcrumbSchema extends BaseSchema {
*
*/
tooltipPosition?: TooltipPositionType;
testIdBuilder?: TestIdBuilder;
}
export interface BreadcrumbProps

View File

@ -2026,7 +2026,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'pagination',
{
type: 'pagination',
testIdBuilder: testIdBuilder.getChild('pagination')
testIdBuilder: testIdBuilder?.getChild('pagination')
},
{
...extraProps,
@ -2105,7 +2105,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
onChange={(value: any) => this.handleChangePage(1, value.value)}
clearable={false}
popOverContainer={this.parentContainer}
testIdBuilder={testIdBuilder.getChild('perPage')}
testIdBuilder={testIdBuilder?.getChild('perPage')}
/>
</div>
);
@ -2131,7 +2131,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
this.search({page: page + 1, loadDataMode: 'load-more'})
}
size="sm"
{...testIdBuilder.getChild('loadMore').getTestId()}
{...testIdBuilder?.getChild('loadMore').getTestId()}
>
{__('CRUD.loadMore')}
</Button>
@ -2258,7 +2258,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
<div
className={cx('Crud-toolbar')}
key={index}
{...testIdBuilder.getChild('toolbar').getTestId()}
{...testIdBuilder?.getChild('toolbar').getTestId()}
>
{children.map(({toolbar, dom: child}, index) => {
const type = (toolbar as Schema).type || toolbar;
@ -2537,7 +2537,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'is-mobile': isMobile()
})}
style={style}
{...testIdBuilder.getChild('wrapper').getTestId()}
{...testIdBuilder?.getChild('wrapper').getTestId()}
>
{filter && (!store.filterTogggable || store.filterVisible)
? render(
@ -2549,7 +2549,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
...filter,
type: 'form',
api: null,
testIdBuilder: testIdBuilder.getChild('filter')
testIdBuilder: testIdBuilder?.getChild('filter')
},
{
key: 'filter',

View File

@ -240,7 +240,7 @@ export default class Dialog extends React.Component<DialogProps> {
let ret: Array<ActionSchema> = [];
ret.push({
type: 'button',
testIdBuilder: testIdBuilder.getChild('cancel'),
testIdBuilder: testIdBuilder?.getChild('cancel'),
actionType: 'cancel',
label: __('cancel')
});
@ -248,7 +248,7 @@ export default class Dialog extends React.Component<DialogProps> {
if (confirm) {
ret.push({
type: 'button',
testIdBuilder: testIdBuilder.getChild('confirm'),
testIdBuilder: testIdBuilder?.getChild('confirm'),
actionType: 'confirm',
label: __('confirm'),
primary: true

View File

@ -266,7 +266,7 @@ export default class Drawer extends React.Component<DrawerProps> {
let ret: Array<ActionSchema> = [];
ret.push({
type: 'button',
testIdBuilder: testIdBuilder.getChild('cancel'),
testIdBuilder: testIdBuilder?.getChild('cancel'),
actionType: 'close',
label: __('cancel')
});
@ -275,7 +275,7 @@ export default class Drawer extends React.Component<DrawerProps> {
ret.push({
type: 'button',
actionType: 'confirm',
testIdBuilder: testIdBuilder.getChild('confirm'),
testIdBuilder: testIdBuilder?.getChild('confirm'),
label: __('confirm'),
primary: true
});

View File

@ -305,7 +305,10 @@ export default class DropDownButton extends React.Component<
...(button as any),
className: '',
// 防止dropdown中button没有 testid或者id
testIdBuilder: testIdBuilder.getChild(button.label || index, data)
testIdBuilder: testIdBuilder?.getChild(
button.label || index,
data
)
},
{
isMenuItem: true,
@ -443,7 +446,6 @@ export default class DropDownButton extends React.Component<
className
)}
style={style}
{...testIdBuilder.getTestId(data)}
onMouseEnter={trigger === 'hover' ? this.open : () => {}}
onMouseLeave={trigger === 'hover' ? this.close : () => {}}
ref={this.domRef}
@ -458,6 +460,7 @@ export default class DropDownButton extends React.Component<
<button
onClick={this.toogle}
disabled={disabled || btnDisabled}
{...testIdBuilder?.getTestId(data)}
className={cx(
'Button',
btnClassName,

View File

@ -4,7 +4,7 @@ import {
OptionsControlProps,
FormOptionsControl
} from 'amis-core';
import type {Option} from 'amis-core';
import {Option, TestIdBuilder} from 'amis-core';
import {ActionObject, isObject} from 'amis-core';
import type {BadgeObject} from 'amis-ui';
import {getLevelFromClassName, autobind, isEmpty} from 'amis-core';
@ -34,6 +34,7 @@ export interface ButtonGroupProps
| 'btnClassName'
> {
options: Array<Option>;
testIdBuilder: TestIdBuilder;
}
export default class ButtonGroupControl extends React.Component<
@ -102,6 +103,7 @@ export default class ButtonGroupControl extends React.Component<
vertical,
tiled,
badge,
testIdBuilder,
translate: __
} = props;
@ -137,6 +139,9 @@ export default class ButtonGroupControl extends React.Component<
active && 'ButtonGroup-button--active'
),
disabled: option.disabled || disabled,
testIdBuilder: testIdBuilder?.getChild(
`item-${option[labelField || 'label'] || key}`
),
onClick: (e: React.UIEvent<any>) => {
if (disabled) {
return;

View File

@ -319,6 +319,7 @@ export default class ChainedSelectControl extends React.Component<
multiple,
mobileUI,
env,
testIdBuilder,
...rest
} = this.props;
const arr = Array.isArray(value)
@ -341,6 +342,7 @@ export default class ChainedSelectControl extends React.Component<
}
classPrefix={ns}
key="base"
testIdBuilder={testIdBuilder?.getChild('base')}
options={Array.isArray(options) ? options : []}
value={arr[0]}
onChange={this.handleChange.bind(this, 0)}
@ -361,6 +363,7 @@ export default class ChainedSelectControl extends React.Component<
}
classPrefix={ns}
key={`x-${index + 1}`}
testIdBuilder={testIdBuilder?.getChild(`x-${index + 1}`)}
options={Array.isArray(options) ? options : []}
value={arr[index + 1]}
onChange={this.handleChange.bind(this, index + 1)}

View File

@ -12,6 +12,7 @@ import {autobind, createObject} from 'amis-core';
import {ActionObject} from 'amis-core';
import {BaseSchema, FormBaseControlSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
import type {TestIdBuilder} from 'amis-core';
export interface SchemaMap {
checkbox: CheckboxControlSchema;
@ -49,6 +50,7 @@ export interface CheckboxControlSchema extends FormBaseControlSchema {
partial?: boolean;
optionType?: 'default' | 'button';
checked?: boolean;
testIdBuilder?: TestIdBuilder;
}
export interface CheckboxProps
@ -139,6 +141,7 @@ export default class CheckboxControl extends React.Component<
optionType,
checked,
labelClassName,
testIdBuilder,
classPrefix: ns
} = this.props;
@ -155,6 +158,7 @@ export default class CheckboxControl extends React.Component<
optionType={optionType}
checked={checked}
labelClassName={labelClassName}
testIdBuilder={testIdBuilder}
>
{option ? render('option', option) : null}
</Checkbox>

View File

@ -12,6 +12,7 @@ import type {ActionObject, Api, OptionsControlProps, Option} from 'amis-core';
import {Checkbox, Icon, Spinner} from 'amis-ui';
import {FormOptionsSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
import type {TestIdBuilder} from 'amis-core';
/**
*
@ -43,6 +44,7 @@ export interface CheckboxesControlSchema extends FormOptionsSchema {
*
*/
menuTpl?: string;
testIdBuilder?: TestIdBuilder;
}
export interface CheckboxesProps
@ -258,10 +260,14 @@ export default class CheckboxesControl extends React.Component<
translate: __,
optionType,
menuTpl,
data
data,
testIdBuilder
} = this.props;
const labelText = String(option[labelField || 'label']);
const optionLabelClassName = option['labelClassName'];
const itemTestIdBuilder = testIdBuilder?.getChild(
'item-' + labelText || index
);
return (
<Checkbox
@ -274,6 +280,7 @@ export default class CheckboxesControl extends React.Component<
labelClassName={optionLabelClassName || labelClassName}
description={option.description}
optionType={optionType}
testIdBuilder={itemTestIdBuilder}
>
{menuTpl
? render(`checkboxes/${index}`, menuTpl, {

View File

@ -1,6 +1,8 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import cloneDeep from 'lodash/cloneDeep';
import isNumber from 'lodash/isNumber';
import get from 'lodash/get';
import {
FormItem,
FormControlProps,
@ -57,6 +59,8 @@ import type {SchemaTokenizeableString} from '../../Schema';
import isPlainObject from 'lodash/isPlainObject';
import isEqual from 'lodash/isEqual';
import type {TestIdBuilder} from 'amis-core';
export type ComboCondition = {
test: string;
items: Array<ComboSubControl>;
@ -75,6 +79,7 @@ export type ComboSubControl = SchemaObject & {
*
*/
columnClassName?: SchemaClassName;
testid?: string;
};
/**
@ -283,6 +288,7 @@ export interface ComboControlSchema extends FormBaseControlSchema {
maxLengthValidateFailed?: string;
};
updatePristineAfterStoreDataReInit?: boolean;
testIdBuilder?: TestIdBuilder;
}
export type ComboRendererEvent = 'add' | 'delete' | 'tabsChange';
@ -1390,9 +1396,10 @@ export default class ComboControl extends React.Component<ComboProps> {
removable,
deleteBtn,
mobileUI,
data
data,
testIdBuilder
} = this.props;
const delTestIdBuilder = testIdBuilder?.getChild(`delete-btn-${index}`);
const finnalRemovable =
store.removable !== false && // minLength ?
!disabled && // 控件自身是否禁用
@ -1419,6 +1426,7 @@ export default class ComboControl extends React.Component<ComboProps> {
'Combo-delController',
deleteBtn ? deleteBtn.className : ''
),
testIdBuilder: delTestIdBuilder,
onClick: (e: any) => {
if (!deleteBtn.onClick) {
this.deleteItem(index);
@ -1456,7 +1464,8 @@ export default class ComboControl extends React.Component<ComboProps> {
type: 'button',
className: cx('Combo-delController'),
label: deleteBtn,
onClick: this.deleteItem.bind(this, index)
onClick: this.deleteItem.bind(this, index),
testIdBuilder: delTestIdBuilder
});
}
@ -1468,6 +1477,7 @@ export default class ComboControl extends React.Component<ComboProps> {
className={cx(`Combo-delBtn ${!store.removable ? 'is-disabled' : ''}`)}
data-tooltip={!mobileUI ? __('delete') : null}
data-position="bottom"
{...delTestIdBuilder?.getTestId()}
>
{deleteIcon ? (
<i className={deleteIcon} />
@ -1495,10 +1505,12 @@ export default class ComboControl extends React.Component<ComboProps> {
addIcon,
conditions,
translate: __,
tabsMode
tabsMode,
testIdBuilder
} = this.props;
const hasConditions = Array.isArray(conditions) && conditions.length;
const addBtnTestIdBuilder = testIdBuilder?.getChild('add-button');
return (
<>
{store.addable &&
@ -1513,7 +1525,8 @@ export default class ComboControl extends React.Component<ComboProps> {
level: 'info',
size: 'sm',
closeOnClick: true,
btnClassName: addButtonClassName
btnClassName: addButtonClassName,
testIdBuilder: addBtnTestIdBuilder
},
{
buttons: conditions?.map(item => ({
@ -1534,12 +1547,14 @@ export default class ComboControl extends React.Component<ComboProps> {
render('add-button', {
...addBtn,
type: 'button',
testIdBuilder: addBtnTestIdBuilder,
onClick: () => this.addItem()
})
) : (
<Button
className={cx(`Combo-addBtn`, addButtonClassName)}
onClick={this.addItem}
testIdBuilder={addBtnTestIdBuilder}
>
{addIcon ? <Icon icon="plus-fine" className="icon" /> : null}
<span>{__(addButtonText || 'add')}</span>
@ -1799,8 +1814,19 @@ export default class ComboControl extends React.Component<ComboProps> {
lazyLoad,
translate: __,
static: isStatic,
testIdBuilder,
updatePristineAfterStoreDataReInit
} = this.props;
const finnalItems = Array.isArray(finnalControls)
? finnalControls.map(item => {
const indexKey = index !== undefined && index >= 0 ? `-${index}` : '';
const key = `item-${item.testid || item.id}` + indexKey;
return {
...item,
testIdBuilder: testIdBuilder?.getChild(key)
};
})
: finnalControls;
// 单个
if (!multiple) {
@ -1808,7 +1834,7 @@ export default class ComboControl extends React.Component<ComboProps> {
'single',
{
type: 'form',
body: finnalControls,
body: finnalItems,
wrapperComponent: 'div',
wrapWithPanel: false,
mode: multiLine ? subFormMode || 'normal' : 'row',
@ -1835,7 +1861,7 @@ export default class ComboControl extends React.Component<ComboProps> {
`multiple/${index}`,
{
type: 'form',
body: finnalControls,
body: finnalItems,
wrapperComponent: 'div',
wrapWithPanel: false,
mode: tabsMode ? subFormMode : multiLine ? subFormMode : 'row',

View File

@ -16,6 +16,8 @@ import {localeable, LocaleProps} from 'amis-core';
import {FormBaseControlSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
import type {TestIdBuilder} from 'amis-core';
/**
* City
* https://aisuda.bce.baidu.com/amis/zh-CN/components/form/city
@ -86,6 +88,7 @@ export interface CityPickerProps
[propName: string]: any;
};
popOverContainer?: any;
testIdBuilder?: TestIdBuilder;
}
export interface CityDb {
@ -419,7 +422,8 @@ export class CityPicker extends React.Component<
translate: __,
loadingConfig,
popOverContainer,
itemClassName
itemClassName,
testIdBuilder
} = this.props;
const {provinceCode, cityCode, districtCode, street, db} = this.state;
@ -437,6 +441,7 @@ export class CityPicker extends React.Component<
value={provinceCode || ''}
onChange={this.handleProvinceChange}
popOverContainer={popOverContainer}
testIdBuilder={testIdBuilder?.getChild('province')}
/>
{allowCity && db.city[provinceCode] && db.city[provinceCode].length ? (
@ -451,6 +456,7 @@ export class CityPicker extends React.Component<
value={cityCode || ''}
onChange={this.handleCityChange}
popOverContainer={popOverContainer}
testIdBuilder={testIdBuilder?.getChild('city')}
/>
) : null}
@ -470,6 +476,7 @@ export class CityPicker extends React.Component<
value={districtCode || ''}
onChange={this.handleDistrictChange}
popOverContainer={popOverContainer}
testIdBuilder={testIdBuilder?.getChild('district')}
/>
) : null}
@ -481,6 +488,7 @@ export class CityPicker extends React.Component<
onBlur={this.handleStreetEnd}
placeholder={__('City.street')}
disabled={disabled}
{...testIdBuilder?.getChild('street').getTestId()}
/>
) : null}
</div>
@ -578,7 +586,8 @@ export class LocationControl extends React.Component<LocationControlProps> {
env,
mobileUI,
popOverContainer,
itemClassName
itemClassName,
testIdBuilder
} = this.props;
return mobileUI ? (
@ -607,6 +616,7 @@ export class LocationControl extends React.Component<LocationControlProps> {
joinValues={joinValues}
allowStreet={allowStreet}
disabled={disabled}
testIdBuilder={testIdBuilder}
/>
);
}

View File

@ -1416,7 +1416,7 @@ export default class FileControl extends React.Component<FileProps, FileState> {
disabled={disabled}
{...getInputProps()}
capture={capture as any}
{...testIdBuilder.getChild('input').getTestId()}
{...testIdBuilder?.getChild('input').getTestId()}
/>
{drag || isDragActive ? (
@ -1461,7 +1461,7 @@ export default class FileControl extends React.Component<FileProps, FileState> {
: ''
}
onClick={this.handleSelect}
testIdBuilder={testIdBuilder.getChild('select')}
testIdBuilder={testIdBuilder?.getChild('select')}
>
<Icon icon="upload" className="icon" />
<span>
@ -1591,7 +1591,7 @@ export default class FileControl extends React.Component<FileProps, FileState> {
{!autoUpload && !hideUploadButton && files.length ? (
<Button
level="default"
testIdBuilder={testIdBuilder.getChild('upload')}
testIdBuilder={testIdBuilder?.getChild('upload')}
disabled={!hasPending}
className={cx('FileControl-uploadBtn', btnUploadClassName)}
onClick={this.toggleUpload}

View File

@ -978,7 +978,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
isCreateMode = false,
editRowIndex?: number
): Array<any> {
const {env, enableStaticTransform} = this.props;
const {env, enableStaticTransform, testIdBuilder} = this.props;
let columns: Array<any> = Array.isArray(props.columns)
? props.columns.concat()
: [];
@ -1014,6 +1014,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.addItem.bind(this, rowIndex + offset, undefined)}
testIdBuilder={testIdBuilder?.getChild(
`addRow-${rowIndex + offset}`
)}
>
{props.addBtnIcon ? (
<Icon
@ -1049,6 +1052,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.copyItem.bind(this, rowIndex + offset, undefined)}
testIdBuilder={testIdBuilder?.getChild(
`copyRow-${rowIndex + offset}`
)}
>
{props.copyBtnIcon ? (
<Icon
@ -1147,6 +1153,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
}
disabled={disabled}
onClick={() => this.editItem(rowIndex + offset)}
testIdBuilder={testIdBuilder?.getChild(
`editRow-${rowIndex + offset}`
)}
>
{/* 兼容之前的写法 */}
{typeof props.updateBtnIcon !== 'undefined' ? (
@ -1193,6 +1202,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
props.popOverContainer || env.getModalContainer
}
onClick={this.confirmEdit}
testIdBuilder={testIdBuilder?.getChild(
`confirmRow-${rowIndex + offset}`
)}
>
{props.confirmBtnIcon ? (
<Icon
@ -1230,6 +1242,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
props.popOverContainer || env.getModalContainer
}
onClick={this.cancelEdit}
testIdBuilder={testIdBuilder?.getChild(
`cancelRow-${rowIndex + offset}`
)}
>
{props.cancelBtnIcon ? (
<Icon
@ -1285,6 +1300,9 @@ export default class FormTable extends React.Component<TableProps, TableState> {
tooltipContainer={props.popOverContainer || env.getModalContainer}
disabled={disabled}
onClick={this.removeItem.bind(this, rowIndex + offset)}
testIdBuilder={testIdBuilder?.getChild(
`delRow-${rowIndex + offset}`
)}
>
{props.deleteBtnIcon ? (
<Icon
@ -1604,7 +1622,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
showFooterAddBtn,
footerAddBtn,
toolbarClassName,
onEvent
onEvent,
testIdBuilder
} = this.props;
const maxLength = this.resolveVariableProps(this.props, 'maxLength');
@ -1664,7 +1683,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
offset,
rowClassName,
rowClassNameExpr,
onPristineChange: this.handlePristineChange
onPristineChange: this.handlePristineChange,
testIdBuilder: testIdBuilder?.getChild('table')
}
)}
{(!isStatic &&
@ -1687,7 +1707,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
},
{
disabled: this.computedAddBtnDisabled(),
onClick: () => this.addItem(this.state.items.length)
onClick: () => this.addItem(this.state.items.length),
testIdBuilder: testIdBuilder?.getChild('add')
}
)
: null}
@ -1703,7 +1724,8 @@ export default class FormTable extends React.Component<TableProps, TableState> {
perPage,
total: this.state.items.length,
onPageChange: this.handlePageChange,
className: 'InputTable-pager'
className: 'InputTable-pager',
testIdBuilder: testIdBuilder?.getChild('page')
}
)
: null}

View File

@ -575,7 +575,8 @@ export default class TagControl extends React.PureComponent<
valueField,
env,
mobileUI,
labelField
labelField,
testIdBuilder
} = this.props;
const term = this.state.inputValue;
@ -633,6 +634,7 @@ export default class TagControl extends React.PureComponent<
popOverContainer={popOverContainer || env.getModalContainer}
allowInput={!mobileUI || (mobileUI && !options?.length)}
mobileUI={mobileUI}
testIdBuilder={testIdBuilder?.getChild('resule-box')}
>
{loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" />
@ -714,6 +716,7 @@ export default class TagControl extends React.PureComponent<
options={finnalOptions}
itemRender={this.renderItem}
highlightIndex={highlightedIndex}
testIdBuilder={testIdBuilder?.getChild('options')}
getItemProps={({
item,
index

View File

@ -801,7 +801,7 @@ export default class TextControl extends React.PureComponent<
}
)}
onClick={this.handleClick}
{...testIdBuilder.getTestId()}
{...testIdBuilder?.getTestId()}
>
<>
{filteredPlaceholder &&
@ -1008,7 +1008,7 @@ export default class TextControl extends React.PureComponent<
inputControlClassName,
inputOnly ? className : ''
)}
{...testIdBuilder.getTestId()}
{...testIdBuilder?.getTestId()}
>
{prefix ? (
<span className={cx('TextControl-inputPrefix')}>
@ -1036,7 +1036,7 @@ export default class TextControl extends React.PureComponent<
className={cx(nativeInputClassName, {
'TextControl-input-password': type === 'password' && revealPassword
})}
{...testIdBuilder.getChild('input').getTestId()}
{...testIdBuilder?.getChild('input').getTestId()}
/>
{clearable && !disabled && !readOnly && value ? (
<a onClick={this.clearValue} className={cx('TextControl-clear')}>

View File

@ -536,7 +536,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
itemRender={menuTpl ? this.renderOptionItem : undefined}
enableDefaultIcon={enableDefaultIcon}
mobileUI={mobileUI}
testIdBuilder={testIdBuilder.getChild('tree')}
testIdBuilder={testIdBuilder?.getChild('tree')}
/>
);
@ -546,7 +546,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
'is-sticky': searchable && searchConfig?.sticky,
'h-auto': heightAuto
})}
{...testIdBuilder.getChild('control').getTestId()}
{...testIdBuilder?.getChild('control').getTestId()}
>
<Spinner
size="sm"
@ -567,7 +567,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
{...omit(searchConfig, 'className', 'sticky')}
onSearch={this.handleSearch}
mobileUI={mobileUI}
testIdBuilder={testIdBuilder.getChild('search')}
testIdBuilder={testIdBuilder?.getChild('search')}
/>
{TreeCmpt}
</>

View File

@ -180,7 +180,8 @@ export default class ListControl extends React.Component<ListProps, any> {
data,
labelField,
listClassName,
translate: __
translate: __,
testIdBuilder
} = this.props;
let body: JSX.Element | null = null;
@ -202,6 +203,9 @@ export default class ListControl extends React.Component<ListProps, any> {
? this.handleDBClick.bind(this, option)
: undefined
}
{...testIdBuilder
?.getChild(`options-${option.value || key}`)
.getTestId()}
>
{itemSchema
? render(

View File

@ -629,7 +629,8 @@ export default class PickerControl extends React.PureComponent<
valueField,
embed,
source,
strictMode
strictMode,
testIdBuilder
} = this.props;
const {maxTagCount, overflowTagPopoverInCRUD, displayPosition} =
this.getOverflowConfig();
@ -641,6 +642,7 @@ export default class PickerControl extends React.PureComponent<
options: source ? [] : options,
multiple,
strictMode,
testIdBuilder: testIdBuilder?.getChild('body-schema'),
onSelect: embed
? (selectedItems: Array<any>, unSelectedItems: Array<any>) => {
// 选择行后crud 会给出连续多次事件且selectedItems会变化会导致初始化和点击无效
@ -712,7 +714,8 @@ export default class PickerControl extends React.PureComponent<
themeCss,
css,
id,
classPrefix: ns
classPrefix: ns,
testIdBuilder
} = this.props;
return (
<div className={cx(`PickerControl`, {'is-mobile': mobileUI}, className)}>
@ -753,7 +756,10 @@ export default class PickerControl extends React.PureComponent<
</div>
) : null}
<div className={cx('Picker-valueWrap')}>
<div
className={cx('Picker-valueWrap')}
{...testIdBuilder?.getTestId()}
>
{this.renderValues()}
<input
@ -773,7 +779,11 @@ export default class PickerControl extends React.PureComponent<
</a>
) : null}
<span onClick={this.open} className={cx('Picker-btn')}>
<span
onClick={this.open}
className={cx('Picker-btn')}
{...testIdBuilder?.getChild('picker-btn').getTestId()}
>
<Icon
icon="window-restore"
className={cx(
@ -802,7 +812,8 @@ export default class PickerControl extends React.PureComponent<
className: modalClassName,
body: {
children: this.renderBody
}
},
testIdBuilder: testIdBuilder?.getChild('modal')
},
{
key: 'modal',

View File

@ -6,7 +6,8 @@ import {
OptionsControlProps,
Option,
FormOptionsControl,
resolveEventData
resolveEventData,
TestIdBuilder
} from 'amis-core';
import {autobind, isEmpty, createObject} from 'amis-core';
import {ActionObject} from 'amis-core';
@ -37,6 +38,7 @@ export interface RadiosProps extends OptionsControlProps {
labelClassName?: string;
/** 选项CSS类名 */
optionClassName?: string;
testIdBuilder?: TestIdBuilder;
}
export default class RadiosControl extends React.Component<RadiosProps, any> {
@ -126,7 +128,8 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
data,
translate: __,
optionType,
level
level,
testIdBuilder
} = this.props;
return (
@ -151,6 +154,7 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
itemClassName={itemClassName}
optionType={optionType}
level={level}
testIdBuilder={testIdBuilder}
/>
);
}

View File

@ -418,7 +418,9 @@ export default class SelectControl extends React.Component<SelectProps, any> {
showNativeTitle: true,
className: cx('Select-option-content', optionClassName),
data: createObject(createObject(data, state), option),
testIdBuilder: testIdBuilder?.getChild('option-' + state.index)
testIdBuilder: testIdBuilder?.getChild(
'option-' + option.value || state.index
)
});
}

View File

@ -33,6 +33,7 @@ import {FormOptionsSchema, SchemaApi} from '../../Schema';
import {supportStatic} from './StaticHoc';
import {TooltipWrapperSchema} from '../TooltipWrapper';
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
import type {TestIdBuilder} from 'amis-core';
/**
* Tree
@ -130,6 +131,7 @@ export interface TreeSelectControlSchema extends FormOptionsSchema {
* Icontrue
*/
enableDefaultIcon?: boolean;
testIdBuilder?: TestIdBuilder;
}
export interface TreeSelectProps
@ -786,7 +788,7 @@ export default class TreeSelectControl extends React.Component<
<div
ref={this.container}
className={cx(`TreeSelectControl`, className)}
{...testIdBuilder.getTestId()}
{...testIdBuilder?.getTestId()}
>
<ResultBox
popOverContainer={popOverContainer || env.getModalContainer}
@ -824,6 +826,7 @@ export default class TreeSelectControl extends React.Component<
hasDropDownArrow
readOnly={mobileUI}
mobileUI={mobileUI}
testIdBuilder={testIdBuilder?.getChild('result-box')}
>
{loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" />

View File

@ -953,7 +953,8 @@ export default class List extends React.Component<ListProps, object> {
checkOnItemClick,
itemAction,
classnames: cx,
translate: __
translate: __,
testIdBuilder
} = this.props;
const hasClickActions =
onEvent &&
@ -973,6 +974,7 @@ export default class List extends React.Component<ListProps, object> {
'is-modified': item.modified,
'is-moved': item.moved
}),
testIdBuilder: testIdBuilder?.getChild(index),
selectable: store.selectable,
checkable: item.checkable,
multiple,
@ -1174,7 +1176,8 @@ export class ListItem extends React.Component<ListItemProps> {
hideCheckToggler,
checkOnItemClick,
classnames: cx,
classPrefix: ns
classPrefix: ns,
testIdBuilder
} = this.props;
if (dragging) {
@ -1193,6 +1196,7 @@ export class ListItem extends React.Component<ListItemProps> {
checked={selected}
onChange={this.handleCheck}
inline
testIdBuilder={testIdBuilder?.getChild('checkbox')}
/>
</div>
);

View File

@ -34,7 +34,7 @@ export interface CellProps extends ThemeProps {
quickEditFormRef: any;
onImageEnlarge?: any;
translate: (key: string, ...args: Array<any>) => string;
testIdBuilder: TestIdBuilder;
testIdBuilder?: TestIdBuilder;
}
export default function Cell({
@ -80,7 +80,7 @@ export default function Cell({
<td
style={style}
className={cx(column.pristine.className, stickyClassName)}
{...testIdBuilder.getTestId()}
{...testIdBuilder?.getTestId()}
>
<Checkbox
classPrefix={ns}
@ -89,7 +89,7 @@ export default function Cell({
checked={item.checked || item.partial}
disabled={item.checkdisable || !item.checkable}
onChange={onCheckboxChange}
testIdBuilder={testIdBuilder.getChild('chekbx')}
testIdBuilder={testIdBuilder?.getChild('chekbx')}
/>
</td>
);
@ -100,7 +100,7 @@ export default function Cell({
className={cx(column.pristine.className, stickyClassName, {
'is-dragDisabled': !item.draggable
})}
{...testIdBuilder.getChild('drag').getTestId()}
{...testIdBuilder?.getChild('drag').getTestId()}
>
{item.draggable ? <Icon icon="drag" className="icon" /> : null}
</td>
@ -118,7 +118,7 @@ export default function Cell({
// data-position="top"
onClick={item.toggleExpanded}
{...testIdBuilder
.getChild(item.expanded ? 'fold' : 'expand')
?.getChild(item.expanded ? 'fold' : 'expand')
.getTestId()}
>
<Icon icon="right-arrow-bold" className="icon" />
@ -151,7 +151,7 @@ export default function Cell({
key="retryBtn"
onClick={item.resetDefered}
data-tooltip={__('Options.retry', {reason: item.error})}
{...testIdBuilder.getChild('retry').getTestId()}
{...testIdBuilder?.getChild('retry').getTestId()}
>
<Icon icon="retry" className="icon" />
</a>
@ -163,7 +163,7 @@ export default function Cell({
// data-position="top"
onClick={item.toggleExpanded}
{...testIdBuilder
.getChild(item.expanded ? 'fold' : 'expand')
?.getChild(item.expanded ? 'fold' : 'expand')
.getTestId()}
>
<Icon icon="right-arrow-bold" className="icon" />
@ -187,7 +187,7 @@ export default function Cell({
draggable
onDragStart={onDragStart}
className={cx('Table-dragBtn')}
{...testIdBuilder.getChild('drag').getTestId()}
{...testIdBuilder?.getChild('drag').getTestId()}
>
<Icon icon="drag" className="icon" />
</a>
@ -250,7 +250,8 @@ export default function Cell({
column.pristine.className,
stickyClassName,
addtionalClassName
)
),
testIdBuilder: testIdBuilder?.getChild(column.name || column.value)
};
delete subProps.label;
@ -259,8 +260,7 @@ export default function Cell({
{
...column.pristine,
column: column.pristine,
type: 'cell',
testid: testIdBuilder.getTestIdValue()
type: 'cell'
},
subProps
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import {ClassNamesFn, RendererEvent} from 'amis-core';
import {ClassNamesFn, RendererEvent, autobind} from 'amis-core';
import {SchemaNode, ActionObject} from 'amis-core';
import TableRow from './TableRow';
@ -69,6 +69,11 @@ export class TableBody extends React.Component<TableBodyProps> {
this.props.store.initTableWidth();
}
@autobind
testIdBuilder(rowPath: string) {
return this.props.testIdBuilder?.getChild(`row-${rowPath}`);
}
renderRows(
rows: Array<any>,
columns = this.props.columns,
@ -94,19 +99,17 @@ export class TableBody extends React.Component<TableBodyProps> {
onRowDbClick,
onRowMouseEnter,
onRowMouseLeave,
store,
testIdBuilder
store
} = this.props;
return rows.map((item: IRow, rowIndex: number) => {
const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null;
const rowPath = `${indexPath ? indexPath + '/' : ''}${rowIndex}`;
const rowTestBuidr = testIdBuilder?.getChild(`row-${rowPath}`);
const doms = [
<TableRow
{...itemProps}
testIdBuilder={rowTestBuidr}
testIdBuilder={this.testIdBuilder}
store={store}
itemAction={itemAction}
classnames={cx}
@ -173,7 +176,7 @@ export class TableBody extends React.Component<TableBodyProps> {
onQuickChange={onQuickChange}
ignoreFootableContent={ignoreFootableContent}
{...rowProps}
testIdBuilder={rowTestBuidr}
testIdBuilder={this.testIdBuilder}
/>
);
}

View File

@ -10,10 +10,13 @@ import {Badge} from 'amis-ui';
import {ColorScale} from 'amis-core';
import {isPureVariable, resolveVariableAndFilter} from 'amis-core';
import type {TestIdBuilder} from 'amis-core';
export interface TableCellProps extends RendererProps {
wrapperComponent?: React.ElementType;
column: any;
contentsOnly?: boolean;
testIdBuilder?: TestIdBuilder;
}
export class TableCell extends React.Component<TableCellProps> {
@ -158,7 +161,7 @@ export class TableCell extends React.Component<TableCellProps> {
className={cx(className)}
tabIndex={tabIndex}
onKeyUp={onKeyUp}
{...testIdBuilder.getTestId()}
{...testIdBuilder?.getChild('cell').getTestId()}
>
{showBadge ? (
<Badge

View File

@ -44,7 +44,7 @@ interface TableRowProps extends Pick<RendererProps, 'render'> {
regionPrefix?: string;
checkOnItemClick?: boolean;
ignoreFootableContent?: boolean;
testIdBuilder?: TestIdBuilder;
testIdBuilder?: (key: string) => TestIdBuilder;
rowPath: string; // 整体行的路径,树形时需要父行序号/当前展开层级下的行序号
[propName: string]: any;
}
@ -323,7 +323,7 @@ export class TableRow extends React.PureComponent<
},
`Table-tr--${depth}th`
)}
{...testIdBuilder?.getTestId()}
{...testIdBuilder?.(rowPath).getTestId()}
>
{columns.map(column =>
appeard ? (

View File

@ -810,7 +810,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
: unmountOnExit
}
onSelect={this.handleSelect}
testIdBuilder={testIdBuilder.getChild(
testIdBuilder={testIdBuilder?.getChild(
`tab-${typeof tab.title === 'string' ? tab.title : index}`
)}
>
@ -854,7 +854,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
: unmountOnExit
}
onSelect={this.handleSelect}
testIdBuilder={testIdBuilder.getChild(
testIdBuilder={testIdBuilder?.getChild(
`tab-${typeof tab.title === 'string' ? tab.title : index}`
)}
>