Skip to content

Commit b450f7d

Browse files
authored
Merge pull request #268 from cibernox/fix-double-class-bug
Append extra classes to existing ones in the body and html tags
2 parents 952140f + eed49df commit b450f7d

File tree

12 files changed

+176177
-21
lines changed

12 files changed

+176177
-21
lines changed

src/result.js

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ class Result {
5252
return insertIntoIndexHTML(
5353
this._html,
5454
this._htmlAttributes,
55+
this._htmlClass,
5556
this._head,
5657
this._body,
57-
this._bodyAttributes
58+
this._bodyAttributes,
59+
this._bodyClass
5860
);
5961
}
6062

@@ -73,9 +75,11 @@ class Result {
7375
return insertIntoIndexHTML(
7476
this._html,
7577
this._htmlAttributes,
78+
this._htmlClass,
7679
this._head,
7780
this._body,
78-
this._bodyAttributes
81+
this._bodyAttributes,
82+
this._bodyClass
7983
).then(html => {
8084
let docParts = html.match(HTML_HEAD_REGEX);
8185
if (!docParts || docParts.length === 1) {
@@ -174,17 +178,13 @@ class Result {
174178
let head = this._doc.head;
175179
let body = this._doc.body;
176180

177-
if (htmlElement.attributes.length > 0) {
178-
this._htmlAttributes = HTMLSerializer.attributes(htmlElement.attributes);
179-
} else {
180-
this._htmlAttributes = null;
181-
}
181+
let { klass: htmlClass, attributes: htmlAttributes } = extractExtraAttributes(htmlElement);
182+
this._htmlClass = htmlClass;
183+
this._htmlAttributes = htmlAttributes;
182184

183-
if (body.attributes.length > 0) {
184-
this._bodyAttributes = HTMLSerializer.attributes(body.attributes);
185-
} else {
186-
this._bodyAttributes = null;
187-
}
185+
let { klass: bodyClass, attributes: bodyAttributes } = extractExtraAttributes(body);
186+
this._bodyClass = bodyClass;
187+
this._bodyAttributes = bodyAttributes;
188188

189189
if (head) {
190190
head = HTMLSerializer.serializeChildren(head);
@@ -199,13 +199,57 @@ class Result {
199199
}
200200
}
201201

202+
function extractExtraAttributes(element) {
203+
let klass;
204+
let attributes;
205+
if (element.attributes.length > 0) {
206+
let elementClass = element.attributes.find(attr => attr.name === 'class');
207+
if (elementClass) {
208+
klass = elementClass;
209+
let otherAttrs = element.attributes.filter(attr => attr.name !== 'class');
210+
if (otherAttrs.length > 0) {
211+
attributes = HTMLSerializer.attributes(otherAttrs);
212+
} else {
213+
attributes = null;
214+
}
215+
} else {
216+
attributes = HTMLSerializer.attributes(element.attributes);
217+
klass = null;
218+
}
219+
} else {
220+
klass = attributes = null;
221+
}
222+
return { klass, attributes };
223+
}
224+
202225
function missingTag(tag) {
203226
throw new Error(
204227
`Fastboot was not able to find ${tag} in base HTML. It could not replace the contents.`
205228
);
206229
}
207230

208-
async function insertIntoIndexHTML(html, htmlAttributes, head, body, bodyAttributes) {
231+
function addClass(html, regex, newClass) {
232+
return html.replace(regex, function(_, tag, attributes) {
233+
if (/class="([^"]*)"/i.test(attributes)) {
234+
attributes = attributes.replace(/class="([^"]*)"/i, function(_, klass) {
235+
return `class="${klass} ${newClass}"`;
236+
});
237+
} else {
238+
attributes += ' class="' + newClass + '"';
239+
}
240+
return `<${tag}${attributes}>`;
241+
});
242+
}
243+
244+
async function insertIntoIndexHTML(
245+
html,
246+
htmlAttributes,
247+
htmlClass,
248+
head,
249+
body,
250+
bodyAttributes,
251+
bodyClass
252+
) {
209253
if (!html) {
210254
return Promise.resolve(html);
211255
}
@@ -223,12 +267,18 @@ async function insertIntoIndexHTML(html, htmlAttributes, head, body, bodyAttribu
223267
return '';
224268
});
225269

270+
if (htmlClass) {
271+
html = addClass(html, /<(html)(.*)>/i, htmlClass.value);
272+
}
226273
if (htmlAttributes) {
227274
html = html.replace(/<html[^>]*/i, function(match) {
228275
return match + ' ' + htmlAttributes;
229276
});
230277
}
231278

279+
if (bodyClass) {
280+
html = addClass(html, /<(body)(.*)>/i, bodyClass.value);
281+
}
232282
if (bodyAttributes) {
233283
html = html.replace(/<body[^>]*/i, function(match) {
234284
return match + ' ' + bodyAttributes;

test/fastboot-test.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe('FastBoot', function() {
125125
.visit('/')
126126
.then(r => r.html())
127127
.then(html => {
128-
expect(html).to.match(/<html data-foo=1 +class="it-works"/);
128+
expect(html).to.match(/<html data-before=1 +class="a b it-works" data-after=2/);
129129
});
130130
});
131131

@@ -138,7 +138,35 @@ describe('FastBoot', function() {
138138
.visit('/')
139139
.then(r => r.html())
140140
.then(html => {
141-
expect(html).to.match(/<body data-foo=1 +class="it-works"/);
141+
expect(html).to.match(
142+
/<body data-before=1 +class="no-js default-class it-works" data-after=2/
143+
);
144+
});
145+
});
146+
147+
it('appends classes correctly even when there are no classes in the original body', function() {
148+
var fastboot = new FastBoot({
149+
distPath: fixture('custom-body-attrs-with-no-default-classes'),
150+
});
151+
152+
return fastboot
153+
.visit('/')
154+
.then(r => r.html())
155+
.then(html => {
156+
expect(html).to.match(/<body data-before=1 data-after=2 +class="it-works"/);
157+
});
158+
});
159+
160+
it('appends classes correctly even when there are no classes in the original html', function() {
161+
var fastboot = new FastBoot({
162+
distPath: fixture('custom-html-attrs-with-no-default-classes'),
163+
});
164+
165+
return fastboot
166+
.visit('/')
167+
.then(r => r.html())
168+
.then(html => {
169+
expect(html).to.match(/<html data-before=1 data-after=2 +class="it-works"/);
142170
});
143171
});
144172

0 commit comments

Comments
 (0)