Merge pull request #28571 from ant-design/master-to-merge-feature

chore: merge master into feature
This commit is contained in:
偏右 2020-12-28 15:44:09 +08:00 committed by GitHub
commit 9b0bed7a7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 158 additions and 143 deletions

View File

@ -1,16 +0,0 @@
name: 🧐 Auto Closer
on:
issues:
types: [labeled]
jobs:
close-by-label:
runs-on: ubuntu-latest
if: github.event.label.name == '3.x'
steps:
- name: Comment on issue
uses: peter-evans/close-issue@v1
with:
comment: "Hi @${{ github.event.issue.user.login }},<br />Current branch is off the maintenance period. We may not accept pull request or fix bug with it anymore. This topic will be auto closed.<br /><br />你好 @${{ github.event.issue.user.login }},<br />当前分支已经过了维护期。我们不会再接受对其的相关 PR 与 issue。当前 topic 会被自动关闭。"

View File

@ -47,3 +47,15 @@ jobs:
Hello @${{ github.event.issue.user.login }}, we use GitHub issues to trace bugs or discuss plans of Ant Design. So, please [don't ask usage questions](https://github.com/ant-design/ant-design/issues/2320) here. You can try to open a new discussion in [antd discussions](https://github.com/ant-design/ant-design/discussions), select `Q&A` to ask questions, also can ask questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/antd) or [Segment Fault](https://segmentfault.com/t/antd), then apply tag `antd` and `react` to your question.
你好 @${{ github.event.issue.user.login }}Ant Design Issue 板块是用于 bug 反馈与需求讨论的地方。请[勿询问如何使用的问题](https://github.com/ant-design/ant-design/issues/2320),你可以试着在 [antd discussions](https://github.com/ant-design/ant-design/discussions) 新开一个 discussion选择 `Q&A` 类别进行提问,也可以在 [Stack Overflow](http://stackoverflow.com/questions/tagged/antd) 或者 [Segment Fault](https://segmentfault.com/t/antd) 中提问(记得添加 `antd` 和 `react` 标签哦~)。
- name: 3.x
if: github.event.label.name == '3.x'
uses: actions-cool/issues-helper@v1.2
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.ANT_BOT_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hi @${{ github.event.issue.user.login }}. Current version (3.x) is off the maintenance period. We may not accept pull request or fix bug with it anymore. This topic will be auto closed.
你好 @${{ github.event.issue.user.login }}当前版本3.x已经过了维护期。我们不会再接受对其的相关 PR 与 issue。当前 topic 会被自动关闭。

View File

@ -1,6 +1,4 @@
/**
* @jest-environment node
*/
/** @jest-environment node */
import getScroll from '../getScroll';
describe('getScroll node', () => {

View File

@ -1,5 +1,6 @@
/**
* Wrap of sub component which need use as Button capacity (like Icon component).
*
* This helps accessibility reader to tread as a interactive button to operation.
*/
import * as React from 'react';

View File

@ -5,12 +5,10 @@ export const tuple = <T extends string[]>(...args: T) => args;
export const tupleNum = <T extends number[]>(...args: T) => args;
/**
* https://stackoverflow.com/a/59187769
* Extract the type of an element of an array/tuple without performing indexing
* https://stackoverflow.com/a/59187769 Extract the type of an element of an array/tuple without
* performing indexing
*/
export type ElementOf<T> = T extends (infer E)[] ? E : T extends readonly (infer F)[] ? F : never;
/**
* https://github.com/Microsoft/TypeScript/issues/29729
*/
/** https://github.com/Microsoft/TypeScript/issues/29729 */
export type LiteralUnion<T extends U, U> = T | (U & {});

View File

@ -19,9 +19,7 @@ function getDefaultTarget() {
// Affix
export interface AffixProps {
/**
*
*/
/** 距离窗口顶部达到指定偏移量后触发 */
offsetTop?: number;
/** 距离窗口底部达到指定偏移量后触发 */
offsetBottom?: number;

View File

@ -17,9 +17,7 @@ import ErrorBoundary from './ErrorBoundary';
import { replaceElement } from '../_util/reactNode';
export interface AlertProps {
/**
* Type of Alert styles, options:`success`, `info`, `warning`, `error`
*/
/** Type of Alert styles, options:`success`, `info`, `warning`, `error` */
type?: 'success' | 'info' | 'warning' | 'error';
/** Whether Alert can be closed */
closable?: boolean;

View File

@ -22,7 +22,7 @@ export interface AvatarProps {
/** Srcset of image avatar */
srcSet?: string;
draggable?: boolean;
/** icon to be used in avatar */
/** Icon to be used in avatar */
icon?: React.ReactNode;
style?: React.CSSProperties;
prefixCls?: string;

View File

@ -22,7 +22,7 @@ export interface BadgeProps {
showZero?: boolean;
/** Max count to show */
overflowCount?: number;
/** whether to show red dot without number */
/** Whether to show red dot without number */
dot?: boolean;
style?: React.CSSProperties;
prefixCls?: string;

View File

@ -26,10 +26,7 @@ const BreadcrumbItem: BreadcrumbItemInterface = ({
}) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
/**
* if overlay is have
* Wrap a DropDown
*/
/** If overlay is have Wrap a DropDown */
const renderBreadcrumbNode = (breadcrumbItem: React.ReactNode) => {
if (overlay) {
return (

View File

@ -140,7 +140,7 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
icon,
ghost = false,
block = false,
/** if we extract items here, we dont need use omit.js */
/** If we extract items here, we don't need use omit.js */
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
htmlType = 'button' as ButtonProps['htmlType'],
...rest

View File

@ -93,7 +93,7 @@ export interface CascaderProps {
name?: string;
/** 输入框id */
id?: string;
/** whether has border style */
/** Whether has border style */
bordered?: boolean;
/** 禁用 */
disabled?: boolean;
@ -115,7 +115,7 @@ export interface CascaderProps {
inputPrefixCls?: string;
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
popupVisible?: boolean;
/** use this after antd@3.7.0 */
/** Use this after antd@3.7.0 */
fieldNames?: FieldNamesType;
suffixIcon?: React.ReactNode;
dropdownRender?: (menus: React.ReactNode) => React.ReactNode;

View File

@ -9,7 +9,7 @@ export interface CommentProps {
author?: React.ReactNode;
/** The element to display as the comment avatar - generally an antd Avatar */
avatar?: React.ReactNode;
/** className of comment */
/** ClassName of comment */
className?: string;
/** The main content of the comment */
content: React.ReactNode;

View File

@ -1,6 +1,4 @@
/**
* Created by Andrey Gayvoronsky on 13/04/16.
*/
/** Created by Andrey Gayvoronsky on 13/04/16. */
import CalendarLocale from 'rc-picker/lib/locale/ru_RU';
import TimePickerLocale from '../../time-picker/locale/ru_RU';

View File

@ -31,7 +31,7 @@ export interface DrawerProps {
mask?: boolean;
maskStyle?: React.CSSProperties;
style?: React.CSSProperties;
/** wrapper dom node style of header and body */
/** Wrapper dom node style of header and body */
drawerStyle?: React.CSSProperties;
headerStyle?: React.CSSProperties;
bodyStyle?: React.CSSProperties;

View File

@ -16,9 +16,7 @@ export interface EmptyProps {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
/**
* @since 3.16.0
*/
/** @since 3.16.0 */
imageStyle?: React.CSSProperties;
image?: React.ReactNode;
description?: React.ReactNode;

View File

@ -30,6 +30,7 @@ export default function ErrorList({
if (changedVisible) {
/**
* We trigger in sync to avoid dom shaking but this get warning in react 16.13.
*
* So use Promise to keep in micro async to handle this.
* https://github.com/ant-design/ant-design/issues/21698#issuecomment-593743485
*/

View File

@ -17,9 +17,7 @@ interface FormItemInputMiscProps {
hasFeedback?: boolean;
validateStatus?: ValidateStatus;
onDomErrorVisibleChange: (visible: boolean) => void;
/**
* @private Internal usage, do not use in any of your production.
*/
/** @private Internal usage, do not use in any of your production. */
_internalItemRender?: {
mark: string;
render: (

View File

@ -7,10 +7,7 @@ import { FormLabelAlign } from './interface';
import { RequiredMark } from './Form';
import { ValidateStatus } from './FormItem';
/**
* Form Context
* Set top form style and pass to Form Item usage.
*/
/** Form Context. Set top form style and pass to Form Item usage. */
export interface FormContextProps {
vertical: boolean;
name?: string;
@ -28,10 +25,7 @@ export const FormContext = React.createContext<FormContextProps>({
itemRef: (() => {}) as any,
});
/**
* Form Item Context
* Used for Form noStyle Item error collection
*/
/** Form Item Context. Used for Form noStyle Item error collection */
export interface FormItemContextProps {
updateItemErrors: (name: string, errors: string[]) => void;
}
@ -40,10 +34,7 @@ export const FormItemContext = React.createContext<FormItemContextProps>({
updateItemErrors: () => {},
});
/**
* Form Provider
*
*/
/** Form Provider */
export interface FormProviderProps extends Omit<RcFormProviderProps, 'validateMessages'> {}
export const FormProvider: React.FC<FormProviderProps> = props => {
@ -51,9 +42,7 @@ export const FormProvider: React.FC<FormProviderProps> = props => {
return <RcFormProvider {...providerProps} />;
};
/**
* Used for ErrorList only
*/
/** Used for ErrorList only */
export interface FormItemPrefixContextProps {
prefixCls: string;
status?: ValidateStatus;

View File

@ -1,9 +1,7 @@
import * as React from 'react';
import useForceUpdate from '../../_util/hooks/useForceUpdate';
/**
* Always debounce error to avoid [error -> null -> error] blink
*/
/** Always debounce error to avoid [error -> null -> error] blink */
export default function useCacheErrors(
errors: React.ReactNode[],
changeTrigger: (visible: boolean) => void,

View File

@ -17,9 +17,7 @@ function hasAddon(props: InputProps | ClearableInputProps) {
return !!(props.addonBefore || props.addonAfter);
}
/**
* This basic props required for input and textarea.
*/
/** This basic props required for input and textarea. */
interface BasicProps {
prefixCls: string;
inputType: typeof ClearableInputType[number];
@ -36,9 +34,7 @@ interface BasicProps {
bordered: boolean;
}
/**
* This props only for input.
*/
/** This props only for input. */
interface ClearableInputProps extends BasicProps {
size?: SizeType;
suffix?: React.ReactNode;

View File

@ -58,12 +58,12 @@ describe('TextArea', () => {
const wrapper = mount(
<TextArea onKeyDown={fakeHandleKeyDown} onPressEnter={fakeHandlePressEnter} />,
);
/** keyCode 65 is A */
/** KeyCode 65 is A */
wrapper.find('textarea').simulate('keydown', { keyCode: 65 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(1);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(0);
/** keyCode 13 is Enter */
/** KeyCode 13 is Enter */
wrapper.find('textarea').simulate('keydown', { keyCode: 13 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(2);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(1);

View File

@ -60,6 +60,7 @@ export default function confirm(config: ModalFuncProps) {
function render({ okText, cancelText, prefixCls, ...props }: any) {
/**
* https://github.com/ant-design/ant-design/issues/23623
*
* Sync render blocks React event. Let's make this async.
*/
setTimeout(() => {

View File

@ -21,8 +21,11 @@ class App extends React.Component {
state = {
visible: false,
disabled: true,
bounds: { left: 0, top: 0, bottom: 0, right: 0 },
};
draggleRef = React.createRef();
showModal = () => {
this.setState({
visible: true,
@ -43,7 +46,21 @@ class App extends React.Component {
});
};
onStart = (event, uiData) => {
const { clientWidth, clientHeight } = window?.document?.documentElement;
const targetRect = this.draggleRef?.current?.getBoundingClientRect();
this.setState({
bounds: {
left: -targetRect?.left + uiData?.x,
right: clientWidth - (targetRect?.right - uiData?.x),
top: -targetRect?.top + uiData?.y,
bottom: clientHeight - (targetRect?.bottom - uiData?.y),
},
});
};
render() {
const { bounds, disabled, visible } = this.state;
return (
<>
<Button onClick={this.showModal}>Open Draggable Modal</Button>
@ -55,7 +72,7 @@ class App extends React.Component {
cursor: 'move',
}}
onMouseOver={() => {
if (this.state.disabled) {
if (disabled) {
this.setState({
disabled: false,
});
@ -75,10 +92,18 @@ class App extends React.Component {
Draggable Modal
</div>
}
visible={this.state.visible}
visible={visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
modalRender={modal => <Draggable disabled={this.state.disabled}>{modal}</Draggable>}
modalRender={modal => (
<Draggable
disabled={disabled}
bounds={bounds}
onStart={(event, uiData) => this.onStart(event, uiData)}
>
<div ref={this.draggleRef}>{modal}</div>
</Draggable>
)}
>
<p>
Just don&apos;t learn physics at school and your life will be full of magic and

View File

@ -11,12 +11,13 @@ interface LineProps extends ProgressProps {
}
/**
* @example
* {
* '0%': '#afc163',
* '75%': '#009900',
* '50%': 'green', ====> '#afc163 0%, #66FF00 25%, #00CC00 50%, #009900 75%, #ffffff 100%'
* '25%': '#66FF00',
* '100%': '#ffffff'
* "0%": "#afc163",
* "75%": "#009900",
* "50%": "green", // ====> '#afc163 0%, #66FF00 25%, #00CC00 50%, #009900 75%, #ffffff 100%'
* "25%": "#66FF00",
* "100%": "#ffffff"
* }
*/
export const sortGradient = (gradients: StringGradients) => {
@ -35,19 +36,17 @@ export const sortGradient = (gradients: StringGradients) => {
};
/**
* {
* '0%': '#afc163',
* '25%': '#66FF00',
* '50%': '#00CC00', ====> linear-gradient(to right, #afc163 0%, #66FF00 25%,
* '75%': '#009900', #00CC00 50%, #009900 75%, #ffffff 100%)
* '100%': '#ffffff'
* }
* Then this man came to realize the truth: Besides six pence, there is the moon. Besides bread and
* butter, there is the bug. And... Besides women, there is the code.
*
* Then this man came to realize the truth:
* Besides six pence, there is the moon.
* Besides bread and butter, there is the bug.
* And...
* Besides women, there is the code.
* @example
* {
* "0%": "#afc163",
* "25%": "#66FF00",
* "50%": "#00CC00", // ====> linear-gradient(to right, #afc163 0%, #66FF00 25%,
* "75%": "#009900", // #00CC00 50%, #009900 75%, #ffffff 100%)
* "100%": "#ffffff"
* }
*/
export const handleGradient = (strokeColor: ProgressGradient, directionConfig: DirectionType) => {
const {

View File

@ -43,9 +43,8 @@ export interface ResultProps {
const ExceptionStatus = Object.keys(ExceptionMap);
/**
* render icon
* if ExceptionStatus includes ,render svg image
* else render iconNode
* Render icon if ExceptionStatus includes ,render svg image else render iconNode
*
* @param prefixCls
* @param {status, icon}
*/

View File

@ -5,10 +5,7 @@ export interface ColumnProps<RecordType> extends ColumnType<RecordType> {
}
/* istanbul ignore next */
/**
* This is a syntactic sugar for `columns` prop.
* So HOC will not work on this.
*/
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
// eslint-disable-next-line no-unused-vars
function Column<RecordType>(_: ColumnProps<RecordType>) {
return null;

View File

@ -9,10 +9,7 @@ export interface ColumnGroupProps<RecordType> extends Omit<ColumnType<RecordType
}
/* istanbul ignore next */
/**
* This is a syntactic sugar for `columns` prop.
* So HOC will not work on this.
*/
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
// eslint-disable-next-line no-unused-vars
function ColumnGroup<RecordType>(_: ColumnGroupProps<RecordType>) {
return null;

View File

@ -233,10 +233,9 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
};
/**
* Controlled state in `columns` is not a good idea that makes too many code (1000+ line?)
* to read state out and then put it back to title render.
* Move these code into `hooks` but still too complex.
* We should provides Table props like `sorter` & `filter` to handle control in next big version.
* Controlled state in `columns` is not a good idea that makes too many code (1000+ line?) to read
* state out and then put it back to title render. Move these code into `hooks` but still too
* complex. We should provides Table props like `sorter` & `filter` to handle control in next big version.
*/
// ============================ Sorter =============================

View File

@ -239,9 +239,8 @@ describe('Table.pagination', () => {
});
/**
* `pagination` is not designed to accept `true` value,
* but in practice, many people assign `true` to `pagination`,
* since they misunderstand that `pagination` can accept a boolean value.
* `pagination` is not designed to accept `true` value, but in practice, many people assign `true`
* to `pagination`, since they misunderstand that `pagination` can accept a boolean value.
*/
it('Accepts pagination as true', () => {
const wrapper = mount(createTable({ pagination: true }));

View File

@ -1057,6 +1057,37 @@ describe('Table.rowSelection', () => {
expect(getIndeterminateSelection(wrapper)).toEqual([]);
expect(onChange.mock.calls[2][0]).toEqual(['Jerry Tom Tom']);
});
it('should support `childrenColumnName`', () => {
const onChange = jest.fn();
const table = createTable({
dataSource: [
{
key: 0,
name: 'Jack',
childList: [
{ key: 1, name: 'Light' },
{ key: 2, name: 'Bamboo' },
],
},
],
expandable: {
childrenColumnName: 'childList',
defaultExpandAllRows: true,
},
rowSelection: {
checkStrictly: false,
onChange,
},
});
const wrapper = mount(table);
const checkboxes = wrapper.find('input');
expect(checkboxes).toHaveLength(1 + 3);
checkboxes.at(1).simulate('change', { target: { checked: true } });
expect(getSelections(wrapper)).toEqual([0, 1, 2]);
});
});
it('warns when set `indeterminate` using `rowSelection.getCheckboxProps` is not allowed with tree structured data.', () => {
resetWarned();

View File

@ -117,8 +117,11 @@ export default function useSelection<RecordType>(
() =>
checkStrictly
? { keyEntities: null }
: convertDataToEntities((data as unknown) as DataNode[], undefined, getRowKey as any),
[data, getRowKey, checkStrictly],
: convertDataToEntities((data as unknown) as DataNode[], {
externalGetKey: getRowKey as any,
childrenPropName: childrenColumnName,
}),
[data, getRowKey, checkStrictly, childrenColumnName],
);
// Get flatten data

View File

@ -7,8 +7,9 @@ export interface CheckableTagProps {
className?: string;
style?: React.CSSProperties;
/**
* @description it is an absolute controlled component and has no uncontrolled mode.
* @description.zh-CN
* It is an absolute controlled component and has no uncontrolled mode.
*
* .zh-cn
*/
checked: boolean;
onChange?: (checked: boolean) => void;

View File

@ -1,6 +1,4 @@
/**
* Created by Andrey Gayvoronsky on 13/04/16.
*/
/** Created by Andrey Gayvoronsky on 13/04/16. */
import { TimePickerLocale } from '../index';
const locale: TimePickerLocale = {

View File

@ -25,7 +25,7 @@ const TimelineItem: React.FC<TimelineItemProps> = ({
color = 'blue',
dot,
pending = false,
position /** dead, but do not pass in <li {...omit()} */,
position /** Dead, but do not pass in <li {...omit()} */,
label,
children,
...restProps

View File

@ -52,7 +52,7 @@ export interface TransferListProps<RecordType> extends TransferLocale {
onItemSelectAll: (dataSource: string[], checkAll: boolean) => void;
onItemRemove?: (keys: string[]) => void;
handleClear: () => void;
/** render item */
/** Render item */
render?: (item: RecordType) => RenderResult;
showSearch?: boolean;
searchPlaceholder: string;

View File

@ -99,7 +99,7 @@ export interface TreeProps extends Omit<RcTreeProps, 'prefixCls' | 'showLine' |
multiple?: boolean;
/** 是否自动展开父节点 */
autoExpandParent?: boolean;
/** checkable状态下节点选择完全受控父子节点选中状态不再关联 */
/** Checkable状态下节点选择完全受控父子节点选中状态不再关联 */
checkStrictly?: boolean;
/** 是否支持选中 */
checkable?: boolean;

View File

@ -104,7 +104,7 @@ Functional color represents a clear message as well as status, such as success,
### Neutral Color
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*UE0tRqE2hkwAAAAAAAAAAABkARQnAQ">
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/zos/antfincdn/8yMmB1lcD%24/colors.jpg">
Neutral color is mainly used in a large part of the text interface, in addition to the background, borders, dividing lines, and other scenes are also very common. Neutral color definition needs to consider the difference between dark background and light background, while incorporating the WCAG 2.0 standard. The neutral color of Ant Design is based on transparency, as shown on the right:

View File

@ -111,7 +111,7 @@ ReactDOM.render(<ColorPaletteTool />, mountNode);
### 中性色
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*UE0tRqE2hkwAAAAAAAAAAABkARQnAQ">
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/zos/antfincdn/8yMmB1lcD%24/colors.jpg">
Ant Design 的中性色主要被大量的应用在界面的文字部分,此外背景、边框、分割线、等场景中也非常常见。产品中性色的定义需要考虑深色背景以及浅色背景的差异,同时结合 WCAG 2.0 标准。Ant Design 的中性色在落地的时候是按照透明度的方式实现的,具体色板如右图:

View File

@ -143,7 +143,7 @@
"rc-tabs": "~11.7.0",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.0.0",
"rc-tree": "~4.0.0",
"rc-tree": "~4.1.0",
"rc-tree-select": "~4.3.0",
"rc-upload": "~3.3.4",
"rc-util": "^5.1.0",
@ -234,6 +234,7 @@
"node-fetch": "^2.6.0",
"open": "^7.0.3",
"prettier": "^2.2.0",
"prettier-plugin-jsdoc": "^0.2.12",
"pretty-quick": "^3.0.0",
"querystring": "^0.2.0",
"rc-footer": "^0.6.3",

View File

@ -1,6 +1,7 @@
/**
* convert dark.less into js vars
* Convert dark.less into js vars
*
* @example
* const darkVars = require('./dark-vars');
*/
const fs = require('fs');

View File

@ -1,6 +1,7 @@
/**
* convert default.less into js vars
* Convert default.less into js vars
*
* @example
* const darkVars = require('./default-vars');
*/
const fs = require('fs');

View File

@ -1,7 +1,5 @@
/* eslint-disable no-console */
/**
* Generate legacy locale file as shadow of `/locale` to `/locale-provider`.
*/
/** Generate legacy locale file as shadow of `/locale` to `/locale-provider`. */
const glob = require('glob');
const fs = require('fs');

View File

@ -1,5 +1,6 @@
/**
* Empty component for app shell
*
* See https://github.com/NekR/offline-plugin/blob/master/docs/app-shell.md
*/
import React from 'react';

View File

@ -10,7 +10,7 @@ const USE_REPLACEMENT = false;
const testDist = process.env.LIB_DIR === 'dist';
/**
* rc component will generate id for aria usage.
* Rc component will generate id for aria usage.
* It's created as `test-uuid` when env === 'test'.
* Or `f7fa7a3c-a675-47bc-912e-0c45fb6a74d9`(randomly) when not test env.
* So we need hack of this to modify the `aria-controls`.