Adjacent text nodes in SSR should have comments between them despite components (#11109)
* Add a failing test for text nodes within components * Reset the text node flag only when emitting tags This ensures we don't reset it when we exit components that return strings. * Add a failing test for a more complex tree * Also reset text flag when emitting a footer This fixes the {'a'}</div>{'b'} case and prevents an unnecessary comment before 'b'.
Dan Abramov committed
Oct 5, 2017 at 20:05 UTC
32ec797274ae2b956bab45aa335be50026ce1209
2 files changed
+79
-2
src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+73
@@ -1117,6 +1117,79 @@ describe('ReactDOMServerIntegration', () => {
1117
expectTextNode(e.childNodes[1], 'bar');
1118
}
1119
});
1120
+
1121
+ itRenders(
1122
+ 'a component returning text node between two text nodes',
1123
+ async render => {
1124
+ const B = () => 'b';
1125
+ const e = await render(<div>{'a'}<B />{'c'}</div>);
1126
+ if (
1127
+ render === serverRender ||
1128
+ render === clientRenderOnServerString ||
1129
+ render === streamRender
1130
+ ) {
1131
+ // In the server render output there's a comment between them.
1132
+ expect(e.childNodes.length).toBe(5);
1133
+ expectTextNode(e.childNodes[0], 'a');
1134
+ expectTextNode(e.childNodes[2], 'b');
1135
+ expectTextNode(e.childNodes[4], 'c');
1136
+ } else {
1137
+ expect(e.childNodes.length).toBe(3);
1138
+ expectTextNode(e.childNodes[0], 'a');
1139
+ expectTextNode(e.childNodes[1], 'b');
1140
+ expectTextNode(e.childNodes[2], 'c');
1141
+ }
1142
+ },
1143
+ );
1144
+
1145
+ itRenders('a tree with sibling host and text nodes', async render => {
1146
+ class X extends React.Component {
1147
+ render() {
1148
+ return [null, [<Y key="1" />], false];
1149
+ }
1150
+ }
1151
+
1152
+ function Y() {
1153
+ return [<Z key="1" />, ['c']];
1154
+ }
1155
+
1156
+ function Z() {
1157
+ return null;
1158
+ }
1159
+
1160
+ const e = await render(
1161
+ <div>
1162
+ {[['a'], 'b']}
1163
+ <div>
1164
+ <X key="1" />
1165
+ d
1166
+ </div>
1167
+ e
1168
+ </div>,
1169
+ );
1170
+ if (
1171
+ render === serverRender ||
1172
+ render === clientRenderOnServerString ||
1173
+ render === streamRender
1174
+ ) {
1175
+ // In the server render output there's comments between text nodes.
1176
+ expect(e.childNodes.length).toBe(5);
1177
+ expectTextNode(e.childNodes[0], 'a');
1178
+ expectTextNode(e.childNodes[2], 'b');
1179
+ expect(e.childNodes[3].childNodes.length).toBe(3);
1180
+ expectTextNode(e.childNodes[3].childNodes[0], 'c');
1181
+ expectTextNode(e.childNodes[3].childNodes[2], 'd');
1182
+ expectTextNode(e.childNodes[4], 'e');
1183
+ } else {
1184
+ expect(e.childNodes.length).toBe(4);
1185
+ expectTextNode(e.childNodes[0], 'a');
1186
+ expectTextNode(e.childNodes[1], 'b');
1187
+ expect(e.childNodes[2].childNodes.length).toBe(2);
1188
+ expectTextNode(e.childNodes[2].childNodes[0], 'c');
1189
+ expectTextNode(e.childNodes[2].childNodes[1], 'd');
1190
+ expectTextNode(e.childNodes[3], 'e');
1191
+ }
1192
+ });
1193
});
1194
1195
describe('number children', function() {
src/renderers/shared/server/ReactPartialRenderer.js
+6
-2
@@ -475,8 +475,11 @@ class ReactDOMServerRenderer {
475
}
476
var frame = this.stack[this.stack.length - 1];
477
if (frame.childIndex >= frame.children.length) {
478
- out += frame.footer;
479
- this.previousWasTextNode = false;
478
+ var footer = frame.footer;
479
+ out += footer;
480
+ if (footer !== '') {
481
+ this.previousWasTextNode = false;
482
+ }
483
this.stack.pop();
484
if (frame.tag === 'select') {
485
this.currentSelectValue = null;
@@ -827,6 +830,7 @@ class ReactDOMServerRenderer {
830
frame.debugElementStack = [];
831
}
832
this.stack.push(frame);
833
+ this.previousWasTextNode = false;
834
return out;
835
}
836
}