From 069c73b91db3249f8eb33f73ef7056f4a3d02e05 Mon Sep 17 00:00:00 2001 From: bindoon Date: Fri, 1 Feb 2019 13:57:06 +0800 Subject: [PATCH 01/12] fix(Input): textarea placeholder in ie11 --- src/input/main.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/input/main.scss b/src/input/main.scss index 3b92685b2f..5ac2b2cc36 100644 --- a/src/input/main.scss +++ b/src/input/main.scss @@ -42,7 +42,6 @@ vertical-align: middle; background-color: transparent; color: $input-text-color; - @include input-placeholder($input-placeholder-color); &::-ms-clear { display: none; @@ -204,6 +203,11 @@ color: $input-label-color; } + input, + textarea { + @include input-placeholder($input-placeholder-color); + } + &.#{$css-prefix}disabled { @include input-disabled(); From 662a95943a2ab9bc069a0186f66b3ec60ddf19c1 Mon Sep 17 00:00:00 2001 From: bindoon Date: Fri, 1 Feb 2019 20:30:45 +0800 Subject: [PATCH 02/12] fix(Select): could add tag while popup not visible --- docs/select/index.md | 4 ++-- src/select/base.jsx | 2 +- src/select/select.jsx | 39 +++++++++++++++++++++++++-------------- test/select/index-spec.js | 18 +++++++++++++++++- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/docs/select/index.md b/docs/select/index.md index 02bc30274a..aa2a942e35 100644 --- a/docs/select/index.md +++ b/docs/select/index.md @@ -70,7 +70,7 @@ const dataSource = [ | placeholder | 没有值的时候的占位符 | String | - | | autoWidth | 下拉菜单是否与选择器对齐 | Boolean | true | | label | 自定义内联 label | ReactNode | - | -| hasClear | 是否有清除按钮 | Boolean | - | +| hasClear | 是否有清除按钮(单选模式有效) | Boolean | - | | state | 校验状态

