Merge branch 'master' into feature-merge-master

This commit is contained in:
二货机器人 2022-08-24 15:46:36 +08:00
commit 9b99c815fa
19 changed files with 384 additions and 238 deletions

View File

@ -1,5 +1,5 @@
import { mount } from 'enzyme';
import React from 'react';
import { render } from '../../../tests/utils';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import Comment from '../index';
@ -9,7 +9,7 @@ describe('Comment', () => {
rtlTest(Comment);
it('should support empty actions', () => {
const wrapper = mount(
const wrapper = render(
<Comment
actions={[]}
author={<a>Han Solo</a>}
@ -23,6 +23,6 @@ describe('Comment', () => {
datetime="YYYY-MM-DD HH:mm:ss"
/>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(wrapper.container.firstChild).toMatchSnapshot();
});
});

View File

@ -43,6 +43,7 @@ import Spin from '../../spin';
import Statistic from '../../statistic';
import Steps from '../../steps';
import Switch from '../../switch';
import type { ColumnsType } from '../../table';
import Table from '../../table';
import Tabs from '../../tabs';
import Tag from '../../tag';
@ -59,7 +60,7 @@ jest.mock('rc-util/lib/Portal');
describe('ConfigProvider', () => {
describe('components', () => {
function testPair(name, renderComponent) {
function testPair(name: string, renderComponent: (props?: any) => React.ReactElement): void {
const isArray = ['Menu', 'TimePicker', 'Tooltip'].includes(name);
describe(`${name}`, () => {
// normal
@ -303,8 +304,8 @@ describe('ConfigProvider', () => {
// Grid
testPair('Grid', props => {
const rowProps = {};
const colProps = {};
const rowProps: { prefixCls?: string } = {};
const colProps: { prefixCls?: string } = {};
if (props.prefixCls) {
rowProps.prefixCls = 'prefix-row';
colProps.prefixCls = 'prefix-col';
@ -334,10 +335,10 @@ describe('ConfigProvider', () => {
// Layout
testPair('Layout', props => {
const siderProps = {};
const headerProps = {};
const contentProps = {};
const footerProps = {};
const siderProps: { prefixCls?: string } = {};
const headerProps: { prefixCls?: string } = {};
const contentProps: { prefixCls?: string } = {};
const footerProps: { prefixCls?: string } = {};
if (props.prefixCls) {
siderProps.prefixCls = 'prefix-sider';
headerProps.prefixCls = 'prefix-header';
@ -499,7 +500,7 @@ describe('ConfigProvider', () => {
// Table
testPair('Table', props => {
const columns = [
const columns: ColumnsType<any> = [
{
title: 'Name',
dataIndex: 'name',

View File

@ -1,11 +1,9 @@
import { mount } from 'enzyme';
import React, { memo, useContext, useRef, useState } from 'react';
import { render, fireEvent } from '../../../tests/utils';
import LocaleProvider, { ANT_MARK } from '..';
import LocaleContext from '../context';
const defaultLocale = {
locale: 'locale',
};
const defaultLocale = { locale: 'locale' };
// we use'memo' here in order to only render inner component while context changed.
const CacheInner = memo(() => {
const countRef = useRef(0);
@ -19,7 +17,7 @@ const CacheInner = memo(() => {
);
});
const CacheOuter = () => {
const CacheOuter: React.FC = () => {
// We use 'useState' here in order to trigger parent component rendering.
const [count, setCount] = useState(1);
const handleClick = () => {
@ -41,13 +39,13 @@ const CacheOuter = () => {
};
it("Rendering on LocaleProvider won't trigger rendering on child component.", () => {
const wrapper = mount(<CacheOuter />);
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('2');
const { container } = render(<CacheOuter />);
fireEvent.click(container.querySelector('#parent_btn')!);
expect(container.querySelector('#parent_count')?.innerHTML).toBe('2');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe('1');
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('3');
expect(container.querySelector('#child_count')?.innerHTML).toBe('1');
fireEvent.click(container.querySelector('#parent_btn')!);
expect(container.querySelector('#parent_count')?.innerHTML).toBe('3');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe('1');
expect(container.querySelector('#child_count')?.innerHTML).toBe('1');
});

View File

@ -1,12 +1,11 @@
import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { Modal } from '../..';
import { sleep } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import zhCN from '../zh_CN';
class Demo extends React.Component {
class Demo extends React.Component<{ type: string }> {
static defaultProps = {};
componentDidMount() {
@ -24,8 +23,8 @@ describe('Locale Provider demo', () => {
it('change type', async () => {
jest.useFakeTimers();
const BasicExample = () => {
const [type, setType] = React.useState('');
const BasicExample: React.FC = () => {
const [type, setType] = React.useState<string>('');
return (
<div>
<a className="about" onClick={() => setType('about')}>
@ -49,21 +48,21 @@ describe('Locale Provider demo', () => {
</div>
);
};
const wrapper = mount(<BasicExample />);
const { container } = render(<BasicExample />);
wrapper.find('.about').at(0).simulate('click');
fireEvent.click(container.querySelector('.about')!);
await act(async () => {
jest.runAllTimers();
await sleep();
});
wrapper.find('.dashboard').at(0).simulate('click');
fireEvent.click(container.querySelector('.dashboard')!);
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect(document.body.querySelectorAll('.ant-btn-primary span')[0].textContent).toBe('确 定');
expect(document.body.querySelectorAll('.ant-btn-primary span')[0]?.textContent).toBe('确 定');
Modal.destroyAll();
jest.useRealTimers();
});

View File

@ -1,8 +1,9 @@
/* eslint-disable react/no-multi-comp */
import { mount } from 'enzyme';
import MockDate from 'mockdate';
import moment from 'moment';
import React from 'react';
import { render } from '../../../tests/utils';
import type { Locale } from '..';
import LocaleProvider from '..';
import {
Calendar,
@ -16,7 +17,6 @@ import {
Transfer,
} from '../..';
import mountTest from '../../../tests/shared/mountTest';
import { act } from '../../../tests/utils';
import arEG from '../ar_EG';
import azAZ from '../az_AZ';
import bgBG from '../bg_BG';
@ -162,20 +162,12 @@ const columns = [
{
title: 'Name',
dataIndex: 'name',
filters: [
{
text: 'filter1',
value: 'filter1',
},
],
},
{
title: 'Age',
dataIndex: 'age',
filters: [{ text: 'filter1', value: 'filter1' }],
},
{ title: 'Age', dataIndex: 'age' },
];
const App = () => (
const App: React.FC = () => (
<div>
<Pagination defaultCurrent={1} total={50} showSizeChanger />
<Select showSearch style={{ width: 200 }}>
@ -188,7 +180,7 @@ const App = () => (
<Popconfirm title="Question?" visible>
<a>Click to confirm</a>
</Popconfirm>
<Transfer dataSource={[]} showSearch targetKeys={[]} render={item => item.title} />
<Transfer dataSource={[]} showSearch targetKeys={[]} render={(item: any) => item.title} />
<Calendar fullscreen={false} value={moment()} />
<Table dataSource={[]} columns={columns} />
<Modal title="Locale Modal" open getContainer={false}>
@ -214,57 +206,37 @@ describe('Locale Provider', () => {
locales.forEach(locale => {
it(`should display the text as ${locale.locale}`, () => {
const wrapper = mount(
const { container } = render(
<LocaleProvider locale={locale}>
<App />
</LocaleProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
});
it('should change locale of Modal.xxx', () => {
class ModalDemo extends React.Component {
componentDidMount() {
jest.useFakeTimers();
Modal.confirm({
title: 'Hello World!',
});
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
}
render() {
return null;
}
}
locales.forEach(locale => {
mount(
const { container } = render(
<LocaleProvider locale={locale}>
<ModalDemo />
<Modal title="Locale Modal" visible getContainer={false}>
Modal
</Modal>
</LocaleProvider>,
);
const currentConfirmNode =
document.querySelectorAll('.ant-modal-confirm')[
document.querySelectorAll('.ant-modal-confirm').length - 1
];
let cancelButtonText = currentConfirmNode.querySelectorAll(
'.ant-btn:not(.ant-btn-primary) span',
)[0].innerHTML;
let okButtonText = currentConfirmNode.querySelectorAll('.ant-btn-primary span')[0].innerHTML;
if (locale.locale.indexOf('zh-') === 0) {
cancelButtonText = cancelButtonText.replace(' ', '');
okButtonText = okButtonText.replace(' ', '');
let cancelButtonText = container?.querySelector('button.ant-btn-default span')?.innerHTML;
let okButtonText = container?.querySelector('button.ant-btn-primary span')?.innerHTML;
if (locale.locale.includes('zh-')) {
cancelButtonText = cancelButtonText?.replace(' ', '');
okButtonText = okButtonText?.replace(' ', '');
}
expect(cancelButtonText).toBe(locale.Modal.cancelText);
expect(okButtonText).toBe(locale.Modal.okText);
expect(cancelButtonText).toBe(locale.Modal?.cancelText);
expect(okButtonText).toBe(locale.Modal?.okText);
});
});
it('set moment locale when locale changes', () => {
const Test = ({ locale }) => (
const Test: React.FC<{ locale?: Locale }> = ({ locale }) => (
<LocaleProvider locale={locale}>
<div>
<DatePicker defaultValue={moment()} open />
@ -272,11 +244,13 @@ describe('Locale Provider', () => {
</LocaleProvider>
);
const wrapper = mount(<Test locale={zhCN} />);
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ locale: frFR });
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ locale: null });
expect(wrapper.render()).toMatchSnapshot();
const { container, rerender } = render(<Test locale={zhCN} />);
expect(container.firstChild).toMatchSnapshot();
rerender(<Test locale={frFR} />);
expect(container.firstChild).toMatchSnapshot();
rerender(<Test />);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@ -10,24 +10,24 @@ import MenuDivider from '../MenuDivider';
import MenuItem from '../MenuItem';
import SubMenu from '../SubMenu';
interface MenuItemType extends RcMenuItemType {
export interface MenuItemType extends RcMenuItemType {
danger?: boolean;
icon?: React.ReactNode;
title?: string;
}
interface SubMenuType extends Omit<RcSubMenuType, 'children'> {
export interface SubMenuType extends Omit<RcSubMenuType, 'children'> {
icon?: React.ReactNode;
theme?: 'dark' | 'light';
children: ItemType[];
}
interface MenuItemGroupType extends Omit<RcMenuItemGroupType, 'children'> {
export interface MenuItemGroupType extends Omit<RcMenuItemGroupType, 'children'> {
children?: ItemType[];
key?: React.Key;
}
interface MenuDividerType extends RcMenuDividerType {
export interface MenuDividerType extends RcMenuDividerType {
dashed?: boolean;
key?: React.Key;
}

View File

@ -231,7 +231,6 @@ exports[`TreeSelect TreeSelect Custom Icons should support customized icons 1`]
aria-hidden="true"
class="ant-select-selection-search-mirror"
>
 
</span>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { mount } from 'enzyme';
import React from 'react';
import { render } from '../../../tests/utils';
import TreeSelect, { TreeNode } from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
@ -12,7 +12,7 @@ describe('TreeSelect', () => {
describe('TreeSelect Custom Icons', () => {
it('should support customized icons', () => {
const wrapper = mount(
const { container } = render(
<TreeSelect
showSearch
clearIcon={<span>clear</span>}
@ -32,23 +32,24 @@ describe('TreeSelect', () => {
</TreeSelect>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
it('should `treeIcon` work', () => {
const wrapper = mount(
const { container } = render(
<TreeSelect treeIcon open>
<TreeNode value="parent 1" title="parent 1" icon={<span>Bamboo</span>} />
</TreeSelect>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
});
it('should support notFoundContent', () => {
const wrapper = mount(<TreeSelect treeIcon open notFoundContent="notFoundContent" />);
expect(wrapper.text()).toBe('notFoundContent');
const content = 'notFoundContent';
const { container } = render(<TreeSelect treeIcon open notFoundContent={content} />);
expect(container.querySelector('.ant-select-empty')?.innerHTML).toBe(content);
});
it('should show warning when use dropdownClassName', () => {

View File

@ -18,34 +18,42 @@ Please find below some of the design resources and tools about Ant Design that w
- https://gw.alipayobjects.com/zos/basement_prod/048ee28f-2c80-4d15-9aa3-4f5ddac50465.svg
- Sketch Symbols for Desktop
- https://gw.alipayobjects.com/os/bmw-prod/a5ff1d86-44cd-4b86-92f8-daab59cba5b7.sketch
- Official
- Sketch Symbols (Dark)
- https://gw.alipayobjects.com/zos/basement_prod/048ee28f-2c80-4d15-9aa3-4f5ddac50465.svg
- Sketch Symbols for Desktop with dark theme
- https://gw.alipayobjects.com/os/bmw-prod/6b670a1c-26e3-4379-9c86-7a2b95e170e5.sketch
- Official
- Mobile Components
- https://gw.alipayobjects.com/zos/basement_prod/c0c3852c-d245-4330-886b-cb02ef49eb6d.svg
- Sketch Symbols File for Mobile
- https://gw.alipayobjects.com/os/bmw-prod/d6266aef-25b7-4892-b275-ce214121831c.sketch
- Official
- Ant Design Pro
- https://gw.alipayobjects.com/zos/basement_prod/5edc7f4d-3302-4710-963b-7b6c77ea8d06.svg
- Common Templates and Pages
- https://gw.alipayobjects.com/os/bmw-prod/22208f9d-f8c5-4d7c-b87a-fec290e96527.sketch
- Official
- Ant Design Library
- https://gw.alipayobjects.com/zos/basement_prod/90740380-bbb7-4329-95e5-64533934c6cf.svg
- A powerful Axure library
- http://library.ant.design
- Official
- Kitchen
- https://gw.alipayobjects.com/zos/basement_prod/d475d063-2754-4442-b9db-5d164e06acc9.svg
- A Sketch plugin collection
- http://kitchen.alipay.com
- Official
- Ant Design Landing
- https://gw.alipayobjects.com/zos/basement_prod/b443f4be-5116-49b7-873f-a7c8502b8f0e.svg
- Landing Templates
- https://landing.ant.design/docs/download-cn
- Official
- Ant UX
- https://gw.alipayobjects.com/zos/basement_prod/51682163-e01a-46fe-810c-ac0062379717.svg
- A set of page logic prototype libraries
- http://ux.ant.design
- Official
- Ant Design Prototype (xiaopiu)
- https://gw.alipayobjects.com/zos/basement_prod/77e6a9ae-24a9-4be6-be42-f7fa8ee0eecf.svg
- Online library and prototype
@ -66,6 +74,7 @@ Please find below some of the design resources and tools about Ant Design that w
- https://gw.alipayobjects.com/zos/basement_prod/a9dc586a-fe0a-4c7d-ab4f-f5ed779b963d.svg
- Sketch Symbols for Chart
- https://gw.alipayobjects.com/os/bmw-prod/704968a5-2641-484e-9f65-c2735b2c0287.sketch
- Official
- MockingBot
- https://cdn.modao.cc/logo_mockingbot.svg
- Rich component resources

View File

@ -18,34 +18,42 @@ toc: false
- https://gw.alipayobjects.com/zos/basement_prod/048ee28f-2c80-4d15-9aa3-4f5ddac50465.svg
- 桌面组件 Sketch 模板包
- https://gw.alipayobjects.com/os/bmw-prod/82c08c51-9993-4568-90c1-249c8301c0af.sketch
- 官方
- Sketch 组件包 (暗色)
- https://gw.alipayobjects.com/zos/basement_prod/048ee28f-2c80-4d15-9aa3-4f5ddac50465.svg
- 桌面组件 Sketch 模板包,内含暗色版本的 antd 组件
- https://gw.alipayobjects.com/os/bmw-prod/f002145c-33d9-408e-ba75-a1a68896dfa3.sketch
- 官方
- Mobile Components
- https://gw.alipayobjects.com/zos/basement_prod/c0c3852c-d245-4330-886b-cb02ef49eb6d.svg
- 移动组件 Sketch 模板
- https://gw.alipayobjects.com/os/bmw-prod/d6266aef-25b7-4892-b275-ce214121831c.sketch
- 官方
- Ant Design Pro
- https://gw.alipayobjects.com/zos/basement_prod/5edc7f4d-3302-4710-963b-7b6c77ea8d06.svg
- 典型页面 + 通用业务模板
- https://gw.alipayobjects.com/os/bmw-prod/22208f9d-f8c5-4d7c-b87a-fec290e96527.sketch
- 官方
- Ant Design Library
- https://gw.alipayobjects.com/zos/basement_prod/90740380-bbb7-4329-95e5-64533934c6cf.svg
- 一套强大的 Axure 部件库
- http://library.ant.design
- 官方
- Kitchen
- https://gw.alipayobjects.com/zos/basement_prod/d475d063-2754-4442-b9db-5d164e06acc9.svg
- Sketch 工具集
- http://kitchen.alipay.com
- 官方
- Ant Design Landing
- https://gw.alipayobjects.com/zos/basement_prod/b443f4be-5116-49b7-873f-a7c8502b8f0e.svg
- 首页模板集
- https://landing.ant.design/docs/download-cn
- 官方
- Ant UX
- https://gw.alipayobjects.com/zos/basement_prod/51682163-e01a-46fe-810c-ac0062379717.svg
- 一套页面逻辑原型库
- http://ux.ant.design
- 官方
- Ant Design 原型 (xiaopiu)
- https://gw.alipayobjects.com/zos/basement_prod/77e6a9ae-24a9-4be6-be42-f7fa8ee0eecf.svg
- 可在线编辑的组件库和交互原型
@ -62,6 +70,7 @@ toc: false
- https://gw.alipayobjects.com/zos/basement_prod/a9dc586a-fe0a-4c7d-ab4f-f5ed779b963d.svg
- 桌面组件 Chart 模板包
- https://gw.alipayobjects.com/os/bmw-prod/704968a5-2641-484e-9f65-c2735b2c0287.sketch
- 官方
- 墨刀原型设计
- https://cdn.modao.cc/logo_mockingbot.svg
- 内置丰富的 Ant Design 组件资源

View File

@ -68,6 +68,7 @@
.resource-cards {
.resource-card {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
@ -90,6 +91,19 @@
object-fit: cover;
}
.resource-card-badge {
position: absolute;
top: 8px;
right: 8px;
padding: 4px 8px;
color: #fff;
font-size: 12px;
line-height: 1;
background: rgba(0, 0, 0, 0.65);
border-radius: 1px;
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
}
.resource-card-title {
margin: 16px 20px 8px;
color: #0d1a26;

View File

@ -43,7 +43,7 @@ interface ResourcesProps {
}
function getUnitString(unit: ContentUnit[]): string {
if (!unit) return '';
if (!unit || unit.length <= 1) return '';
const last = unit[unit.length - 1];
return Array.isArray(last) ? getUnitString(last) : (last as string);
@ -53,8 +53,9 @@ function toCardList([, ...items]: ContentUnit[]): ContentUnit[] {
return [
'div',
{ className: 'ant-row resource-cards', style: 'margin: -12px -12px 0 -12px' },
...items.map(([, title, [, image, description, link]]: any) => {
...items.map(([, title, [, image, description, link, badge]]: any) => {
let titleStr = getUnitString(title);
const badgeStr = getUnitString(badge);
const imageStr = getUnitString(image);
const descStr = getUnitString(description);
const linkStr = getUnitString(link);
@ -90,9 +91,17 @@ function toCardList([, ...items]: ContentUnit[]): ContentUnit[] {
: {},
},
],
badgeStr &&
badgeStr !== '-' && [
'div',
{
className: 'resource-card-badge',
},
badgeStr,
],
['p', { className: 'resource-card-title' }, titleStr],
['p', { className: 'resource-card-description' }, descStr],
],
].filter(c => c),
];
}),
];
@ -123,9 +132,10 @@ const Resources = (props: ResourcesProps) => {
const { localizedPageData, location } = props;
const { locale } = useIntl();
const content = React.useMemo(() => injectCards(localizedPageData.content), [
localizedPageData.content,
]);
const content = React.useMemo(
() => injectCards(localizedPageData.content),
[localizedPageData.content],
);
return (
<div id="resources-page">