Skip to content

Commit

Permalink
Allow aria-describedby overriding
Browse files Browse the repository at this point in the history
Summary:
Currently it's not possible to have draft-js *not* emit an `aria-describedby` attribute. This diff allows the developer to remove it.

This attribute points to the generated DOM id of the placeholder. Currently even if no placeholder was specified **[minor bug]**.

I will need this in order to complete T67720986. It will also resolve this [GitHub issue](facebookarchive#1739).

Fixing the GitHub issue meant that I needed a way to have the generated placeholder ID as well as a second specified one. The special token "{{PLACEHOLDER}}" can be used inside the prop to be replaced with that generated ID.

Fixes facebookarchive#1739

Reviewed By: claudiopro

Differential Revision: D21808668

fbshipit-source-id: c27fe0fa8237518f1e2b6830cc8a3e13b3a9d8c7
  • Loading branch information
jdx authored and vilemj-Viclick committed Jul 16, 2020
1 parent b1f203c commit 802116f
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 6 deletions.
21 changes: 18 additions & 3 deletions src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class UpdateDraftEditorFlags extends React.Component<{
*/
class DraftEditor extends React.Component<DraftEditorProps, State> {
static defaultProps: DraftEditorDefaultProps = {
ariaDescribedBy: '{{editor_id_placeholder}}',
blockRenderMap: DefaultDraftBlockRenderMap,
blockRendererFn: function() {
return null;
Expand Down Expand Up @@ -314,6 +315,22 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
return null;
}

/**
* returns ariaDescribedBy prop with '{{editor_id_placeholder}}' replaced with
* the DOM id of the placeholder (if it exists)
* @returns aria-describedby attribute value
*/
_renderARIADescribedBy(): ?string {
const describedBy = this.props.ariaDescribedBy || '';
const placeholderID = this._showPlaceholder()
? this._placeholderAccessibilityID
: '';
return (
describedBy.replace('{{editor_id_placeholder}}', placeholderID) ||
undefined
);
}

render(): React.Node {
const {
blockRenderMap,
Expand Down Expand Up @@ -381,9 +398,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
}
aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
aria-controls={readOnly ? null : this.props.ariaControls}
aria-describedby={
this.props.ariaDescribedBy || this._placeholderAccessibilityID
}
aria-describedby={this._renderARIADescribedBy()}
aria-expanded={readOnly ? null : ariaExpanded}
aria-label={this.props.ariaLabel}
aria-labelledby={this.props.ariaLabelledBy}
Expand Down
7 changes: 7 additions & 0 deletions src/component/base/DraftEditorProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ export type DraftEditorProps = {
ariaActiveDescendantID?: string,
ariaAutoComplete?: string,
ariaControls?: string,
/**
* aria-describedby attribute. should point to the id of a descriptive
* element. The substring, "{{editor_id_placeholder}}" will be replaced with
* the DOM id of the placeholder element if it exists.
* @default "{{editor_id_placeholder}}"
*/
ariaDescribedBy?: string,
ariaExpanded?: boolean,
ariaLabel?: string,
Expand Down Expand Up @@ -175,6 +181,7 @@ export type DraftEditorProps = {
};

export type DraftEditorDefaultProps = {
ariaDescribedBy: string,
blockRenderMap: DraftBlockRenderMap,
blockRendererFn: (block: BlockNodeRecord) => ?Object,
blockStyleFn: (block: BlockNodeRecord) => string,
Expand Down
94 changes: 92 additions & 2 deletions src/component/base/__tests__/DraftEditor.react-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+draft_js
* @flow
* @format
*/

Expand All @@ -13,18 +14,22 @@
jest.mock('generateRandomKey');

const DraftEditor = require('DraftEditor.react');
const EditorState = require('EditorState');
const React = require('React');

// $FlowFixMe
const ReactShallowRenderer = require('react-test-renderer/shallow');

let shallow;
let editorState;

beforeEach(() => {
shallow = new ReactShallowRenderer();
editorState = EditorState.createEmpty();
});

test('must has generated editorKey', () => {
shallow.render(<DraftEditor />);
shallow.render(<DraftEditor editorState={editorState} onChange={() => {}} />);

// internally at Facebook we use a newer version of the shallowRenderer
// which has a different level of wrapping of the '_instance'
Expand All @@ -36,7 +41,13 @@ test('must has generated editorKey', () => {
});

test('must has editorKey same as props', () => {
shallow.render(<DraftEditor editorKey="hash" />);
shallow.render(
<DraftEditor
editorState={editorState}
onChange={() => {}}
editorKey="hash"
/>,
);

// internally at Facebook we use a newer version of the shallowRenderer
// which has a different level of wrapping of the '_instance'
Expand All @@ -46,3 +57,82 @@ test('must has editorKey same as props', () => {
shallow._instance.getEditorKey || shallow._instance._instance.getEditorKey;
expect(getEditorKey()).toMatchSnapshot();
});

describe('ariaDescribedBy', () => {
function getProps(elem) {
const r = shallow.render(elem);
const ec = r.props.children[1].props.children;
return ec.props;
}

describe('without placeholder', () => {
test('undefined by default', () => {
const props = getProps(
<DraftEditor editorState={editorState} onChange={() => {}} />,
);
expect(props).toHaveProperty('aria-describedby', undefined);
});

test('can be set to something arbitrary', () => {
const props = getProps(
<DraftEditor
editorState={editorState}
onChange={() => {}}
ariaDescribedBy="abc"
/>,
);
expect(props).toHaveProperty('aria-describedby', 'abc');
});

test('can use special token', () => {
const props = getProps(
<DraftEditor
editorState={editorState}
onChange={() => {}}
ariaDescribedBy="abc {{editor_id_placeholder}} xyz"
/>,
);
expect(props).toHaveProperty('aria-describedby', 'abc xyz');
});
});

describe('with placeholder', () => {
test('has placeholder id by default', () => {
const props = getProps(
<DraftEditor
editorState={editorState}
onChange={() => {}}
editorKey="X"
placeholder="place"
/>,
);
expect(props).toHaveProperty('aria-describedby', 'placeholder-X');
});

test('can be set to something arbitrary', () => {
const props = getProps(
<DraftEditor
editorState={editorState}
onChange={() => {}}
editorKey="X"
placeholder="place"
ariaDescribedBy="abc"
/>,
);
expect(props).toHaveProperty('aria-describedby', 'abc');
});

test('can use special token', () => {
const props = getProps(
<DraftEditor
editorState={editorState}
onChange={() => {}}
editorKey="X"
placeholder="place"
ariaDescribedBy="abc {{editor_id_placeholder}} xyz"
/>,
);
expect(props).toHaveProperty('aria-describedby', 'abc placeholder-X xyz');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

exports[`must has editorKey same as props 1`] = `"hash"`;

exports[`must has generated editorKey 1`] = `"key0"`;
exports[`must has generated editorKey 1`] = `"key1"`;

0 comments on commit 802116f

Please sign in to comment.