Skip to content

Commit b393e4a

Browse files
authoredJun 2, 2021
fix(attributes): Boolean attributes should not be special in xmlMode (#1903)
Fixes #1805
1 parent 41f68b0 commit b393e4a

File tree

2 files changed

+58
-18
lines changed

2 files changed

+58
-18
lines changed
 

‎src/api/attributes.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ describe('$(...)', () => {
179179
expect($('.pear').attr('foo')).toBeUndefined();
180180
expect($pear).toBeInstanceOf($);
181181
});
182+
183+
it("(bool) shouldn't treat boolean attributes differently in XML mode", () => {
184+
const $xml = $.load(`<input checked=checked disabled=yes />`, {
185+
xml: true,
186+
})('input');
187+
188+
expect($xml.attr('checked')).toBe('checked');
189+
expect($xml.attr('disabled')).toBe('yes');
190+
});
182191
});
183192

184193
describe('.prop', () => {
@@ -302,6 +311,15 @@ describe('$(...)', () => {
302311
'<a test-name="tester">1</a>TEXT<b test-name="tester">2</b>'
303312
);
304313
});
314+
315+
it("(bool) shouldn't treat boolean attributes differently in XML mode", () => {
316+
const $xml = $.load(`<input checked=checked disabled=yes />`, {
317+
xml: true,
318+
})('input');
319+
320+
expect($xml.prop('checked')).toBe('checked');
321+
expect($xml.prop('disabled')).toBe('yes');
322+
});
305323
});
306324

307325
describe('.data', () => {

‎src/api/attributes.ts

+40-18
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const primitives: Record<string, unknown> = {
2525
const rboolean =
2626
/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i;
2727
// Matches strings that look like JSON objects or arrays
28-
const rbrace = /^(?:{[\w\W]*}|\[[\w\W]*])$/;
28+
const rbrace = /^{[^]*}$|^\[[^]*]$/;
2929

3030
/**
3131
* Gets a node's attribute. For boolean attributes, it will return the value's
@@ -39,11 +39,20 @@ const rbrace = /^(?:{[\w\W]*}|\[[\w\W]*])$/;
3939
* @param name - Name of the attribute.
4040
* @returns The attribute's value.
4141
*/
42-
function getAttr(elem: Node, name: undefined): Record<string, string>;
43-
function getAttr(elem: Node, name: string): string | undefined;
4442
function getAttr(
4543
elem: Node,
46-
name: string | undefined
44+
name: undefined,
45+
xmlMode?: boolean
46+
): Record<string, string>;
47+
function getAttr(
48+
elem: Node,
49+
name: string,
50+
xmlMode?: boolean
51+
): string | undefined;
52+
function getAttr(
53+
elem: Node,
54+
name: string | undefined,
55+
xmlMode?: boolean
4756
): Record<string, string> | string | undefined {
4857
if (!elem || !isTag(elem)) return undefined;
4958

@@ -56,7 +65,7 @@ function getAttr(
5665

5766
if (hasOwn.call(elem.attribs, name)) {
5867
// Get the (decoded) attribute
59-
return rboolean.test(name) ? name : elem.attribs[name];
68+
return !xmlMode && rboolean.test(name) ? name : elem.attribs[name];
6069
}
6170

6271
// Mimic the DOM and return text content as value for `option's`
@@ -210,7 +219,9 @@ export function attr<T extends Node>(
210219
});
211220
}
212221

213-
return arguments.length > 1 ? this : getAttr(this[0], name as string);
222+
return arguments.length > 1
223+
? this
224+
: getAttr(this[0], name as string, this.options.xmlMode);
214225
}
215226

216227
/**
@@ -224,16 +235,17 @@ export function attr<T extends Node>(
224235
*/
225236
function getProp(
226237
el: Node | undefined,
227-
name: string
238+
name: string,
239+
xmlMode?: boolean
228240
): string | undefined | Element[keyof Element] {
229241
if (!el || !isTag(el)) return;
230242

231243
return name in el
232244
? // @ts-expect-error TS doesn't like us accessing the value directly here.
233245
el[name]
234-
: rboolean.test(name)
235-
? getAttr(el, name) !== undefined
236-
: getAttr(el, name);
246+
: !xmlMode && rboolean.test(name)
247+
? getAttr(el, name, false) !== undefined
248+
: getAttr(el, name, xmlMode);
237249
}
238250

239251
/**
@@ -244,12 +256,16 @@ function getProp(
244256
* @param name - The prop's name.
245257
* @param value - The prop's value.
246258
*/
247-
function setProp(el: Element, name: string, value: unknown) {
259+
function setProp(el: Element, name: string, value: unknown, xmlMode?: boolean) {
248260
if (name in el) {
249261
// @ts-expect-error Overriding value
250262
el[name] = value;
251263
} else {
252-
setAttr(el, name, rboolean.test(name) ? (value ? '' : null) : `${value}`);
264+
setAttr(
265+
el,
266+
name,
267+
!xmlMode && rboolean.test(name) ? (value ? '' : null) : `${value}`
268+
);
253269
}
254270
}
255271

@@ -353,7 +369,7 @@ export function prop<T extends Node>(
353369
return this.html();
354370

355371
default:
356-
return getProp(this[0], name);
372+
return getProp(this[0], name, this.options.xmlMode);
357373
}
358374
}
359375

@@ -363,7 +379,13 @@ export function prop<T extends Node>(
363379
throw new Error('Bad combination of arguments.');
364380
}
365381
return domEach(this, (el, i) => {
366-
if (isTag(el)) setProp(el, name, value.call(el, i, getProp(el, name)));
382+
if (isTag(el))
383+
setProp(
384+
el,
385+
name,
386+
value.call(el, i, getProp(el, name, this.options.xmlMode)),
387+
this.options.xmlMode
388+
);
367389
});
368390
}
369391

@@ -373,10 +395,10 @@ export function prop<T extends Node>(
373395
if (typeof name === 'object') {
374396
Object.keys(name).forEach((key) => {
375397
const val = name[key];
376-
setProp(el, key, val);
398+
setProp(el, key, val, this.options.xmlMode);
377399
});
378400
} else {
379-
setProp(el, name, value);
401+
setProp(el, name, value, this.options.xmlMode);
380402
}
381403
});
382404
}
@@ -810,8 +832,8 @@ export function addClass<T extends Node, R extends ArrayLike<T>>(
810832
// If selected element isn't a tag, move on
811833
if (!isTag(el)) continue;
812834

813-
// If we don't already have classes
814-
const className = getAttr(el, 'class');
835+
// If we don't already have classes — always set xmlMode to false here, as it doesn't matter for classes
836+
const className = getAttr(el, 'class', false);
815837

816838
if (!className) {
817839
setAttr(el, 'class', classNames.join(' ').trim());

0 commit comments

Comments
 (0)
Please sign in to comment.