From bec9e7d6bb7f940e5bf701f8d88ea98bbff924af Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Thu, 7 Mar 2019 11:43:59 +0100 Subject: [PATCH 1/5] feat(tab): implement setFocusOnActivate --- packages/tab/README.md | 1 + packages/tab/index.tsx | 17 +++++++++++++---- test/unit/tab/index.test.tsx | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/tab/README.md b/packages/tab/README.md index 107292b85..fcea4dc6a 100644 --- a/packages/tab/README.md +++ b/packages/tab/README.md @@ -78,6 +78,7 @@ class MyApp extends React.Component { Prop Name | Type | Description --- | --- | --- active | boolean | If true will activate the tab and indicator. +focusOnActivate | boolean | If true will focus itself when activated. className | string | Classes to appear on className attribute of root element. isFadingIndicator | boolean | Enables a fading indicator, instead of sliding (default). indicatorContent | element | Element that will appear within the `` element. diff --git a/packages/tab/index.tsx b/packages/tab/index.tsx index 0d63cf797..6873a74de 100644 --- a/packages/tab/index.tsx +++ b/packages/tab/index.tsx @@ -31,6 +31,7 @@ import TabRipple, {TabRippleProps} from './TabRipple'; export interface TabProps extends React.HTMLProps { active?: boolean; + focusOnActivate?: boolean; isFadingIndicator?: boolean; indicatorContent?: React.ReactNode; minWidth?: boolean; @@ -59,6 +60,7 @@ export default class Tab extends React.Component { static defaultProps: Partial = { active: false, + focusOnActivate: false, className: '', isFadingIndicator: false, indicatorContent: null, @@ -76,9 +78,11 @@ export default class Tab extends React.Component { }; componentDidMount() { + const {active, focusOnActivate} = this.props; this.foundation = new MDCTabFoundation(this.adapter); this.foundation.init(); - if (this.props.active) { + this.foundation.setFocusOnActivate(focusOnActivate); + if (active) { this.foundation.activate(); } } @@ -88,10 +92,14 @@ export default class Tab extends React.Component { } componentDidUpdate(prevProps: TabProps) { - if (this.props.active !== prevProps.active) { - if (this.props.active) { + const {active, focusOnActivate, previousIndicatorClientRect} = this.props; + if (focusOnActivate !== prevProps.focusOnActivate) { + this.foundation.setFocusOnActivate(focusOnActivate); + } + if (active !== prevProps.active) { + if (active) { // If active state is updated through props, previousIndicatorClientRect must also be passed through props - this.activate(this.props.previousIndicatorClientRect); + this.activate(previousIndicatorClientRect); } else { this.deactivate(); } @@ -176,6 +184,7 @@ export default class Tab extends React.Component { const { /* eslint-disable */ active, + focusOnActivate, previousIndicatorClientRect, className, isFadingIndicator, diff --git a/test/unit/tab/index.test.tsx b/test/unit/tab/index.test.tsx index 5787a66d5..9b9cab77a 100644 --- a/test/unit/tab/index.test.tsx +++ b/test/unit/tab/index.test.tsx @@ -65,6 +65,30 @@ test('if props.active updates to false, foundation.deactivate is called', () => td.verify(wrapper.instance().deactivate(), {times: 1}); }); +test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from false to true', () => { + const wrapper = shallow(); + wrapper.instance().foundation.setFocusOnActivate = td.func(); + wrapper.setProps({focusOnActivate: true}); + td.verify(wrapper.instance().foundation.setFocusOnActivate(true), {times: 1}); +}); + +test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from true to false', () => { + const wrapper = shallow(); + wrapper.instance().foundation.setFocusOnActivate = td.func(); + wrapper.setProps({focusOnActivate: false}); + td.verify(wrapper.instance().foundation.setFocusOnActivate(false), {times: 1}); +}); + +test('when props.focusOnActivate is true, an active tab should be focused on mount', () => { + const wrapper = mount(, {attachTo: document.body}); + assert.equal(document.activeElement, wrapper.getDOMNode()); +}); + +test('when props.focusOnActivate is false, an active tab should not be focused on mount', () => { + const wrapper = mount(, {attachTo: document.body}); + assert.notEqual(document.activeElement, wrapper.getDOMNode()); +}); + test('#adapter.addClass adds class to state.classList', () => { const wrapper = shallow(); wrapper.instance().adapter.addClass('test-class'); From 0c943c8fc931f7a12a88c1a877ac1fd0abefb9a8 Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Mon, 11 Mar 2019 08:29:30 +0100 Subject: [PATCH 2/5] change default to true --- packages/tab/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tab/index.tsx b/packages/tab/index.tsx index 6873a74de..a6d580004 100644 --- a/packages/tab/index.tsx +++ b/packages/tab/index.tsx @@ -60,7 +60,7 @@ export default class Tab extends React.Component { static defaultProps: Partial = { active: false, - focusOnActivate: false, + focusOnActivate: true, className: '', isFadingIndicator: false, indicatorContent: null, From 3f1a4347295a2f5e247ed20f6e98ddcceda78a07 Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Mon, 11 Mar 2019 08:46:43 +0100 Subject: [PATCH 3/5] add additional unit tests --- test/unit/tab/index.test.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/unit/tab/index.test.tsx b/test/unit/tab/index.test.tsx index 9b9cab77a..900f51434 100644 --- a/test/unit/tab/index.test.tsx +++ b/test/unit/tab/index.test.tsx @@ -84,11 +84,25 @@ test('when props.focusOnActivate is true, an active tab should be focused on mou assert.equal(document.activeElement, wrapper.getDOMNode()); }); +test('when props.focusOnActivate is true and active is changed to true, the tab should be focused', () => { + const wrapper = mount(, {attachTo: document.body}); + assert.notEqual(document.activeElement, wrapper.getDOMNode()); + wrapper.setProps({active: true}); + assert.equal(document.activeElement, wrapper.getDOMNode()); +}); + test('when props.focusOnActivate is false, an active tab should not be focused on mount', () => { const wrapper = mount(, {attachTo: document.body}); assert.notEqual(document.activeElement, wrapper.getDOMNode()); }); +test('when props.focusOnActivate is false and active is changed to true, the tab should not be focused', () => { + const wrapper = mount(, {attachTo: document.body}); + assert.notEqual(document.activeElement, wrapper.getDOMNode()); + wrapper.setProps({active: true}); + assert.notEqual(document.activeElement, wrapper.getDOMNode()); +}); + test('#adapter.addClass adds class to state.classList', () => { const wrapper = shallow(); wrapper.instance().adapter.addClass('test-class'); From d9c5df949ee79f1167e956fff226bff937eb4cc7 Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Tue, 12 Mar 2019 08:15:22 +0100 Subject: [PATCH 4/5] render tests to wrapper DOM node --- test/unit/tab/index.test.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/unit/tab/index.test.tsx b/test/unit/tab/index.test.tsx index 900f51434..92797aed7 100644 --- a/test/unit/tab/index.test.tsx +++ b/test/unit/tab/index.test.tsx @@ -80,27 +80,39 @@ test('calls foundation.setFocusOnActivate when props.focusOnActivate changes fro }); test('when props.focusOnActivate is true, an active tab should be focused on mount', () => { - const wrapper = mount(, {attachTo: document.body}); + const div = document.createElement('div'); + document.body.append(div); + const wrapper = mount(, {attachTo: div}); assert.equal(document.activeElement, wrapper.getDOMNode()); + div.remove(); }); test('when props.focusOnActivate is true and active is changed to true, the tab should be focused', () => { - const wrapper = mount(, {attachTo: document.body}); + const div = document.createElement('div'); + document.body.append(div); + const wrapper = mount(, {attachTo: div}); assert.notEqual(document.activeElement, wrapper.getDOMNode()); wrapper.setProps({active: true}); assert.equal(document.activeElement, wrapper.getDOMNode()); + div.remove(); }); test('when props.focusOnActivate is false, an active tab should not be focused on mount', () => { - const wrapper = mount(, {attachTo: document.body}); + const div = document.createElement('div'); + document.body.append(div); + const wrapper = mount(, {attachTo: div}); assert.notEqual(document.activeElement, wrapper.getDOMNode()); + div.remove(); }); test('when props.focusOnActivate is false and active is changed to true, the tab should not be focused', () => { - const wrapper = mount(, {attachTo: document.body}); + const div = document.createElement('div'); + document.body.append(div); + const wrapper = mount(, {attachTo: div}); assert.notEqual(document.activeElement, wrapper.getDOMNode()); wrapper.setProps({active: true}); assert.notEqual(document.activeElement, wrapper.getDOMNode()); + div.remove(); }); test('#adapter.addClass adds class to state.classList', () => { From 47825680851afc6d38a8efadf7ef9635f1ace927 Mon Sep 17 00:00:00 2001 From: Ben McKernan Date: Tue, 12 Mar 2019 08:47:03 +0100 Subject: [PATCH 5/5] add focusOnActivate default value to README --- packages/tab/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tab/README.md b/packages/tab/README.md index fcea4dc6a..c0a65e7e5 100644 --- a/packages/tab/README.md +++ b/packages/tab/README.md @@ -78,7 +78,7 @@ class MyApp extends React.Component { Prop Name | Type | Description --- | --- | --- active | boolean | If true will activate the tab and indicator. -focusOnActivate | boolean | If true will focus itself when activated. +focusOnActivate | boolean | If true will focus itself when activated. Defaults to `true`. className | string | Classes to appear on className attribute of root element. isFadingIndicator | boolean | Enables a fading indicator, instead of sliding (default). indicatorContent | element | Element that will appear within the `` element.