Skip to content

Commit 9f99224

Browse files
authored
fix(rich-text-input): fix for safari (#1150)
* fix(rich-text-input): fix for safari
1 parent e6eb44d commit 9f99224

10 files changed

+124
-38
lines changed

src/components/inputs/localized-rich-text-input/editor.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AngleUpIcon, AngleDownIcon } from '../../icons';
1313
import Text from '../../typography/text';
1414
import FlatButton from '../../buttons/flat-button';
1515
import RichTextBody from '../../internals/rich-text-body';
16+
import HiddenInput from '../../internals/rich-text-body/hidden-input';
1617
import { getLanguageLabelStyles } from './editor.styles';
1718
import messages from '../../internals/messages/multiline-input';
1819

@@ -177,8 +178,10 @@ const Editor = props => {
177178

178179
// eslint-disable-next-line react/display-name
179180
const renderEditor = (props, editor, next) => {
181+
const internalId = `${props.id}__internal__id`;
182+
180183
const children = React.cloneElement(next(), {
181-
tagName: 'output',
184+
id: internalId,
182185
});
183186

184187
const passedProps = {
@@ -201,9 +204,16 @@ const renderEditor = (props, editor, next) => {
201204
...filterDataAttributes(props),
202205
};
203206

207+
const isFocused = props.editor.value.selection.isFocused;
208+
204209
return (
205210
<Editor editor={editor} {...passedProps}>
206211
{children}
212+
<HiddenInput
213+
isFocused={isFocused}
214+
handleFocus={editor.focus}
215+
id={props.id}
216+
/>
207217
</Editor>
208218
);
209219
};
@@ -233,6 +243,7 @@ renderEditor.propTypes = {
233243
name: PropTypes.string,
234244
disabled: PropTypes.bool,
235245
readOnly: PropTypes.bool,
246+
editor: PropTypes.any,
236247
options: PropTypes.shape({
237248
language: PropTypes.string.isRequired,
238249
error: PropTypes.node,

src/components/inputs/localized-rich-text-input/localized-rich-text-input.spec.js

+50-30
Original file line numberDiff line numberDiff line change
@@ -10,107 +10,125 @@ const initialValue = '';
1010
const baseProps = {
1111
value: { en: initialValue, de: initialValue },
1212
id: 'rich-text-input',
13+
'data-testid': 'rich-text-data-test',
1314
onChange: () => {},
1415
};
1516

1617
describe('LocalizedRichTextInput', () => {
1718
it('should have an HTML name', () => {
18-
const { getByLabelText } = render(
19+
const { getByTestId } = render(
1920
<LocalizedRichTextInput {...baseProps} name="foo" selectedLanguage="en" />
2021
);
21-
expect(getByLabelText('EN')).toHaveAttribute('name', 'foo.en');
22+
expect(getByTestId('rich-text-data-test-en')).toHaveAttribute(
23+
'name',
24+
'foo.en'
25+
);
2226
});
2327
describe('when collapsed', () => {
2428
it('should render input the selected languages (en)', () => {
25-
const { getByLabelText, queryByLabelText } = render(
29+
const { getByTestId, queryByLabelText } = render(
2630
<LocalizedRichTextInput {...baseProps} selectedLanguage="en" />
2731
);
28-
expect(getByLabelText('EN')).toBeInTheDocument();
29-
expect(queryByLabelText('DE')).not.toBeInTheDocument();
32+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
33+
expect(
34+
queryByLabelText('rich-text-data-test-de')
35+
).not.toBeInTheDocument();
3036
});
3137
});
3238

3339
describe('when expanded', () => {
3440
it('should render inputs for all the languages (en, de)', () => {
35-
const { getByLabelText } = render(
41+
const { getByTestId } = render(
3642
<LocalizedRichTextInput
3743
{...baseProps}
3844
selectedLanguage="en"
3945
defaultExpandLanguages={true}
4046
/>
4147
);
42-
expect(getByLabelText('EN')).toBeInTheDocument();
43-
expect(getByLabelText('DE')).toBeInTheDocument();
48+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
49+
expect(getByTestId('rich-text-data-test-de')).toBeInTheDocument();
4450
});
4551
});
4652
describe('when expansion controls are hidden', () => {
4753
it('should render one input per language and no hide button', () => {
48-
const { getByLabelText, queryByLabelText } = render(
54+
const { getByTestId, queryByLabelText } = render(
4955
<LocalizedRichTextInput
5056
{...baseProps}
5157
selectedLanguage="en"
5258
hideLanguageExpansionControls={true}
5359
/>
5460
);
55-
expect(getByLabelText('EN')).toBeInTheDocument();
56-
expect(getByLabelText('DE')).toBeInTheDocument();
61+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
62+
expect(getByTestId('rich-text-data-test-de')).toBeInTheDocument();
5763
expect(queryByLabelText(/hide languages/i)).not.toBeInTheDocument();
5864
});
5965
});
6066

6167
describe('when disabled', () => {
6268
describe('when expanded', () => {
6369
it('should render a disabled input for each language (en, de)', () => {
64-
const { getByLabelText } = render(
70+
const { getByLabelText, getByTestId } = render(
6571
<LocalizedRichTextInput
6672
{...baseProps}
6773
selectedLanguage="en"
6874
isDisabled={true}
6975
/>
7076
);
7177
getByLabelText(/show all languages/i).click();
72-
expect(getByLabelText('EN')).toHaveAttribute('disabled');
73-
expect(getByLabelText('DE')).toHaveAttribute('disabled');
78+
expect(getByTestId('rich-text-data-test-en')).toHaveAttribute(
79+
'disabled'
80+
);
81+
expect(getByTestId('rich-text-data-test-de')).toHaveAttribute(
82+
'disabled'
83+
);
7484
});
7585
});
7686
describe('when not expanded', () => {
7787
it('should render a disabled input', () => {
78-
const { getByLabelText } = render(
88+
const { getByTestId } = render(
7989
<LocalizedRichTextInput
8090
{...baseProps}
8191
selectedLanguage="en"
8292
isDisabled={true}
8393
/>
8494
);
85-
expect(getByLabelText('EN')).toHaveAttribute('disabled');
95+
expect(getByTestId('rich-text-data-test-en')).toHaveAttribute(
96+
'disabled'
97+
);
8698
});
8799
});
88100
});
89101
describe('when readonly', () => {
90102
describe('when expanded', () => {
91103
it('should render a readonly input for each language (en, de)', () => {
92-
const { getByLabelText } = render(
104+
const { getByLabelText, getByTestId } = render(
93105
<LocalizedRichTextInput
94106
{...baseProps}
95107
selectedLanguage="en"
96108
isReadOnly={true}
97109
/>
98110
);
99111
getByLabelText(/show all languages/i).click();
100-
expect(getByLabelText('EN')).not.toHaveAttribute('contenteditable');
101-
expect(getByLabelText('DE')).not.toHaveAttribute('contenteditable');
112+
expect(getByTestId('rich-text-data-test-en')).not.toHaveAttribute(
113+
'contenteditable'
114+
);
115+
expect(getByTestId('rich-text-data-test-de')).not.toHaveAttribute(
116+
'contenteditable'
117+
);
102118
});
103119
});
104120
describe('when not expanded', () => {
105121
it('should render a disabled input', () => {
106-
const { getByLabelText } = render(
122+
const { getByTestId } = render(
107123
<LocalizedRichTextInput
108124
{...baseProps}
109125
selectedLanguage="en"
110126
isReadOnly={true}
111127
/>
112128
);
113-
expect(getByLabelText('EN')).not.toHaveAttribute('contenteditable');
129+
expect(getByTestId('rich-text-data-test-en')).not.toHaveAttribute(
130+
'contenteditable'
131+
);
114132
});
115133
});
116134
});
@@ -120,15 +138,15 @@ describe('LocalizedRichTextInput', () => {
120138
de: 'Another error',
121139
};
122140
it('should be open all fields and render errors', () => {
123-
const { getByLabelText, getByText } = render(
141+
const { getByText, getByTestId } = render(
124142
<LocalizedRichTextInput
125143
{...baseProps}
126144
selectedLanguage="en"
127145
errors={errors}
128146
/>
129147
);
130-
expect(getByLabelText('EN')).toBeInTheDocument();
131-
expect(getByLabelText('DE')).toBeInTheDocument();
148+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
149+
expect(getByTestId('rich-text-data-test-de')).toBeInTheDocument();
132150
expect(getByText(errors.en)).toBeInTheDocument();
133151
expect(getByText(errors.de)).toBeInTheDocument();
134152
});
@@ -139,15 +157,15 @@ describe('LocalizedRichTextInput', () => {
139157
de: 'An error',
140158
};
141159
it('should be open all fields and render errors', () => {
142-
const { getByLabelText, getByText } = render(
160+
const { getByText, getByTestId } = render(
143161
<LocalizedRichTextInput
144162
{...baseProps}
145163
selectedLanguage="en"
146164
errors={errors}
147165
/>
148166
);
149-
expect(getByLabelText('EN')).toBeInTheDocument();
150-
expect(getByLabelText('DE')).toBeInTheDocument();
167+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
168+
expect(getByTestId('rich-text-data-test-de')).toBeInTheDocument();
151169
expect(getByText(errors.de)).toBeInTheDocument();
152170
});
153171
});
@@ -156,16 +174,18 @@ describe('LocalizedRichTextInput', () => {
156174
const errors = {
157175
en: 'A value required',
158176
};
159-
const { getByLabelText, getByText, queryByLabelText } = render(
177+
const { getByText, getByTestId, queryByLabelText } = render(
160178
<LocalizedRichTextInput
161179
{...baseProps}
162180
selectedLanguage="en"
163181
errors={errors}
164182
/>
165183
);
166-
expect(getByLabelText('EN')).toBeInTheDocument();
184+
expect(getByTestId('rich-text-data-test-en')).toBeInTheDocument();
167185
expect(getByText(errors.en)).toBeInTheDocument();
168-
expect(queryByLabelText('DE')).not.toBeInTheDocument();
186+
expect(
187+
queryByLabelText('rich-text-data-test-de')
188+
).not.toBeInTheDocument();
169189
});
170190
});
171191
});

src/components/inputs/localized-rich-text-input/localized-rich-text-input.visualroute.js

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const WrappedComponent = ({ onChange, value }) => {
2929
return (
3030
<LocalizedRichTextInput
3131
id="rich-text"
32+
name="rich-text"
33+
data-testid="rich-text-data-test"
3234
onChange={handleChange}
3335
value={value}
3436
selectedLanguage="en"

src/components/inputs/localized-rich-text-input/localized-rich-text-input.visualspec.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { percySnapshot } from '@percy/puppeteer';
22
import { getDocument, queries, wait } from 'pptr-testing-library';
33

4-
const { getByLabelText, getAllByLabelText, getByText } = queries;
4+
const { getByLabelText, getByTestId, getAllByLabelText, getByText } = queries;
55

66
describe('LocalizedRichTextInput', () => {
77
const selectAllText = async input => {
@@ -33,7 +33,7 @@ describe('LocalizedRichTextInput', () => {
3333
it('Interactive', async () => {
3434
await page.goto(`${HOST}/localized-rich-text-input/interactive`);
3535
const doc = await getDocument(page);
36-
let input = await getByLabelText(doc, 'EN');
36+
let input = await getByTestId(doc, 'rich-text-data-test-en');
3737

3838
// make the text bold
3939
let boldButton = await getByLabelText(doc, 'Bold');
@@ -66,7 +66,7 @@ describe('LocalizedRichTextInput', () => {
6666
expect(boldButtons.length).toBe(3);
6767

6868
// switch to german input
69-
input = await getByLabelText(doc, 'DE');
69+
input = await getByTestId(doc, 'rich-text-data-test-de');
7070

7171
boldButton = boldButtons[1];
7272

src/components/inputs/rich-text-input/editor.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { AngleUpIcon, AngleDownIcon } from '../../icons';
1111
import Constraints from '../../constraints';
1212
import FlatButton from '../../buttons/flat-button';
1313
import RichTextBody from '../../internals/rich-text-body';
14+
import HiddenInput from '../../internals/rich-text-body/hidden-input';
1415
import messages from '../../internals/messages/multiline-input';
1516

1617
const COLLAPSED_HEIGHT = 32;
@@ -106,10 +107,14 @@ const Editor = props => {
106107

107108
// eslint-disable-next-line react/display-name
108109
const renderEditor = (props, editor, next) => {
110+
const internalId = `${props.id}__internal__id`;
111+
109112
const children = React.cloneElement(next(), {
110-
tagName: 'output',
113+
id: internalId,
111114
});
112115

116+
const isFocused = props.editor.value.selection.isFocused;
117+
113118
const passedProps = {
114119
name: props.name,
115120
id: props.id,
@@ -129,6 +134,11 @@ const renderEditor = (props, editor, next) => {
129134
return (
130135
<Editor editor={editor} {...passedProps}>
131136
{children}
137+
<HiddenInput
138+
isFocused={isFocused}
139+
handleFocus={editor.focus}
140+
id={props.id}
141+
/>
132142
</Editor>
133143
);
134144
};

src/components/inputs/rich-text-input/rich-text-input.story.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ storiesOf('Components|Inputs', module)
5757

5858
const onBlur = React.useCallback(action('onBlur'), []);
5959
const onFocus = React.useCallback(action('onFocus'), []);
60-
60+
const id = text('id', 'test-id');
6161
return (
6262
<Section>
6363
<Spacings.Stack scale="l">
@@ -72,6 +72,7 @@ storiesOf('Components|Inputs', module)
7272
/>
7373
)}
7474
/>
75+
<label htmlFor={id}>Rich Text</label>
7576
<Input
7677
id={text('id', 'test-id')}
7778
name={text('name', 'test-name')}

src/components/inputs/rich-text-input/rich-text-input.visualroute.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const InteractiveRoute = () => {
4141
</div>
4242
<label htmlFor="rich-text">Rich text</label>
4343
<RichTextInput
44+
data-testid="rich-text"
4445
id="rich-text"
4546
onChange={onChange}
4647
value={value}

src/components/inputs/rich-text-input/rich-text-input.visualspec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { percySnapshot } from '@percy/puppeteer';
22
import { getDocument, queries, wait } from 'pptr-testing-library';
33

4-
const { getByLabelText, getByText } = queries;
4+
const { getByLabelText, getByTestId, getByText } = queries;
55

66
describe('RichTextInput', () => {
77
const blur = async element => {
@@ -39,7 +39,7 @@ describe('RichTextInput', () => {
3939
it('Interactive', async () => {
4040
await page.goto(`${HOST}/rich-text-input/interactive`);
4141
const doc = await getDocument(page);
42-
const input = await getByLabelText(doc, 'Rich text');
42+
const input = await getByTestId(doc, 'rich-text');
4343

4444
// make the text bold
4545
const boldButton = await getByLabelText(doc, 'Bold');

0 commit comments

Comments
 (0)