@samitouri / QOS-React / commits / 32ec797274

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 }