aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/attributes/attr.js38
-rw-r--r--test/unit/attributes.js86
-rw-r--r--test/unit/selector.js8
3 files changed, 84 insertions, 48 deletions
diff --git a/src/attributes/attr.js b/src/attributes/attr.js
index 426a8e524..118895916 100644
--- a/src/attributes/attr.js
+++ b/src/attributes/attr.js
@@ -38,7 +38,14 @@ jQuery.extend( {
}
if ( value !== undefined ) {
- if ( value === null ) {
+ if ( value === null ||
+
+ // For compat with previous handling of boolean attributes,
+ // remove when `false` passed. For ARIA attributes -
+ // many of which recognize a `"false"` value - continue to
+ // set the `"false"` value as jQuery <4 did.
+ ( value === false && name.toLowerCase().indexOf( "aria-" ) !== 0 ) ) {
+
jQuery.removeAttr( elem, name );
return;
}
@@ -96,32 +103,3 @@ if ( isIE ) {
}
};
}
-
-// HTML boolean attributes have special behavior:
-// we consider the lowercase name to be the only valid value, so
-// getting (if the attribute is present) normalizes to that, as does
-// setting to any non-`false` value (and setting to `false` removes the attribute).
-// See https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
-jQuery.each( (
- "checked selected async autofocus autoplay controls defer disabled " +
- "hidden ismap loop multiple open readonly required scoped"
-).split( " " ), function( _i, name ) {
- jQuery.attrHooks[ name ] = {
- get: function( elem ) {
- return elem.getAttribute( name ) != null ?
- name.toLowerCase() :
- null;
- },
-
- set: function( elem, value, name ) {
- if ( value === false ) {
-
- // Remove boolean attributes when set to false
- jQuery.removeAttr( elem, name );
- } else {
- elem.setAttribute( name, name );
- }
- return name;
- }
- };
-} );
diff --git a/test/unit/attributes.js b/test/unit/attributes.js
index b9647e6a7..477f3464a 100644
--- a/test/unit/attributes.js
+++ b/test/unit/attributes.js
@@ -304,12 +304,12 @@ QUnit.test( "attr(String, Object)", function( assert ) {
assert.equal( $input[ 0 ].selected, true, "Setting selected updates property (verified by native property)" );
$input = jQuery( "#check2" );
- $input.prop( "checked", true ).prop( "checked", false ).attr( "checked", true );
+ $input.prop( "checked", true ).prop( "checked", false ).attr( "checked", "checked" );
assert.equal( $input.attr( "checked" ), "checked", "Set checked (verified by .attr)" );
$input.prop( "checked", false ).prop( "checked", true ).attr( "checked", false );
assert.equal( $input.attr( "checked" ), undefined, "Remove checked (verified by .attr)" );
- $input = jQuery( "#text1" ).prop( "readOnly", true ).prop( "readOnly", false ).attr( "readonly", true );
+ $input = jQuery( "#text1" ).prop( "readOnly", true ).prop( "readOnly", false ).attr( "readonly", "readonly" );
assert.equal( $input.attr( "readonly" ), "readonly", "Set readonly (verified by .attr)" );
$input.prop( "readOnly", false ).prop( "readOnly", true ).attr( "readonly", false );
assert.equal( $input.attr( "readonly" ), undefined, "Remove readonly (verified by .attr)" );
@@ -318,7 +318,7 @@ QUnit.test( "attr(String, Object)", function( assert ) {
assert.equal( $input[ 0 ].checked, true, "Set checked property (verified by native property)" );
assert.equal( $input.prop( "checked" ), true, "Set checked property (verified by .prop)" );
assert.equal( $input.attr( "checked" ), undefined, "Setting checked property doesn't affect checked attribute" );
- $input.attr( "checked", false ).attr( "checked", true ).prop( "checked", false );
+ $input.attr( "checked", false ).attr( "checked", "checked" ).prop( "checked", false );
assert.equal( $input[ 0 ].checked, false, "Clear checked property (verified by native property)" );
assert.equal( $input.prop( "checked" ), false, "Clear checked property (verified by .prop)" );
assert.equal( $input.attr( "checked" ), "checked", "Clearing checked property doesn't affect checked attribute" );
@@ -345,8 +345,8 @@ QUnit.test( "attr(String, Object)", function( assert ) {
// HTML5 boolean attributes
$text = jQuery( "#text1" ).attr( {
- "autofocus": true,
- "required": true
+ "autofocus": "autofocus",
+ "required": "required"
} );
assert.equal( $text.attr( "autofocus" ), "autofocus", "Reading autofocus attribute yields 'autofocus'" );
assert.equal( $text.attr( "autofocus", false ).attr( "autofocus" ), undefined, "Setting autofocus to false removes it" );
@@ -354,13 +354,13 @@ QUnit.test( "attr(String, Object)", function( assert ) {
assert.equal( $text.attr( "required", false ).attr( "required" ), undefined, "Setting required attribute to false removes it" );
$details = jQuery( "<details open></details>" ).appendTo( "#qunit-fixture" );
- assert.equal( $details.attr( "open" ), "open", "open attribute presence indicates true" );
+ assert.equal( $details.attr( "open" ), "", "open attribute presence indicates true" );
assert.equal( $details.attr( "open", false ).attr( "open" ), undefined, "Setting open attribute to false removes it" );
$text.attr( "data-something", true );
assert.equal( $text.attr( "data-something" ), "true", "Set data attributes" );
assert.equal( $text.data( "something" ), true, "Setting data attributes are not affected by boolean settings" );
- $text.attr( "data-another", false );
+ $text.attr( "data-another", "false" );
assert.equal( $text.attr( "data-another" ), "false", "Set data attributes" );
assert.equal( $text.data( "another" ), false, "Setting data attributes are not affected by boolean settings" );
assert.equal( $text.attr( "aria-disabled", false ).attr( "aria-disabled" ), "false", "Setting aria attributes are not affected by boolean settings" );
@@ -1790,14 +1790,12 @@ QUnit.test( "non-lowercase boolean attribute getters should not crash", function
var elem = jQuery( "<input checked required autofocus type='checkbox'>" );
- jQuery.each( {
- checked: "Checked",
- required: "requiRed",
- autofocus: "AUTOFOCUS"
- }, function( lowercased, original ) {
+ [
+ "Checked", "requiRed", "AUTOFOCUS"
+ ].forEach( function( inconsistentlyCased ) {
try {
- assert.strictEqual( elem.attr( original ), lowercased,
- "The '" + this + "' attribute getter should return the lowercased name" );
+ assert.strictEqual( elem.attr( inconsistentlyCased ), "",
+ "The '" + this + "' attribute getter should return an empty string" );
} catch ( e ) {
assert.ok( false, "The '" + this + "' attribute getter threw" );
}
@@ -1805,6 +1803,66 @@ QUnit.test( "non-lowercase boolean attribute getters should not crash", function
} );
+QUnit.test( "false setter removes non-ARIA attrs (gh-5388)", function( assert ) {
+ assert.expect( 24 );
+
+ var elem = jQuery( "<input" +
+ " checked required autofocus" +
+ " type='checkbox'" +
+ " title='Example title'" +
+ " class='test-class'" +
+ " style='color: brown'" +
+ " aria-hidden='true'" +
+ " aria-checked='true'" +
+ " aria-label='Example ARIA label'" +
+ " data-prop='Example data value'" +
+ " data-title='Example data title'" +
+ " data-true='true'" +
+ ">" );
+
+ function testFalseSetter( attributes, options ) {
+ var removal = options.removal;
+
+ attributes.forEach( function( attrName ) {
+ assert.ok( elem.attr( attrName ) != null,
+ "Attribute '" + attrName + "': initial defined value" );
+ elem.attr( attrName, false );
+
+ if ( removal ) {
+ assert.strictEqual( elem.attr( attrName ), undefined,
+ "Attribute '" + attrName + "' removed" );
+ } else {
+ assert.strictEqual( elem.attr( attrName ), "false",
+ "Attribute '" + attrName + "' set to 'false'" );
+ }
+ } );
+ }
+
+ // Boolean attributes
+ testFalseSetter(
+ [ "checked", "required", "autofocus" ],
+ { removal: true }
+ );
+
+ // Regular attributes
+ testFalseSetter(
+ [ "title", "class", "style" ],
+ { removal: true }
+ );
+
+ // `aria-*` attributes
+ testFalseSetter(
+ [ "aria-hidden", "aria-checked", "aria-label" ],
+ { removal: false }
+ );
+
+ // `data-*` attributes
+ testFalseSetter(
+ [ "data-prop", "data-title", "data-true" ],
+ { removal: true }
+ );
+} );
+
// Test trustedTypes support in browsers where they're supported (currently Chrome 83+).
// Browsers with no TrustedScriptURL support still run tests on object wrappers with
// a proper `toString` function.
diff --git a/test/unit/selector.js b/test/unit/selector.js
index 2a2481a05..419120e7d 100644
--- a/test/unit/selector.js
+++ b/test/unit/selector.js
@@ -948,10 +948,10 @@ QUnit.test( "pseudo - nth-last-of-type", function( assert ) {
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "pseudo - has", function( assert ) {
assert.expect( 4 );
- assert.t( "Basic test", "p:has(a)", [ "firstp", "ap", "en", "sap" ] );
- assert.t( "Basic test (irrelevant whitespace)", "p:has( a )", [ "firstp", "ap", "en", "sap" ] );
- assert.t( "Nested with overlapping candidates",
- "#qunit-fixture div:has(div:has(div:not([id])))",
+ assert.selectInFixture( "Basic test", "p:has(a)", [ "firstp", "ap", "en", "sap" ] );
+ assert.selectInFixture( "Basic test (irrelevant whitespace)", "p:has( a )", [ "firstp", "ap", "en", "sap" ] );
+ assert.selectInFixture( "Nested with overlapping candidates",
+ "div:has(div:has(div:not([id])))",
[ "moretests", "t2037", "fx-test-group", "fx-queue" ] );
// Support: Safari 15.4+, Chrome 105+