Skip to content

Commit dee06b8

Browse files
committed
Fix cloneElement when child is null or string
1 parent 50cb03a commit dee06b8

File tree

2 files changed

+71
-28
lines changed

2 files changed

+71
-28
lines changed

fluent-react/src/localized.js

+27-22
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,41 @@ export default class Localized extends Component {
7979

8080
render() {
8181
const { l10n, parseMarkup } = this.context;
82-
const { id, attrs, children: elem = null } = this.props;
82+
const { id, attrs, children: child = null } = this.props;
8383

8484
// Validate that the child element isn't an array
85-
if (Array.isArray(elem)) {
85+
if (Array.isArray(child)) {
8686
throw new Error("<Localized/> expected to receive a single " +
8787
"React node child");
8888
}
8989

9090
if (!l10n) {
9191
// Use the wrapped component as fallback.
92-
return elem;
92+
return child;
9393
}
9494

9595
const bundle = l10n.getBundle(id);
9696

9797
if (bundle === null) {
9898
// Use the wrapped component as fallback.
99-
return elem;
99+
return child;
100100
}
101101

102102
const msg = bundle.getMessage(id);
103103
const [args, elems] = toArguments(this.props);
104104

105+
// Check if the child inside <Localized> is a valid element -- if not, then
106+
// it's either null or a simple fallback string. No need to localize the
107+
// attributes.
108+
if (!isValidElement(child)) {
109+
if (msg.value) {
110+
// Replace the fallback string with the message value;
111+
return bundle.formatPattern(msg.value, args);
112+
}
113+
114+
return child;
115+
}
116+
105117
// The default is to forbid all message attributes. If the attrs prop exists
106118
// on the Localized instance, only set message attributes which have been
107119
// explicitly allowed by the developer.
@@ -115,34 +127,27 @@ export default class Localized extends Component {
115127
}
116128
}
117129

130+
// If the wrapped component is a known void element, explicitly dismiss the
131+
// message value and do not pass it to cloneElement in order to avoid the
132+
// "void element tags must neither have `children` nor use
133+
// `dangerouslySetInnerHTML`" error.
134+
if (child.type in VOID_ELEMENTS) {
135+
return cloneElement(child, localizedProps);
136+
}
137+
118138
// If the message has a null value, we're only interested in its attributes.
119139
// Do not pass the null value to cloneElement as it would nuke all children
120140
// of the wrapped component.
121141
if (msg.value === null) {
122-
return cloneElement(elem, localizedProps);
142+
return cloneElement(child, localizedProps);
123143
}
124144

125145
const messageValue = bundle.formatPattern(msg.value, args);
126146

127-
// Check if the fallback is a valid element -- if not then it's not
128-
// markup (e.g. nothing or a fallback string) so just use the
129-
// formatted message value
130-
if (!isValidElement(elem)) {
131-
return messageValue;
132-
}
133-
134-
// If the wrapped component is a known void element, explicitly dismiss the
135-
// message value and do not pass it to cloneElement in order to avoid the
136-
// "void element tags must neither have `children` nor use
137-
// `dangerouslySetInnerHTML`" error.
138-
if (elem.type in VOID_ELEMENTS) {
139-
return cloneElement(elem, localizedProps);
140-
}
141-
142147
// If the message value doesn't contain any markup nor any HTML entities,
143148
// insert it as the only child of the wrapped component.
144149
if (!reMarkup.test(messageValue)) {
145-
return cloneElement(elem, localizedProps, messageValue);
150+
return cloneElement(child, localizedProps, messageValue);
146151
}
147152

148153
// If the message contains markup, parse it and try to match the children
@@ -175,7 +180,7 @@ export default class Localized extends Component {
175180
return cloneElement(sourceChild, null, childNode.textContent);
176181
});
177182

178-
return cloneElement(elem, localizedProps, ...translatedChildren);
183+
return cloneElement(child, localizedProps, ...translatedChildren);
179184
}
180185
}
181186

fluent-react/test/localized_render_test.js

+44-6
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,25 @@ foo = { $arg }
278278
assert.equal(wrapper.text(), 'String fallback');
279279
});
280280

281+
test('render with a string fallback and no message value preserves the fallback',
282+
function() {
283+
const mcx = new FluentBundle();
284+
const l10n = new ReactLocalization([mcx]);
285+
mcx.addMessages(`
286+
foo =
287+
.attr = Attribute
288+
`)
289+
290+
const wrapper = shallow(
291+
<Localized id="foo">
292+
String fallback
293+
</Localized>,
294+
{ context: { l10n } }
295+
);
296+
297+
assert.equal(wrapper.text(), 'String fallback');
298+
});
299+
281300
test('render with a string fallback returns the message', function() {
282301
const mcx = new FluentBundle();
283302
const l10n = new ReactLocalization([mcx]);
@@ -295,32 +314,51 @@ foo = Test message
295314
assert.equal(wrapper.text(), 'Test message');
296315
});
297316

298-
test('render without a fallback returns the message', function() {
317+
test('render without a fallback and no message returns nothing',
318+
function() {
319+
const mcx = new FluentBundle();
320+
const l10n = new ReactLocalization([mcx]);
321+
322+
const wrapper = shallow(
323+
<Localized id="foo" />,
324+
{ context: { l10n } }
325+
);
326+
327+
assert.equal(wrapper.text(), '');
328+
});
329+
330+
test('render without a fallback and no message value returns nothing',
331+
function() {
299332
const mcx = new FluentBundle();
300333
const l10n = new ReactLocalization([mcx]);
301334

302335
mcx.addMessages(`
303-
foo = Message
336+
foo =
337+
.attr = Attribute
304338
`)
305339

306340
const wrapper = shallow(
307341
<Localized id="foo" />,
308342
{ context: { l10n } }
309343
);
310344

311-
assert.equal(wrapper.text(), 'Message');
345+
assert.equal(wrapper.text(), '');
312346
});
313347

314-
test('render without a fallback and no message returns nothing',
315-
function() {
348+
test('render without a fallback returns the message', function() {
316349
const mcx = new FluentBundle();
317350
const l10n = new ReactLocalization([mcx]);
318351

352+
mcx.addMessages(`
353+
foo = Message
354+
`)
355+
319356
const wrapper = shallow(
320357
<Localized id="foo" />,
321358
{ context: { l10n } }
322359
);
323360

324-
assert.equal(wrapper.text(), '');
361+
assert.equal(wrapper.text(), 'Message');
325362
});
363+
326364
});

0 commit comments

Comments
 (0)