**可选值**:
'error', 'loading' | Enum | - | | readOnly | 是否只读,只读模式下可以展开弹层但不能选 | Boolean | - | | disabled | 是否禁用选择器 | Boolean | - | @@ -117,7 +117,7 @@ const dataSource = [ | placeholder | 没有值的时候的占位符 | String | - | | autoWidth | 下拉菜单是否与选择器对齐 | Boolean | true | | label | 自定义内联 label | ReactNode | - | -| hasClear | 是否有清除按钮 | Boolean | - | +| hasClear | 是否有清除按钮(单选模式有效) | Boolean | - | | state | 校验状态

**可选值**:
'error', 'loading' | Enum | - | | readOnly | 是否只读,只读模式下可以展开弹层但不能选 | Boolean | - | | disabled | 是否禁用选择器 | Boolean | - | diff --git a/src/select/base.jsx b/src/select/base.jsx index 2da7bc6a8a..7f4e5bc7fb 100644 --- a/src/select/base.jsx +++ b/src/select/base.jsx @@ -42,7 +42,7 @@ export default class Base extends React.Component { */ label: PropTypes.node, /** - * 是否有清除按钮 + * 是否有清除按钮(单选模式有效) */ hasClear: PropTypes.bool, /** diff --git a/src/select/select.jsx b/src/select/select.jsx index 876298936c..00376c0d93 100644 --- a/src/select/select.jsx +++ b/src/select/select.jsx @@ -454,10 +454,33 @@ class Select extends Base { onKeyDown(e); } + chooseMultipleItem(key) { + const value = this.state.value || []; + const keys = value.map(v => { + return valueToSelectKey(v); + }); + + const index = keys.map(v => `${v}`).indexOf(key); + + if (index > -1) { // unselect + keys.splice(index, 1); + } else { // select + keys.push(key); + } + + this.handleMultipleSelect(keys, 'enter'); + } + // 回车 选择高亮的 item chooseHighlightItem(proxy, e) { const prevVisible = this.state.visible; + const { mode } = this.props; + if (!prevVisible) { + // input tag by itself + if (mode === 'tag' && this.state.searchValue) { + this.chooseMultipleItem(this.state.searchValue); + } return false; } @@ -468,22 +491,10 @@ class Select extends Base { return; } - const { mode } = this.props; - if (mode === 'single') { this.handleSingleSelect(highlightKey, 'enter'); } else { - const value = this.state.value || []; - const keys = value.map(v => { - return valueToSelectKey(v); - }); - const index = keys.map(v => `${v}`).indexOf(highlightKey); - if (index > -1) { // 反选 - keys.splice(index, 1); - } else { // 勾选 - keys.push(highlightKey); - } - this.handleMultipleSelect(keys, 'enter'); + this.chooseMultipleItem(highlightKey); // 阻止事件冒泡到最外层,让Popup 监听到触发弹层关闭 e && e.stopPropagation(); } @@ -674,7 +685,7 @@ class Select extends Base { type="arrow-down" />); } - // 不能使用 this.hasClear() 方法判断,要保证 clear 按钮 dom 结构一直存在,防止其不能成为弹层的安全节点,导致弹层没有必要的显示或隐藏 + // do not use this.hasClear() here, to make sure clear btn always exists, can not influenced by apis like `disabled` `readOnly` if (hasClear) { ret.push(); diff --git a/test/select/index-spec.js b/test/select/index-spec.js index 54395aebeb..4c935cfa31 100644 --- a/test/select/index-spec.js +++ b/test/select/index-spec.js @@ -352,7 +352,23 @@ describe('Select', () => { wrapper.find('div.next-tag .next-tag-close-btn').simulate('click'); }); - it('should support clear', (done) => { + it('should support mode=tag with visible=false', (done) => { + wrapper.setProps({ + mode: 'tag', + visible: false, + value: ['yyy'], + onChange: function (value) { + assert(value.length === 2); + assert(value[1] === 'bbb'); + done(); + } + }); + + wrapper.find('input').simulate('change', {target: {value: 'bbb'}}); + wrapper.find('input').simulate('keydown', {keyCode: 13}); + }); + + it('should support mode=tag with hasClear', (done) => { wrapper.setProps({ mode: 'tag', hasClear: true, From b6c710e9b91e5ca557f58f6236be1425839ac54e Mon Sep 17 00:00:00 2001 From: bindoon Date: Fri, 1 Feb 2019 21:05:18 +0800 Subject: [PATCH 03/12] docs(Field): add usage for parseName=true --- docs/field/index.en-us.md | 1 + docs/field/index.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/field/index.en-us.md b/docs/field/index.en-us.md index 19a81cd06c..2ec6852263 100644 --- a/docs/field/index.en-us.md +++ b/docs/field/index.en-us.md @@ -18,6 +18,7 @@ Fields can be used to manage data when it comes to form data manipulation and va - With Field `init` components, `value` `onChange` must be placed in init's third argument, otherwise it may be overridden by init. - `Form` has been deeply optimized with `Field` for `data acquisition` and `automatic verification prompt`. It is recommended to use `Field` in `Form`. Please check Form demo. - initValue The defaultValue of a similar component, which only takes effect when the component first renders (the ajax asynchronous invocation setting initValue may have missed the first render) +- with `parseName=true` you could use `getValues` to get a struct value, but not work in `getValue` you still need pass complete key ### basic use diff --git a/docs/field/index.md b/docs/field/index.md index d8f0ede677..cc5dcafe51 100644 --- a/docs/field/index.md +++ b/docs/field/index.md @@ -20,6 +20,7 @@ - `Form`已经和`Field` 在`数据获取`和`自动校验提示`方面做了深度优化,建议在`Form`中使用`Field`, 请查看 Form demo。 - initValue 类似组件的 defaultValue 只有在组件第一次render的时候才生效(ajax 异步调用设置 initValue 可能已经错过了第一次render) - autoUnmount 默认打开的,如果需要保留会 `自动卸载的组件` 数据请关闭此项 +- `parseName=true` 可以通过 `getValues` 获取到结构化的数据, 但是 getValue 还是必须传完整 key 值 ### 基本使用 From abb6175e596e2a47925bb31dff366f92e77acf48 Mon Sep 17 00:00:00 2001 From: Justin Kahn Date: Sun, 3 Feb 2019 15:19:23 +0800 Subject: [PATCH 04/12] test(*): a11y updates: dialog, tag, collapse, loading, overlay, progress --- src/dialog/inner.jsx | 4 +- src/locale/en-us.js | 3 ++ src/locale/ja-jp.js | 3 ++ src/locale/zh-cn.js | 3 ++ src/locale/zh-tw.js | 3 ++ src/tag/tag.jsx | 13 ++++-- test/collapse/a11y-spec.js | 18 ++++---- test/dialog/a11y-spec.js | 91 ++++++++++++++++++++++++++++++++++++++ test/loading/a11y-spec.js | 53 ++++++++++++++++++++++ test/overlay/a11y-spec.js | 64 +++++++++++++++++++++++++++ test/overlay/index-spec.js | 4 +- test/progress/a11y-spec.js | 14 +++--- test/tag/a11y-spec.js | 68 ++++++++++++++++++++++++++++ test/util/a11y/validate.js | 83 ++++++++++++++++++---------------- 14 files changed, 366 insertions(+), 58 deletions(-) create mode 100644 test/dialog/a11y-spec.js create mode 100644 test/loading/a11y-spec.js create mode 100644 test/overlay/a11y-spec.js create mode 100644 test/tag/a11y-spec.js diff --git a/src/dialog/inner.jsx b/src/dialog/inner.jsx index b701f80d54..d447cda4be 100644 --- a/src/dialog/inner.jsx +++ b/src/dialog/inner.jsx @@ -52,7 +52,9 @@ export default class Inner extends Component { const { prefix, title } = this.props; if (title) { this.titleId = guid('dialog-title-'); - return
{title}
; + return (
+ {title} +
); } return null; } diff --git a/src/locale/en-us.js b/src/locale/en-us.js index d87886217b..8b662a9d03 100644 --- a/src/locale/en-us.js +++ b/src/locale/en-us.js @@ -98,5 +98,8 @@ export default { }, Search: { buttonText: 'Search' + }, + Tag: { + delete: 'Delete' } }; diff --git a/src/locale/ja-jp.js b/src/locale/ja-jp.js index 86a9fbfba5..414d723eef 100644 --- a/src/locale/ja-jp.js +++ b/src/locale/ja-jp.js @@ -98,5 +98,8 @@ export default { }, Search: { buttonText: 'サーチ' + }, + Tag: { + delete: 'デリート' } }; diff --git a/src/locale/zh-cn.js b/src/locale/zh-cn.js index 70747363ce..18413d5366 100644 --- a/src/locale/zh-cn.js +++ b/src/locale/zh-cn.js @@ -98,5 +98,8 @@ export default { }, Search: { buttonText: '搜索' + }, + Tag: { + delete: '删除' } }; diff --git a/src/locale/zh-tw.js b/src/locale/zh-tw.js index 957fd82600..761e061557 100644 --- a/src/locale/zh-tw.js +++ b/src/locale/zh-tw.js @@ -98,5 +98,8 @@ export default { }, Search: { buttonText: '檢索' + }, + Tag: { + delete: '删除' } }; diff --git a/src/tag/tag.jsx b/src/tag/tag.jsx index 2536fbbb9b..f7182bdb7f 100644 --- a/src/tag/tag.jsx +++ b/src/tag/tag.jsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; import Animate from '../animate'; import Icon from '../icon'; import { obj, func, support, KEYCODE } from '../util'; +import zhCN from '../locale/zh-cn'; const { noop, bindCtx } = func; @@ -46,7 +47,8 @@ class Tag extends Component { onClick: PropTypes.func, _shape: PropTypes.oneOf(['default', 'closable', 'checkable']), disabled: PropTypes.bool, - rtl: PropTypes.bool + rtl: PropTypes.bool, + locale: PropTypes.object, }; static defaultProps = { @@ -61,7 +63,8 @@ class Tag extends Component { onClick: noop, _shape: 'default', disabled: false, - rtl: false + rtl: false, + locale: zhCN.Tag }; constructor(props) { @@ -164,7 +167,7 @@ class Tag extends Component { } renderTailNode() { - const { prefix, closable } = this.props; + const { prefix, closable, locale } = this.props; if (!closable) { return null; @@ -174,6 +177,8 @@ class Tag extends Component { @@ -217,7 +222,7 @@ class Tag extends Component { className={bodyClazz} onClick={this.handleBodyClick} onKeyDown={this.onKeyDown} - tabIndex="0" + tabIndex={disabled ? '' : '0'} role="button" aria-disabled={disabled} disabled={disabled} diff --git a/test/collapse/a11y-spec.js b/test/collapse/a11y-spec.js index 88c36f2183..a2a55c7f0d 100644 --- a/test/collapse/a11y-spec.js +++ b/test/collapse/a11y-spec.js @@ -3,13 +3,14 @@ import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Collapse from '../../src/collapse/index'; import '../../src/collapse/style'; -import a11y from '../util/a11y/validate'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; Enzyme.configure({ adapter: new Adapter() }); const Panel = Collapse.Panel; +/* eslint-disable no-undef, react/jsx-filename-extension */ describe('Collapse A11y', () => { let wrapper; @@ -18,11 +19,11 @@ describe('Collapse A11y', () => { wrapper.unmount(); wrapper = null; } - a11y.afterEach(); + a11yAfterEach(); }); - it('should not have any violations for children rendered component', (done) => { - wrapper = a11y.testReact( + it('should not have any violations for children rendered component', async () => { + wrapper = await testReact( Pannel Content @@ -30,11 +31,11 @@ describe('Collapse A11y', () => { Pannel Content
others
-
, done); +
); + return wrapper; }); - it('should not have any violations for data rendered component', (done) => { - + it('should not have any violations for data rendered component', async () => { const list = [ { title: 'Well, hello there', @@ -45,6 +46,7 @@ describe('Collapse A11y', () => { content: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.' } ]; - wrapper = a11y.testReact(, done, { incomplete: true }); + wrapper = await testReact(, { incomplete: true }); + return wrapper; }); }); diff --git a/test/dialog/a11y-spec.js b/test/dialog/a11y-spec.js new file mode 100644 index 0000000000..aaa2f2b072 --- /dev/null +++ b/test/dialog/a11y-spec.js @@ -0,0 +1,91 @@ +import React from 'react'; +import Enzyme, { mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import assert from 'power-assert'; +import Dialog from '../../src/dialog/index'; +import '../../src/dialog/style.js'; +import { testReact, test, afterEach as a11yAfterEach } from '../util/a11y/validate'; +import { roleType, isHeading, isButton } from '../util/a11y/checks'; + +/* eslint-disable react/jsx-filename-extension */ +/* global describe it afterEach */ + +Enzyme.configure({ adapter: new Adapter() }); + +describe('Dialog A11y', () => { + describe('Basic', () => { + let wrapper; + + afterEach(() => { + if (wrapper && wrapper.unmount) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await testReact(); + return wrapper; + }); + + it('should have accessible header', () => { + wrapper = mount(); + assert(isHeading('.next-dialog-header', wrapper)); + }); + + it('should have accessible close button', () => { + wrapper = mount(); + assert(isButton('.next-dialog-close', wrapper)); + }); + }); + + describe('Show', () => { + let hide; + + afterEach(() => { + if (hide && typeof hide === 'function') { + hide(); + hide = null; + } + }); + + it('should not have any violations', async () => { + hide = Dialog.alert({ + title: 'Title', + content: 'Content', + animation: false, + className: 'dialog-a11y-tests' + }).hide; + return test('.dialog-a11y-tests'); + }); + + it('should have role `alertdialog` for alert dialog', () => { + hide = Dialog.alert({ + title: 'Title', + content: 'Content', + animation: false + }).hide; + assert(roleType('alertdialog', document.querySelector('.next-dialog'))); + }); + + it('should have role `alertdialog` for show dialog', () => { + hide = Dialog.show({ + title: 'Title', + content: 'Content', + animation: false + }).hide; + assert(roleType('alertdialog', document.querySelector('.next-dialog'))); + }); + + it('should have role `alertdialog` for confirm dialog', () => { + hide = Dialog.confirm({ + title: 'Title', + content: 'Content', + animation: false + }).hide; + assert(roleType('alertdialog', document.querySelector('.next-dialog'))); + }); + }); +}); + diff --git a/test/loading/a11y-spec.js b/test/loading/a11y-spec.js new file mode 100644 index 0000000000..aee8429df8 --- /dev/null +++ b/test/loading/a11y-spec.js @@ -0,0 +1,53 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Loading from '../../src/loading/index'; +import '../../src/loading/style.js'; +import { testReact, afterEach as a11yAfterEach } from '../util/a11y/validate'; + +/* eslint-disable react/jsx-filename-extension */ +/* global describe it afterEach*/ + +Enzyme.configure({ adapter: new Adapter() }); + + +describe('Loading', () => { + + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await testReact( +
test
+
, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when hidden', async () => { + wrapper = await testReact( +
test
+
, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when fullscreen', async () => { + wrapper = await testReact( +
test
+
, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when inline', async () => { + wrapper = await testReact( +
test
+
, { incomplete: true }); + return wrapper; + }); +}); diff --git a/test/overlay/a11y-spec.js b/test/overlay/a11y-spec.js new file mode 100644 index 0000000000..c56ac147bc --- /dev/null +++ b/test/overlay/a11y-spec.js @@ -0,0 +1,64 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Overlay from '../../src/overlay/index'; +import { test, afterEach as a11yAfterEach } from '../util/a11y/validate'; +import '../../src/progress/style.js'; + +const { Popup } = Overlay; +Enzyme.configure({ adapter: new Adapter() }); + +const divId = 'a11y-root'; +const delay = time => new Promise(resolve => setTimeout(resolve, time)); + +const render = element => { + const container = document.createElement('div'); + container.className = 'container'; + container.id = divId; + document.body.appendChild(container); + const el = element(container); + ReactDOM.render(el, container); + return { + unmount: () => { + ReactDOM.unmountComponentAtNode(container); + document.body.removeChild(container); + }, + container + }; +}; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Overlay A11y', () => { + let wrapper; + + beforeEach(() => { + const nodeListArr = [].slice.call(document.querySelectorAll('.next-overlay-wrapper')); + + nodeListArr.forEach((node) => { + node.parentNode.removeChild(node); + }); + }); + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async function() { + wrapper = render((container) =>
Hello World
); + await delay(500); + return test(wrapper.container); + }); + + it('should not have any violations for Popup', async function() { + wrapper = render((container) => (Open} triggerType="click" canCloseByTrigger={false}> + Hello World From Popup! + )); + await delay(500); + return test(wrapper.container); + }); +}); diff --git a/test/overlay/index-spec.js b/test/overlay/index-spec.js index 60904fc3c1..237269f379 100644 --- a/test/overlay/index-spec.js +++ b/test/overlay/index-spec.js @@ -154,8 +154,8 @@ describe('Overlay', () => { }); yield delay(500); - assert(wrapper.find('.next-overlay-wrapper.opened')); - assert(wrapper.find('.next-overlay-inner .content')); + assert(document.querySelector('.next-overlay-wrapper.opened')); + assert(document.querySelector('.next-overlay-inner.content')); assert(!document.querySelector('.next-overlay-backdrop')); diff --git a/test/progress/a11y-spec.js b/test/progress/a11y-spec.js index e6c37dd794..1d0d39fb0c 100644 --- a/test/progress/a11y-spec.js +++ b/test/progress/a11y-spec.js @@ -3,7 +3,7 @@ import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import Progress from '../../src/progress/index'; import '../../src/progress/style.js'; -import a11y from '../util/a11y/validate'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; Enzyme.configure({ adapter: new Adapter() }); @@ -16,14 +16,16 @@ describe('Progress A11y', () => { wrapper.unmount(); wrapper = null; } - a11y.afterEach(); + a11yAfterEach(); }); - it('should not have any violations for Line Progress', (done) => { - wrapper = a11y.testReact(, done, { incomplete: true }); + it('should not have any violations for Line Progress', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; }); - it('should not have any violations for Circle Progress', (done) => { - wrapper = a11y.testReact(, done, { incomplete: true }); + it('should not have any violations for Circle Progress', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; }); }); diff --git a/test/tag/a11y-spec.js b/test/tag/a11y-spec.js new file mode 100644 index 0000000000..8dd25db7e1 --- /dev/null +++ b/test/tag/a11y-spec.js @@ -0,0 +1,68 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Tag from '../../src/tag'; +import '../../src/tag/style.js'; +import { testReact, afterEach as a11yAfterEach } from '../util/a11y/validate'; + +/* eslint-disable react/jsx-filename-extension */ +/* global describe it afterEach*/ + +Enzyme.configure({ adapter: new Adapter() }); + +const {Selectable, Group, Closable} = Tag; + + + +describe('Tag', () => { + + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await testReact(Test, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when disabled', async () => { + wrapper = await testReact(Test, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when closable', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when closable and disabled', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when closable and checked', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when selectable', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when selectable and checked', async () => { + wrapper = await testReact(, { incomplete: true }); + return wrapper; + }); + + it('should not have any violations when group with child node', async () => { + wrapper = await testReact(
Hello World
, { incomplete: true }); + return wrapper; + }); +}); diff --git a/test/util/a11y/validate.js b/test/util/a11y/validate.js index 0831bfdf51..f0dfc61ae3 100644 --- a/test/util/a11y/validate.js +++ b/test/util/a11y/validate.js @@ -6,71 +6,80 @@ import { mount } from 'enzyme'; const divId = 'a11y-root'; /** - * Helper function for running a11y unit tests using axe-core + * Run Axe-core tests on a dom node * * @param {String} selector - css selector for element to test - * @param {function} cb - callback function to call on success (normally will be `done` function) * @param {Object} options - options for axe tests * {Boolean} `incomplete` - should test error if there was an incomplete test? (not recommended) * {Object} `rules` - set properties for rules + * @returns {Promise} results object from Axe.run */ -const test = function (selector, cb, options) { - // disable `color-contrast` test by default when failing on incomplete tests. Can be overriden by setting `options.rules` - if (options.incomplete && !options.rules) { - options.rules = { - 'color-contrast': { - enabled: false - } - }; - } - - Axe.run(selector, { rules: options.rules }, function(error, results) { - assert(!error); +export const test = function (selector, options = {}) { + // disable `color-contrast` test by default. Can be overriden by setting `options.rules['color-contrast']` + options.rules = Object.assign({ + 'color-contrast': { + enabled: false + } + }, options.rules); - if (results.violations.length) { + return Axe.run(selector, { rules: options.rules }) + .then((results) => { + if (results.violations.length) { // eslint-disable-next-line no-console - console.error(JSON.stringify(results.violations)); - } + console.error(JSON.stringify(results.violations)); + } - assert(results.violations.length === 0); + assert(results.violations.length === 0); - if (options.incomplete) { - if (results.incomplete.length) { + if (options.incomplete) { + if (results.incomplete.length) { // eslint-disable-next-line no-console - console.error(results.incomplete); + console.error(results.incomplete); + } + assert(results.incomplete.length === 0); } - assert(results.incomplete.length === 0); - } - cb(); - }); + }).catch((error) => { + assert(!error); + }); }; /** - * Helper function for running a11y unit tests using axe-core + * Mount a ReactDOM Element to the dom * * @param {ReactDOM Element} node - React element to mount and run axe-core tests on - * @param {function} cb - callback function to call on success (normally will be `done` function) - * @param {Object} options - options for axe tests - * {Boolean} `incomplete` - should test error if there was an incomplete test? (not recommended) - * {Object} `rules` - set properties for rules + * @param {String} id - id to set on the wrapper div + * @param {Promise} wrapper of the react component */ -const testReact = function (node, cb, options = {}) { +export const mountReact = async function (node, id) { const div = document.createElement('div'); - div.id = divId; + div.id = id; document.body.appendChild(div); const wrapper = mount(node, { attachTo: div }); + return wrapper; +}; - test(`#${divId}`, cb, options); - +/** + * Run Axe-core tests on a React element + * + * @param {ReactDOM Element} node - React element to mount and run axe-core tests on + * @param {Object} options - options for axe tests + * {Boolean} `incomplete` - should test error if there was an incomplete test? (not recommended) + * {Object} `rules` - set properties for rules + * @param {Promise} wrapper of the react component + */ +export const testReact = async function (node, options = {}) { + const wrapper = mountReact(node, divId); + await test(`#${divId}`, options); return wrapper; }; -const afterEach = function () { +/** + * Helper function to use with `testReact` to unmount the div wrapper + */ +export const afterEach = function () { const div = document.querySelector(`#${divId}`); if (div) { div.remove(); } }; - -export default { test, testReact, afterEach }; From 217348a48154445ec083734e3148734167b84fb3 Mon Sep 17 00:00:00 2001 From: bindoon Date: Mon, 4 Feb 2019 11:25:20 +0800 Subject: [PATCH 05/12] docs(Field): update basic --- docs/field/demo/basic.md | 4 ++-- docs/field/demo/mix.md | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/field/demo/basic.md b/docs/field/demo/basic.md index 773347bd26..5b02bce32d 100644 --- a/docs/field/demo/basic.md +++ b/docs/field/demo/basic.md @@ -19,8 +19,8 @@ import { Input, Button, Field } from '@alifd/next'; -class App extends React.PureComponent { - field = new Field(this, {forceUpdate: true}); +class App extends React.Component { + field = new Field(this); onGetValue() { console.log(this.field.getValue('input')); diff --git a/docs/field/demo/mix.md b/docs/field/demo/mix.md index eabdd686c3..3e1b151f56 100644 --- a/docs/field/demo/mix.md +++ b/docs/field/demo/mix.md @@ -39,9 +39,7 @@ const layout = { }; class App extends React.Component { - field = new Field(this, { - deepReset: true - }); + field = new Field(this); render() { const {init, getValue} = this.field; From 5935b522cc9515cf81730b2deb4b3994fc4ca654 Mon Sep 17 00:00:00 2001 From: youluna Date: Tue, 12 Feb 2019 19:19:56 +0800 Subject: [PATCH 06/12] fix(Table): can't scroll horizontally when body scrollTop !== 0 --- src/table/lock.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/table/lock.jsx b/src/table/lock.jsx index 8016176873..a26664d432 100644 --- a/src/table/lock.jsx +++ b/src/table/lock.jsx @@ -243,6 +243,9 @@ export default function lock(BaseComponent) { onLockBodyWheel = (e) => { const y = e.deltaY; + const x = e.deltaX; + const thresholdX = 15; + const safeScrollX = x > -thresholdX && x < thresholdX; if (this.isLock()) { const lockRightBody = this.bodyRightNode, lockLeftBody = this.bodyLeftNode, @@ -257,7 +260,7 @@ export default function lock(BaseComponent) { } scrollNode.scrollTop = scrollTop + y; const { scrollTop: newScrollTop } = scrollNode; - if (newScrollTop + clientHeight < scrollHeight && newScrollTop) { + if (newScrollTop + clientHeight < scrollHeight && newScrollTop && safeScrollX) { e.preventDefault(); } } From 4528b7cbe38b961022627e385371735275b1028c Mon Sep 17 00:00:00 2001 From: youluna Date: Wed, 13 Feb 2019 15:15:24 +0800 Subject: [PATCH 07/12] test(Table): add test case for scroll x --- test/table/index-spec.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/table/index-spec.js b/test/table/index-spec.js index 1f7feb9f11..3866e04874 100644 --- a/test/table/index-spec.js +++ b/test/table/index-spec.js @@ -474,4 +474,42 @@ describe('Table', () => { }, 100) }) }); + + it('should support dataSource [] => [{},{}] => []', () => { + wrapper.setProps({ + children: [, + ] + }) + wrapper.debug(); + assert(wrapper.find('div.next-table-lock-left').length === 1); + assert(wrapper.find('div.next-table-lock-right').length === 1); + assert(wrapper.find('div.next-table-empty').length === 0); + + wrapper.setProps({ + dataSource: [] + }); + assert(wrapper.find('div.next-table-empty').length !== 0); + + wrapper.setProps({ + dataSource: [{ id: '1', name: 'test' }, { id: '2', name: 'test2' }] + }); + + assert(wrapper.find('div.next-table-lock-left').length === 1); + assert(wrapper.find('div.next-table-lock-right').length === 1); + assert(wrapper.find('div.next-table-empty').length === 0); + }) + + it('should support lock scroll x', () => { + wrapper.setProps({ + children: [, + , + ] + }) + wrapper.debug(); + assert(wrapper.find('div.next-table-lock-left').length === 1); + assert(wrapper.find('div.next-table-lock-right').length === 1); + + const body = wrapper.find('div.next-table-lock .next-table-body').at(1).props().onWheel({deltaY: 200, deltaX: 5}) + + }) }); From adb3b5906000f4761fba03e224316f649f0b1a47 Mon Sep 17 00:00:00 2001 From: youluna Date: Wed, 13 Feb 2019 15:54:26 +0800 Subject: [PATCH 08/12] fix(Table): head selection emptied wrongly - useVirtual - check one and scroll untill it's out of visual area --- src/table/selection.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/table/selection.jsx b/src/table/selection.jsx index af600e5037..80838c72b7 100644 --- a/src/table/selection.jsx +++ b/src/table/selection.jsx @@ -104,13 +104,16 @@ export default function selection(BaseComponent) { renderSelectionHeader = () => { const onChange = this.selectAllRow, attrs = {}, - { rowSelection, primaryKey, dataSource, locale } = this.props, + { rowSelection, primaryKey, dataSource, entireDataSource, locale } = this.props, { selectedRowKeys } = this.state, mode = rowSelection.mode ? rowSelection.mode : 'multiple'; let checked = !!selectedRowKeys.length; let indeterminate = false; - this.flatDataSource(dataSource) + + const source = entireDataSource || dataSource; + + this.flatDataSource(source) .filter((record, index) => { if (!rowSelection.getProps) { return true; From da897e7f9ce982c6396f9c90136fbeb0e774c21e Mon Sep 17 00:00:00 2001 From: youluna Date: Thu, 14 Feb 2019 09:44:57 +0800 Subject: [PATCH 09/12] chore(*): add banner for files in dist --- scripts/webpack/prod.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/webpack/prod.js b/scripts/webpack/prod.js index b9667597d9..660fb792cf 100644 --- a/scripts/webpack/prod.js +++ b/scripts/webpack/prod.js @@ -1,4 +1,5 @@ const webpack = require('webpack'); +const path = require('path'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CssSplitWebpackPlugin = require('css-split-webpack-plugin').default; @@ -7,6 +8,9 @@ const loaders = require('./loaders'); module.exports = function(options = {}) { const minimize = options.minimize; + const packagePath = path.resolve('package.json'); + const version = require(packagePath).version; + const config = { output: {}, resolve: { @@ -32,6 +36,9 @@ module.exports = function(options = {}) { }] }, plugins: [ + new webpack.BannerPlugin(`@alifd/next@${version} (https://fusion.design) +Copyright 2018-present Alibaba Group, +Licensed under MIT (https://github.com/alibaba-fusion/next/blob/master/LICENSE)`), new CaseSensitivePathsPlugin(), new webpack.optimize.ModuleConcatenationPlugin(), // support ie 9 From eefeddcb3337dee5ccac50df0310ccc68241ed9a Mon Sep 17 00:00:00 2001 From: youluna Date: Thu, 14 Feb 2019 10:22:06 +0800 Subject: [PATCH 10/12] chore(*): Release-1.12.4 --- CHANGELOG.md | 22 ++++++++++++++++++++++ LATESTLOG.md | 17 +++++++++++++---- docs/step/index.md | 18 +++++++++--------- docs/tree/index.md | 41 +++++++++++++++++++++++++++++++++++++++++ index.js | 2 +- package.json | 2 +- 6 files changed, 87 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217b1ec359..c2f9a28551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log + +## [1.12.4](https://github.com/alibaba-fusion/next/compare/1.12.3...1.12.4) (2019-02-14) + + +### Bug Fixes + +* **Checkbox:** group didn't honor children's indeternimated state ([f90a747](https://github.com/alibaba-fusion/next/commit/f90a747)) +* **Input:** textarea placeholder in ie11 ([069c73b](https://github.com/alibaba-fusion/next/commit/069c73b)) +* **Select:** could add tag while popup not visible ([662a959](https://github.com/alibaba-fusion/next/commit/662a959)) +* **Table:** can't scroll horizontally when body scrollTop !== 0 ([5935b52](https://github.com/alibaba-fusion/next/commit/5935b52)) +* **Table:** head selection emptied wrongly ([adb3b59](https://github.com/alibaba-fusion/next/commit/adb3b59)) + + +### Features + +* **Icon:** suport size of inherit ([48e7f1d](https://github.com/alibaba-fusion/next/commit/48e7f1d)) +* **Progress:** remove tabindex ([602b267](https://github.com/alibaba-fusion/next/commit/602b267)) +* **Step:** uniform parameter of step ([e1d2ab5](https://github.com/alibaba-fusion/next/commit/e1d2ab5)) + + + + ## [1.12.3](https://github.com/alibaba-fusion/next/compare/1.12.2...1.12.3) (2019-01-28) diff --git a/LATESTLOG.md b/LATESTLOG.md index 87b29faa04..09f9793ecc 100644 --- a/LATESTLOG.md +++ b/LATESTLOG.md @@ -1,12 +1,21 @@ # Latest Log -## [1.12.3](https://github.com/alibaba-fusion/next/compare/1.12.2...1.12.3) (2019-01-28) +## [1.12.4](https://github.com/alibaba-fusion/next/compare/1.12.3...1.12.4) (2019-02-14) ### Bug Fixes -* **Pagination:** locale text error ([406c198](https://github.com/alibaba-fusion/next/commit/406c198)) -* **Tab:** extra is covered by other elements ([c3d913f](https://github.com/alibaba-fusion/next/commit/c3d913f)) -* **Tab:** inproperly focus when using with field ([0d3bd2c](https://github.com/alibaba-fusion/next/commit/0d3bd2c)) +* **Checkbox:** group didn't honor children's indeternimated state ([f90a747](https://github.com/alibaba-fusion/next/commit/f90a747)) +* **Input:** textarea placeholder in ie11 ([069c73b](https://github.com/alibaba-fusion/next/commit/069c73b)) +* **Select:** could add tag while popup not visible ([662a959](https://github.com/alibaba-fusion/next/commit/662a959)) +* **Table:** can't scroll horizontally when body scrollTop !== 0 ([5935b52](https://github.com/alibaba-fusion/next/commit/5935b52)) +* **Table:** head selection emptied wrongly ([adb3b59](https://github.com/alibaba-fusion/next/commit/adb3b59)) + + +### Features + +* **Icon:** suport size of inherit ([48e7f1d](https://github.com/alibaba-fusion/next/commit/48e7f1d)) +* **Progress:** remove tabindex ([602b267](https://github.com/alibaba-fusion/next/commit/602b267)) +* **Step:** uniform parameter of step ([e1d2ab5](https://github.com/alibaba-fusion/next/commit/e1d2ab5)) diff --git a/docs/step/index.md b/docs/step/index.md index 574053c2cd..e844e7c496 100644 --- a/docs/step/index.md +++ b/docs/step/index.md @@ -11,15 +11,15 @@ ### Step -| 参数 | 说明 | 类型 | 默认值 | -| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | -| current | 当前步骤 | Number | 0 | -| shape | 类型

**可选值**:
'circle', 'arrow', 'dot' | Enum | 'circle' | -| direction | 展示方向

**可选值**:
'hoz', 'ver' | Enum | 'hoz' | -| labelPlacement | 横向布局时的内容排列

**可选值**:
'hoz', 'ver' | Enum | 'ver' | -| readOnly | 是否只读模式 | Boolean | - | -| animation | 是否开启动效 | Boolean | true | -| itemRender | StepItem 的自定义渲染

**签名**:
Function(index: Number, status: String) => Node
**参数**:
_index_: {Number} 节点索引
_status_: {String} 节点状态
**返回值**:
{Node} 节点的渲染结果
| Function | null | +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------- | +| current | 当前步骤 | Number | 0 | +| shape | 类型

**可选值**:
'circle', 'arrow', 'dot' | Enum | 'circle' | +| direction | 展示方向

**可选值**:
'hoz', 'ver' | Enum | 'hoz' | +| labelPlacement | 横向布局时的内容排列

**可选值**:
'hoz', 'ver' | Enum | 'ver' | +| readOnly | 是否只读模式 | Boolean | - | +| animation | 是否开启动效 | Boolean | true | +| itemRender | StepItem 的自定义渲染

**签名**:
Function(index: Number, status: String) => Node
**参数**:
_index_: {Number} 节点索引
_status_: {String} 节点状态
**返回值**:
{Node} 节点的渲染结果
| Function | null | ### Step.Item diff --git a/docs/tree/index.md b/docs/tree/index.md index 2ae3e39fa8..a38804e8c4 100644 --- a/docs/tree/index.md +++ b/docs/tree/index.md @@ -15,6 +15,47 @@ ## API +### Tree + +| 参数 | 说明 | 类型 | 默认值 | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ---------- | +| children | 树节点 | ReactNode | - | +| dataSource | 数据源,该属性优先级高于 children | Array | - | +| showLine | 是否显示树的线 | Boolean | false | +| selectable | 是否支持选中节点 | Boolean | true | +| selectedKeys | (用于受控)当前选中节点 key 的数组 | Array<String> | - | +| defaultSelectedKeys | (用于非受控)默认选中节点 key 的数组 | Array<String> | \[] | +| onSelect | 选中或取消选中节点时触发的回调函数

**签名**:
Function(selectedKeys: Array, extra: Object) => void
**参数**:
_selectedKeys_: {Array} 选中节点key的数组
_extra_: {Object} 额外参数
_extra.selectedNodes_: {Array} 选中节点的数组
_extra.node_: {Object} 当前操作的节点
_extra.selected_: {Boolean} 当前操作是否是选中 | Function | func.noop | +| multiple | 是否支持多选 | Boolean | false | +| checkable | 是否支持勾选节点的复选框 | Boolean | false | +| checkedKeys | (用于受控)当前勾选复选框节点 key 的数组或 `{checked: Array, indeterminate: Array}` 的对象 | Array<String>/Object | - | +| defaultCheckedKeys | (用于非受控)默认勾选复选框节点 key 的数组 | Array<String> | \[] | +| checkStrictly | 勾选节点复选框是否完全受控(父子节点选中状态不再关联) | Boolean | false | +| checkedStrategy | 定义选中时回填的方式

**可选值**:
'all'(返回所有选中的节点)
'parent'(父子节点都选中时只返回父节点)
'child'(父子节点都选中时只返回子节点) | Enum | 'all' | +| onCheck | 勾选或取消勾选复选框时触发的回调函数

**签名**:
Function(checkedKeys: Array, extra: Object) => void
**参数**:
_checkedKeys_: {Array} 勾选复选框节点key的数组
_extra_: {Object} 额外参数
_extra.checkedNodes_: {Array} 勾选复选框节点的数组
_extra.checkedNodesPositions_: {Array} 包含有勾选复选框节点和其位置的对象的数组
_extra.indeterminateKeys_: {Array} 半选复选框节点 key 的数组
_extra.node_: {Object} 当前操作的节点
_extra.checked_: {Boolean} 当前操作是否是勾选 | Function | func.noop | +| expandedKeys | (用于受控)当前展开的节点 key 的数组 | Array<String> | - | +| defaultExpandedKeys | (用于非受控)默认展开的节点 key 的数组 | Array<String> | \[] | +| defaultExpandAll | 是否默认展开所有节点 | Boolean | false | +| autoExpandParent | 是否自动展开父节点 | Boolean | true | +| onExpand | 展开或收起节点时触发的回调函数

**签名**:
Function(expandedKeys: Array, extra: Object) => void
**参数**:
_expandedKeys_: {Array} 展开的节点key的数组
_extra_: {Object} 额外参数
_extra.node_: {Object} 当前操作的节点
_extra.expanded_: {Boolean} 当前操作是否是展开 | Function | func.noop | +| editable | 是否支持编辑节点内容 | Boolean | false | +| onEditFinish | 编辑节点内容完成时触发的回调函数

**签名**:
Function(key: String, label: String, node: Object) => void
**参数**:
_key_: {String} 编辑节点的 key
_label_: {String} 编辑节点完成时节点的文本
_node_: {Object} 当前编辑的节点 | Function | func.noop | +| draggable | 是否支持拖拽节点 | Boolean | false | +| onDragStart | 开始拖拽节点时触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 拖拽的节点 | Function | func.noop | +| onDragEnter | 拖拽节点进入目标节点时触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 目标节点
_info.expandedKeys_: {Array} 当前展开的节点key的数组 | Function | func.noop | +| onDragOver | 拖拽节点在目标节点上移动的时候触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 目标节点 | Function | func.noop | +| onDragLeave | 拖拽节点离开目标节点时触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 目标节点 | Function | func.noop | +| onDragEnd | 拖拽节点拖拽结束时触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 目标节点 | Function | func.noop | +| onDrop | 拖拽节点放入目标节点内或前后触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 拖拽信息
_info.event_: {Object} 事件对象
_info.node_: {Object} 目标节点
_info.dragNode_: {Object} 拖拽的节点
_info.dragNodesKeys_: {Array} 拖拽的节点和其子节点 key 的数组
_info.dropPosition_: {Number} 放置位置,-1代表当前节点前,0代表当前节点里,1代表当前节点后 | Function | func.noop | +| canDrop | 节点是否可被作为拖拽的目标节点

**签名**:
Function(info: Object) => Boolean
**参数**:
_info_: {Object} 拖拽信息
_info.node_: {Object} 目标节点
_info.dragNode_: {Object} 拖拽的节点
_info.dragNodesKeys_: {Array} 拖拽的节点和其子节点 key 的数组
_info.dropPosition_: {Number} 放置位置,-1代表当前节点前,0代表当前节点里,1代表当前节点后
**返回值**:
{Boolean} 是否可以被当作目标节点
| Function | () => true | +| loadData | 异步加载数据的函数

**签名**:
Function(node: Object) => void
**参数**:
_node_: {Object} 被点击展开的节点 | Function | - | +| filterTreeNode | 按需筛选高亮节点

**签名**:
Function(node: Object) => Boolean
**参数**:
_node_: {Object} 待筛选的节点
**返回值**:
{Boolean} 是否被筛选中
| Function | - | +| onRightClick | 右键点击节点时触发的回调函数

**签名**:
Function(info: Object) => void
**参数**:
_info_: {Object} 信息对象
_info.event_: {Object} 事件对象
_info.node_: {Object} 点击的节点 | Function | func.noop | +| isLabelBlock | 设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助 flex 实现,暂时只支持 ie10+) | Boolean | false | +| isNodeBlock | 设置节点是否占满一行 | Boolean/Object | false | +| animation | 是否开启展开收起动画 | Boolean | true | +| focusedKey | 当前获得焦点的子菜单或菜单项 key 值 | String | - | + ### Tree.Node | 参数 | 说明 | 类型 | 默认值 | diff --git a/index.js b/index.js index 748838e710..fc6661c1fd 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ var next = require('./lib/index.js'); -next.version = '1.12.3'; +next.version = '1.12.4'; module.exports = next; diff --git a/package.json b/package.json index 4ad2d384d6..4be634cfec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@alifd/next", - "version": "1.12.3", + "version": "1.12.4", "description": "A configurable component library for web built on React.", "keywords": [ "fusion", From 95233cc29bf92bb19f11227f007433a943c10906 Mon Sep 17 00:00:00 2001 From: Justin Kahn Date: Thu, 14 Feb 2019 12:12:49 +0800 Subject: [PATCH 11/12] feat(Tag): wrap in ConfigProvider --- src/tag/tag.jsx | 3 ++- test/tag/index-spec.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tag/tag.jsx b/src/tag/tag.jsx index f7182bdb7f..2024f6ec59 100644 --- a/src/tag/tag.jsx +++ b/src/tag/tag.jsx @@ -5,6 +5,7 @@ import Animate from '../animate'; import Icon from '../icon'; import { obj, func, support, KEYCODE } from '../util'; import zhCN from '../locale/zh-cn'; +import ConfigProvider from '../config-provider'; const { noop, bindCtx } = func; @@ -244,4 +245,4 @@ class Tag extends Component { } } -export default Tag; +export default ConfigProvider.config(Tag); diff --git a/test/tag/index-spec.js b/test/tag/index-spec.js index 968394f89f..d0ecaf4c8a 100644 --- a/test/tag/index-spec.js +++ b/test/tag/index-spec.js @@ -13,7 +13,7 @@ describe('Tag', () => { describe('render', () => { it('should render nothing if tag is unvisible', () => { - const wrapper = shallow().dive(); + const wrapper = shallow().dive().dive(); // note: react setState is asynchronous // so must force update or setTimeout in test suit to check render results. wrapper.setState({ @@ -35,14 +35,14 @@ describe('Tag', () => { it('`afterAppear` should be called when tag appeared', () => { const afterAppearCb = sinon.spy(); - const wrapper = shallow().dive(); + const wrapper = shallow().dive().dive(); wrapper.instance().handleAnimationInit(); assert(afterAppearCb.calledOnce === true); }); it('`afterLeave` should be called when tag leaved', () => { const afterLeaveCb = sinon.spy(); - const wrapper = shallow().dive(); + const wrapper = shallow().dive().dive(); wrapper.instance().handleAnimationEnd(); assert(afterLeaveCb.calledOnce === true); }); @@ -71,7 +71,7 @@ describe('Tag', () => { }); it('tag should be destroyed after unmoun', () => { - const wrapper = shallow().dive(); + const wrapper = shallow().dive().dive(); const willUnmount = sinon.spy(); const instance = wrapper.instance(); // for coverage From 8ab196b5ba0024c2adaca657f5c4b6dfaa00a576 Mon Sep 17 00:00:00 2001 From: Justin Kahn Date: Thu, 14 Feb 2019 12:48:22 +0800 Subject: [PATCH 12/12] test(*): many more a11y unit tests, update to validate util --- test/badge/a11y-spec.js | 39 +++++++++++++ test/balloon/a11y-spec.js | 46 +++++++++++++++ test/breadcrumb/a11y-spec.js | 55 +++++++++++++++++ test/button/a11y-spec.js | 107 ++++++++++++++++++++++++++++++++++ test/card/a11y-spec.js | 77 ++++++++++++++++++++++++ test/dropdown/a11y-spec.js | 30 ++++++++++ test/icon/a11y-spec.js | 27 +++++++++ test/menu-button/a11y-spec.js | 39 +++++++++++++ test/menu/a11y-spec.js | 70 ++++++++++++++++++++++ test/message/a11y-spec.js | 83 ++++++++++++++++++++++++++ test/paragraph/a11y-spec.js | 41 +++++++++++++ test/radio/a11y-spec.js | 63 ++++++++++++++++++++ test/util/a11y/validate.js | 7 ++- 13 files changed, 681 insertions(+), 3 deletions(-) create mode 100644 test/badge/a11y-spec.js create mode 100644 test/balloon/a11y-spec.js create mode 100644 test/breadcrumb/a11y-spec.js create mode 100644 test/button/a11y-spec.js create mode 100644 test/card/a11y-spec.js create mode 100644 test/dropdown/a11y-spec.js create mode 100644 test/icon/a11y-spec.js create mode 100644 test/menu-button/a11y-spec.js create mode 100644 test/menu/a11y-spec.js create mode 100644 test/message/a11y-spec.js create mode 100644 test/paragraph/a11y-spec.js create mode 100644 test/radio/a11y-spec.js diff --git a/test/badge/a11y-spec.js b/test/badge/a11y-spec.js new file mode 100644 index 0000000000..b259f2fc15 --- /dev/null +++ b/test/badge/a11y-spec.js @@ -0,0 +1,39 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Badge from '../../src/badge/index'; +import '../../src/badge/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Badge A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations for count and no children', async () => { + wrapper = await testReact(); + return wrapper; + }); + + it('should not have any violations for dot', async () => { + wrapper = await testReact(); + return wrapper; + }); + + it('should not have any violations for content', async () => { + wrapper = await testReact( + + ); + return wrapper; + }); +}); diff --git a/test/balloon/a11y-spec.js b/test/balloon/a11y-spec.js new file mode 100644 index 0000000000..5d4c05827a --- /dev/null +++ b/test/balloon/a11y-spec.js @@ -0,0 +1,46 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Balloon from '../../src/balloon/index'; +import '../../src/balloon/style'; +import { afterEach as a11yAfterEach, test, mountReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + +const wrapperClassName = 'js-a11y-test'; +const popupProps = { wrapperClassName }; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Balloon A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + // wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + // TODO: fix `button-name` violation for close button + it.skip('should not have any violations', async () => { + wrapper = await mountReact( + I am balloon content + ); + return test(`.${wrapperClassName}`); + }); + + it('should not have any violations when not closable', async () => { + wrapper = await mountReact( + I am balloon content + ); + return test(`.${wrapperClassName}`); + }); + + it('should not have any violations when Tooltip', async () => { + wrapper = await mountReact( + I am balloon content + ); + return test(`.${wrapperClassName}`); + }); +}); diff --git a/test/breadcrumb/a11y-spec.js b/test/breadcrumb/a11y-spec.js new file mode 100644 index 0000000000..729240a401 --- /dev/null +++ b/test/breadcrumb/a11y-spec.js @@ -0,0 +1,55 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Breadcrumb from '../../src/breadcrumb/index'; +import '../../src/breadcrumb/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Breadcrumb A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations when empty', async () => { + wrapper = await testReact(); + return wrapper; + }); + + it('should not have any violations for breadcrumb items', async () => { + wrapper = await testReact( + Home + + T-shirts  78,999 Results + + ); + return wrapper; + }); + + it('should not have any violations for max node limit', async () => { + wrapper = await testReact( + 1 + 2 + 3 + ); + return wrapper; + }); + + it('should not have any violations for separator', async () => { + wrapper = await testReact( + 1 + 2 + 3 + ); + return wrapper; + }); +}); diff --git a/test/button/a11y-spec.js b/test/button/a11y-spec.js new file mode 100644 index 0000000000..6b900a2bf4 --- /dev/null +++ b/test/button/a11y-spec.js @@ -0,0 +1,107 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Button from '../../src/button/index'; +import '../../src/button/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Button A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations when default', async () => { + wrapper = await testReact(
+    +    +    +    +    + +
); + return wrapper; + }); + + it('should not have any violations when text buttons', async () => { + wrapper = await testReact(
+    +    +    +    +    + +
); + return wrapper; + }); + + it('should not have any violations when warning buttons', async () => { + wrapper = await testReact(
+    +    +    +    +
); + return wrapper; + }); + + it('should not have any violations when anchor tag', async () => { + const props = { + component: 'a', + href: 'http://www.alibaba.com', + target: '_blank' + }; + wrapper = await testReact(); + return wrapper; + }); + + it('should not have any violations when ghost', async () => { + wrapper = await testReact(
+    +    +    + +
); + return wrapper; + }); + + it('should not have any violations when in a Button Group', async () => { + wrapper = await testReact( + + + + + ); + return wrapper; + }); + + it('should not have any violations when loading', async () => { + wrapper = await testReact(); + return wrapper; + }); + + it('should not have any violations when various sizes', async () => { + wrapper = await testReact(
+    +    + +
+
+ + + + + +
); + return wrapper; + }); +}); diff --git a/test/card/a11y-spec.js b/test/card/a11y-spec.js new file mode 100644 index 0000000000..9fccf42510 --- /dev/null +++ b/test/card/a11y-spec.js @@ -0,0 +1,77 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Card from '../../src/card/index'; +import '../../src/card/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Card A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations when default', async () => { + wrapper = await testReact( +
+
); + return wrapper; + }); + + it('should not have any violations when displaying images', async () => { + wrapper = await testReact( + father day +
+

Father's Day

+

Thank you, papa

+
+
); + return wrapper; + }); + + it('should not have any violations when setting height', async () => { + const commonProps = { + style: { width: 300 }, + title: 'Title', + subTitle: 'Sub-title' + }; + + wrapper = await testReact(
+ +
+

Card content

+

Card content

+
+
+    + +
+

Card content

+

Card content

+
+
+
); + return wrapper; + }); + + it('should not have any violations when setting title off', async () => { + const commonProps = { + style: { width: 300 }, + title: 'Title' + }; + + wrapper = await testReact( + Card Content + ); + return wrapper; + }); +}); diff --git a/test/dropdown/a11y-spec.js b/test/dropdown/a11y-spec.js new file mode 100644 index 0000000000..3e14f93307 --- /dev/null +++ b/test/dropdown/a11y-spec.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Dropdown from '../../src/dropdown/index'; +import '../../src/dropdown/style'; +import { afterEach as a11yAfterEach, test, mountReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + +const wrapperClassName = 'js-a11y-test'; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Dropdown A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await mountReact(Hello dropdown} visible wrapperClassName={wrapperClassName}> +
dropdown
+
); + return test(`.${wrapperClassName}`); + }); +}); diff --git a/test/icon/a11y-spec.js b/test/icon/a11y-spec.js new file mode 100644 index 0000000000..805ef57931 --- /dev/null +++ b/test/icon/a11y-spec.js @@ -0,0 +1,27 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Icon from '../../src/icon/index'; +import '../../src/icon/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Icon A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await testReact(); + return wrapper; + }); +}); diff --git a/test/menu-button/a11y-spec.js b/test/menu-button/a11y-spec.js new file mode 100644 index 0000000000..3293fd828b --- /dev/null +++ b/test/menu-button/a11y-spec.js @@ -0,0 +1,39 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import MenuButton from '../../src/menu-button/index'; +import '../../src/menu-button/style'; +import { afterEach as a11yAfterEach, test, mountReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + +const wrapperClassName = 'js-a11y-test'; +const popupProps = { wrapperClassName }; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +const menu = ['a', 'b'].map(item => {item}); + +// TODO: fix `aria-allowed-attr` violation +describe.skip('MenuButton A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await mountReact({menu}); + return test(`.${wrapperClassName}`); + }); + + it('should not have any violations when text button', async () => { + wrapper = await mountReact(
+ {menu}   +
); + return test(`.${wrapperClassName}`); + }); +}); diff --git a/test/menu/a11y-spec.js b/test/menu/a11y-spec.js new file mode 100644 index 0000000000..e9353e8f17 --- /dev/null +++ b/test/menu/a11y-spec.js @@ -0,0 +1,70 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Menu from '../../src/menu/index'; +import '../../src/menu/style'; +import { afterEach as a11yAfterEach, test, testReact, mountReact } from '../util/a11y/validate'; + +const { SubMenu, Item, Group, Divider } = Menu; +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe.skip('Menu A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + // TODO: Fix `aria-allowed-attr` due to `aria-multiselectable=\"false\"`, `aria-selected=\"false\"` + it('should not have any violations for Item', async () => { + wrapper = await mountReact( + Option 1 + ); + return test('#item'); + }); + + // TODO: Fix issue with Item + it('should not have any violations for simple example', async () => { + wrapper = await testReact( + + Option 1 + + Disabled option 2 + + ); + return wrapper; + }); + + // TODO: Fix issue with Item + it('should not have any violations for Group', async () => { + wrapper = await testReact( + + + Group option 1 + Group option 2 + + + ); + return wrapper; + }); + + // TODO: This throws a false error for `li` nested inside role="menu". This is a bug in axe-core. Issue was created (https://github.com/dequelabs/axe-core/issues/1365) + // Follow up to resolve this bug. + it.skip('should not have any violations for SubMenu', async () => { + wrapper = await testReact( + + + Sub option 1 + Sub option 2 + + + ); + return wrapper; + }); +}); diff --git a/test/message/a11y-spec.js b/test/message/a11y-spec.js new file mode 100644 index 0000000000..b529c955f5 --- /dev/null +++ b/test/message/a11y-spec.js @@ -0,0 +1,83 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Message from '../../src/message/index'; +import '../../src/message/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Message A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations when various types', async () => { + wrapper = await testReact(
+ + Content Content Content Content + + + Content Content Content Content + + + Content Content Content Content + + + Content Content Content Content + + + Content Content Content Content + + + Content Content Content Content + +
); + return wrapper; + }); + + it('should not have any violations when various shapes', async () => { + wrapper = await testReact(
+ + Content Content Content Content + + + Content Content Content Content + + + Content Content Content Content + +
); + return wrapper; + }); + + it('should not have any violations when various sizes', async () => { + wrapper = await testReact(
+ + Content Content Content Content + + + Content Content Content Content + +
); + return wrapper; + }); + + // TODO: fix close button + it.skip('should not have any violations when closable', async () => { + wrapper = await testReact(
+ + Content Content Content Content + +
); + return wrapper; + }); +}); diff --git a/test/paragraph/a11y-spec.js b/test/paragraph/a11y-spec.js new file mode 100644 index 0000000000..27c70abd17 --- /dev/null +++ b/test/paragraph/a11y-spec.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Paragraph from '../../src/paragraph/index'; +import '../../src/paragraph/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + +const content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Paragraph A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it('should not have any violations', async () => { + wrapper = await testReact(
+ {content} +
+ {content} +
); + return wrapper; + }); + + it('should not have any violations when type=`short`', async () => { + wrapper = await testReact(
+ {content} +
+ {content} +
); + return wrapper; + }); +}); diff --git a/test/radio/a11y-spec.js b/test/radio/a11y-spec.js new file mode 100644 index 0000000000..d7348fe118 --- /dev/null +++ b/test/radio/a11y-spec.js @@ -0,0 +1,63 @@ +import React from 'react'; +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import Radio from '../../src/radio/index'; +import '../../src/radio/style'; +import { afterEach as a11yAfterEach, testReact } from '../util/a11y/validate'; + +Enzyme.configure({ adapter: new Adapter() }); + +const list = [ + { + value: 'apple', + label: 'Apple', + disabled: false + }, { + value: 'pear', + label: 'Pear' + }, { + value: 'orange', + label: 'Orange', + disabled: true + } +]; + +/* eslint-disable no-undef, react/jsx-filename-extension */ +describe('Radio A11y', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + a11yAfterEach(); + }); + + it.skip('should not have any violations for different states', async () => { + wrapper = await testReact(
+ test 1  + test 1  + test 1  + test 1  + +
); + return wrapper; + }); + + it.skip('should not have any violations for various label methods', async () => { + wrapper = await testReact(
+ Apple  +   + +
); + return wrapper; + }); + + it.skip('should not have any violations for group', async () => { + wrapper = await testReact(
+ +
); + return wrapper; + }); +}); diff --git a/test/util/a11y/validate.js b/test/util/a11y/validate.js index f0dfc61ae3..0b8bbd3b05 100644 --- a/test/util/a11y/validate.js +++ b/test/util/a11y/validate.js @@ -23,6 +23,9 @@ export const test = function (selector, options = {}) { }, options.rules); return Axe.run(selector, { rules: options.rules }) + .catch((error) => { + assert(!error); + }) .then((results) => { if (results.violations.length) { // eslint-disable-next-line no-console @@ -38,8 +41,6 @@ export const test = function (selector, options = {}) { } assert(results.incomplete.length === 0); } - }).catch((error) => { - assert(!error); }); }; @@ -52,7 +53,7 @@ export const test = function (selector, options = {}) { */ export const mountReact = async function (node, id) { const div = document.createElement('div'); - div.id = id; + div.id = id || divId; document.body.appendChild(div); const wrapper = mount(node, { attachTo: div }); return wrapper;