mirror of
https://github.com/jquery-validation/jquery-validation.git
synced 2025-12-14 20:35:47 +01:00
Core: Unnecessary aria-describedby (#2410)
* Core: progress on removing aria-describedby from valid fields * core: aria-describedby * Core: remove aria-describedby when hiding error * Core: fix syntax issues, Test: add test for new setting * Core: Fix bugs in aria-describedby behavior and related tests * Core: bugfix for labels without id * Core: don't create a new error element if one exists * Core: progress on test for group of fields with ariaDescribedbyCleanup * Core: Groups aria-describedby * Core: fix aria-describedby not being removed from grouped fields Ensure that aria-describedby is removed from all members of a group when all the known errors are resolved * Core: Update capitalization * Demo: Add page for ariaDescribedByCleanup * Core: add setting to remove aria-describedby from valid fields Includes additional unit tests and a demo page * Core: Fix camel case inconsistency, remove stray comment * Update demo/css/cmxform.css Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Kieran <kieran.brahney@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
110
demo/aria-describedby-cleanup.html
Normal file
110
demo/aria-describedby-cleanup.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>jQuery Validation Plugin Demo - ariaDescribedByCleanup set to true</title>
|
||||
<link rel="stylesheet" href="css/screen.css">
|
||||
<script src="../lib/jquery.js"></script>
|
||||
<script src="../dist/jquery.validate.js"></script>
|
||||
<script>
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function() {
|
||||
alert("submitted!");
|
||||
}
|
||||
});
|
||||
|
||||
$().ready(function() {
|
||||
var valid = $("#group-form").validate({
|
||||
errorElement: 'div',
|
||||
|
||||
groups: {
|
||||
fullName: "first middle last"
|
||||
},
|
||||
ariaDescribedByCleanup: true,
|
||||
rules: {
|
||||
|
||||
first: { required: true, minlength: 2 },
|
||||
middle: {required: true, minlength: 2 },
|
||||
last: {required: true},
|
||||
email: { required: true, email: true },
|
||||
phone: { required: true },
|
||||
comment: {required: true, maxlength: 300}
|
||||
}
|
||||
});
|
||||
$('button[type="reset"]').on('click',function(){
|
||||
valid.resetForm();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#group-form {
|
||||
width: 35rem;
|
||||
}
|
||||
.textarea-container {
|
||||
display: inline-block;
|
||||
}
|
||||
.description {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="banner"><a href="https://jqueryvalidation.org/">jQuery Validation Plugin</a> Demo - ariaDescribedByCleanup set to true</h1>
|
||||
<main id="main">
|
||||
|
||||
<form id="group-form" class="cmxform" aria-labelledby="group-example-title" aria-describedby="required-note">
|
||||
|
||||
<div class="box">
|
||||
<h2 id="group-example-title">Example with group</h2>
|
||||
<div><p id="required-note">Fields marked with * are required</p></div>
|
||||
|
||||
<div id="errorlabelcontainer"></div>
|
||||
<fieldset>
|
||||
<legend>Name*</legend>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label for="first">First</label>
|
||||
<input type="text" aria-required="true" id="first" name="first"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="middle">Middle</label>
|
||||
<input type="text" aria-required="true" id="middle" name="middle"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="last">Last</label>
|
||||
<input type="text" aria-required="true" id="last" name="last"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="row">
|
||||
<label for="email">Email*</label>
|
||||
<input type="email" id="email" aria-required="true" name="email"/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label for="phone">Phone*</label>
|
||||
<input type="text" id="phone" aria-required="true" name="phone"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="comment">Your comment*</label>
|
||||
<div class="textarea-container">
|
||||
<textarea id="comment" name="comment" aria-required="true" aria-describedby="comment-max-length"></textarea>
|
||||
<span class="description" id="comment-max-length">300 characters maximum</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<button>Submit</button>
|
||||
<button type="reset">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<p><a href="index.html">Back to main page</a></p>
|
||||
</div>
|
||||
</main>
|
||||
</html>
|
||||
@@ -17,18 +17,36 @@ form.cmxform legend, form.cmxform label {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form.cmxform fieldset {
|
||||
form.cmxform fieldset, form.cmxform .box {
|
||||
border: none;
|
||||
border-top: 1px solid #C9DCA6;
|
||||
background: url(../images/cmxform-fieldset.gif) left bottom repeat-x;
|
||||
background-color: #F8FDEF;
|
||||
}
|
||||
form.cmxform fieldset .col label {
|
||||
margin-left: 0;
|
||||
}
|
||||
form.cmxform fieldset .col {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
form.cmxform fieldset .row .col:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
form.cmxform fieldset .row {
|
||||
display: flex;
|
||||
|
||||
form.cmxform fieldset fieldset {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
form.cmxform .box,
|
||||
form.cmxform fieldset .row,
|
||||
form.cmxform .box fieldset {
|
||||
width: 100%;
|
||||
}
|
||||
form.cmxform fieldset fieldset, form.cmxform .box fieldset {
|
||||
background: none;
|
||||
}
|
||||
|
||||
form.cmxform fieldset p, form.cmxform fieldset fieldset {
|
||||
form.cmxform .box > .row, form.cmxform fieldset p, form.cmxform fieldset fieldset, form.cmxform .box fieldset {
|
||||
padding: 5px 10px 7px;
|
||||
background: url(../images/cmxform-divider.gif) left bottom repeat-x;
|
||||
}
|
||||
@@ -43,4 +61,4 @@ input { border: 1px solid black; }
|
||||
input.checkbox { border: none }
|
||||
input:focus { border: 1px dotted black; }
|
||||
input.error { border: 1px dotted red; }
|
||||
form.cmxform .gray * { color: gray; }
|
||||
form.cmxform .gray * { color: gray; }
|
||||
|
||||
@@ -7,7 +7,7 @@ form.cmxform fieldset {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
form.cmxform legend {
|
||||
form.cmxform legend, form.cmxform .box .title {
|
||||
padding: 0 2px;
|
||||
font-weight: bold;
|
||||
_margin: 0 -7px; /* IE Win */
|
||||
@@ -20,29 +20,35 @@ form.cmxform label {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
form.cmxform fieldset p {
|
||||
form.cmxform fieldset p,
|
||||
form.cmxform .box p {
|
||||
list-style: none;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
form.cmxform fieldset fieldset {
|
||||
form.cmxform fieldset fieldset,
|
||||
form.cmxform .box fieldset {
|
||||
border: none;
|
||||
margin: 3px 0 0;
|
||||
}
|
||||
|
||||
form.cmxform fieldset fieldset legend {
|
||||
form.cmxform fieldset fieldset legend, form.cmxform .box fieldset legend {
|
||||
padding: 0 0 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
form.cmxform fieldset fieldset label {
|
||||
display: block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
form.cmxform label { width: 100px; } /* Width of labels */
|
||||
form.cmxform fieldset fieldset label { margin-left: 103px; } /* Width plus 3 (html space) */
|
||||
form.cmxform fieldset fieldset label,
|
||||
form.cmxform .box fieldset label {
|
||||
display: block;
|
||||
width: auto;
|
||||
/* Width plus 3 (html space) */
|
||||
margin-left: 103px;
|
||||
}
|
||||
|
||||
|
||||
form.cmxform label.error {
|
||||
margin-left: 103px;
|
||||
width: 220px;
|
||||
@@ -52,4 +58,4 @@ form.cmxform input.submit {
|
||||
margin-left: 103px;
|
||||
}
|
||||
|
||||
/*\*//*/ form.cmxform legend { display: inline-block; } /* IE Mac legend fix */
|
||||
/*\*//*/ form.cmxform legend { display: inline-block; } /* IE Mac legend fix */
|
||||
|
||||
@@ -226,6 +226,8 @@
|
||||
</li>
|
||||
<li><a href="semantic-ui/index.html">Using with Semantic-UI</a>
|
||||
</li>
|
||||
<li><a href="aria-describedby-cleanup.html">ariaDescribedByCleanup set to true</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Real-world examples</h3>
|
||||
<ul>
|
||||
|
||||
116
src/core.js
116
src/core.js
@@ -270,6 +270,7 @@ $.extend( $.validator, {
|
||||
errorElement: "label",
|
||||
focusCleanup: false,
|
||||
focusInvalid: true,
|
||||
ariaDescribedByCleanup: false,
|
||||
errorContainer: $( [] ),
|
||||
errorLabelContainer: $( [] ),
|
||||
onsubmit: true,
|
||||
@@ -481,6 +482,8 @@ $.extend( $.validator, {
|
||||
$.each( this.groups, function( name, testgroup ) {
|
||||
if ( testgroup === group && name !== checkElement.name ) {
|
||||
cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) );
|
||||
|
||||
// Don't want to check fields if a user hasn't gotten to them yet
|
||||
if ( cleanElement && cleanElement.name in v.invalid ) {
|
||||
v.currentElements.push( cleanElement );
|
||||
result = v.check( cleanElement ) && result;
|
||||
@@ -591,10 +594,76 @@ $.extend( $.validator, {
|
||||
hideErrors: function() {
|
||||
this.hideThese( this.toHide );
|
||||
},
|
||||
addErrorAriaDescribedBy: function( element, error, updateGroupMembers ) {
|
||||
updateGroupMembers = ( updateGroupMembers === undefined ) ? false : updateGroupMembers;
|
||||
|
||||
var errorID, v, group,
|
||||
describedBy = $( element ).attr( "aria-describedby" );
|
||||
errorID = error.attr( "id" );
|
||||
|
||||
// Respect existing non-error aria-describedby
|
||||
if ( !describedBy ) {
|
||||
describedBy = errorID;
|
||||
} else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) {
|
||||
|
||||
// Add to end of list if not already present
|
||||
describedBy += " " + errorID;
|
||||
}
|
||||
|
||||
$( element ).attr( "aria-describedby", describedBy );
|
||||
|
||||
if ( updateGroupMembers ) {
|
||||
|
||||
// If this element is grouped, then assign to all elements in the same group
|
||||
group = this.groups[ element.name ];
|
||||
if ( group ) {
|
||||
v = this;
|
||||
$.each( v.groups, function( name, testgroup ) {
|
||||
if ( testgroup === group ) {
|
||||
v.addErrorAriaDescribedBy( $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm ), error, false );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeErrorAriaDescribedBy: function( element, error ) {
|
||||
|
||||
var describedBy = $( element ).attr( "aria-describedby" ),
|
||||
describedByIds = describedBy.split( " " ),
|
||||
errorID = error.attr( "id" ),
|
||||
ind = describedByIds.indexOf( errorID );
|
||||
|
||||
if ( ind > -1 ) {
|
||||
describedByIds.splice( ind, 1 );
|
||||
}
|
||||
|
||||
if ( describedByIds.length ) {
|
||||
$( element ).attr( "aria-describedby", describedByIds.join( " " ) );
|
||||
} else {
|
||||
$( element ).removeAttr( "aria-describedby" );
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
hideThese: function( errors ) {
|
||||
errors.not( this.containers ).text( "" );
|
||||
this.addWrapper( errors ).hide();
|
||||
|
||||
for ( var i = 0; errors[ i ]; i++ ) {
|
||||
var error = $( errors[ i ] ),
|
||||
errorID = error.attr( "id" ) ? this.escapeCssMeta( error.attr( "id" ) ) : undefined,
|
||||
element = ( errorID ) ? this.elements().filter( '[aria-describedby~="' + errorID + '"]' ) : [];
|
||||
|
||||
if ( this.settings.ariaDescribedByCleanup && element.length ) {
|
||||
this.removeErrorAriaDescribedBy( element, error );
|
||||
}
|
||||
|
||||
if ( !error.is( this.containers ) ) {
|
||||
error.text( "" );
|
||||
}
|
||||
|
||||
this.addWrapper( error ).hide();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
valid: function() {
|
||||
@@ -900,6 +969,7 @@ $.extend( $.validator, {
|
||||
|
||||
defaultShowErrors: function() {
|
||||
var i, elements, error;
|
||||
|
||||
for ( i = 0; this.errorList[ i ]; i++ ) {
|
||||
error = this.errorList[ i ];
|
||||
if ( this.settings.highlight ) {
|
||||
@@ -907,19 +977,23 @@ $.extend( $.validator, {
|
||||
}
|
||||
this.showLabel( error.element, error.message );
|
||||
}
|
||||
|
||||
if ( this.errorList.length ) {
|
||||
this.toShow = this.toShow.add( this.containers );
|
||||
}
|
||||
|
||||
if ( this.settings.success ) {
|
||||
for ( i = 0; this.successList[ i ]; i++ ) {
|
||||
this.showLabel( this.successList[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.settings.unhighlight ) {
|
||||
for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) {
|
||||
this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass );
|
||||
}
|
||||
}
|
||||
|
||||
this.toHide = this.toHide.not( this.toShow );
|
||||
this.hideErrors();
|
||||
this.addWrapper( this.toShow ).show();
|
||||
@@ -936,13 +1010,18 @@ $.extend( $.validator, {
|
||||
},
|
||||
|
||||
showLabel: function( element, message ) {
|
||||
var place, group, errorID, v,
|
||||
var place,
|
||||
error = this.errorsFor( element ),
|
||||
elementID = this.idOrName( element ),
|
||||
describedBy = $( element ).attr( "aria-describedby" );
|
||||
|
||||
if ( error.length ) {
|
||||
|
||||
// Non-label error exists but is not currently associated with element via aria-describedby
|
||||
if ( error.closest( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 && ( describedBy === undefined || describedBy.split( " " ).indexOf( error.attr( "id" ) ) === -1 ) ) {
|
||||
this.addErrorAriaDescribedBy( element, error, true );
|
||||
}
|
||||
|
||||
// Refresh error/success class
|
||||
error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass );
|
||||
|
||||
@@ -987,32 +1066,10 @@ $.extend( $.validator, {
|
||||
// If the error is a label, then associate using 'for'
|
||||
error.attr( "for", elementID );
|
||||
|
||||
// If the element is not a child of an associated label, then it's necessary
|
||||
// to explicitly apply aria-describedby
|
||||
// If the element is not a child of an associated label, then it's necessary
|
||||
// to explicitly apply aria-describedby
|
||||
} else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) {
|
||||
errorID = error.attr( "id" );
|
||||
|
||||
// Respect existing non-error aria-describedby
|
||||
if ( !describedBy ) {
|
||||
describedBy = errorID;
|
||||
} else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) {
|
||||
|
||||
// Add to end of list if not already present
|
||||
describedBy += " " + errorID;
|
||||
}
|
||||
$( element ).attr( "aria-describedby", describedBy );
|
||||
|
||||
// If this element is grouped, then assign to all elements in the same group
|
||||
group = this.groups[ element.name ];
|
||||
if ( group ) {
|
||||
v = this;
|
||||
$.each( v.groups, function( name, testgroup ) {
|
||||
if ( testgroup === group ) {
|
||||
$( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm )
|
||||
.attr( "aria-describedby", error.attr( "id" ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
this.addErrorAriaDescribedBy( element, error, true );
|
||||
}
|
||||
}
|
||||
if ( !message && this.settings.success ) {
|
||||
@@ -1037,6 +1094,9 @@ $.extend( $.validator, {
|
||||
.replace( /\s+/g, ", #" );
|
||||
}
|
||||
|
||||
// There may be hidden error elements not currently associated via aria-describedby (if ariaDescribedByCleanup is true)
|
||||
selector = selector + ", #" + name + "-error";
|
||||
|
||||
return this
|
||||
.errors()
|
||||
.filter( selector );
|
||||
|
||||
@@ -347,7 +347,155 @@ QUnit.test( "test existing non-error aria-describedby", function( assert ) {
|
||||
assert.strictEqual( $( "#testForm17text-description" ).text(), "This is where you enter your data" );
|
||||
assert.strictEqual( $( "#testForm17text-error" ).text(), "", "Error label is empty for valid field" );
|
||||
} );
|
||||
QUnit.test( "test aria-describedby cleanup with existing non-error aria-describedby", function( assert ) {
|
||||
assert.expect( 13 );
|
||||
|
||||
var form = $( "#ariaDescribedByCleanupWithExistingNonError" ),
|
||||
field = $( "#testCleanupExistingNonErrortext" ),
|
||||
errorID = "testCleanupExistingNonErrortext-error",
|
||||
descriptionID = "testCleanupExistingNonErrortext-description";
|
||||
|
||||
assert.equal( field.attr( "aria-describedby" ), descriptionID );
|
||||
|
||||
// First test an invalid value
|
||||
form.validate( { errorElement: "span", ariaDescribedByCleanup: true } );
|
||||
assert.ok( !field.valid() );
|
||||
assert.equal( ( field.attr( "aria-describedby" ).split( " " ).indexOf( errorID ) > -1 && field.attr( "aria-describedby" ).split( " " ).indexOf( descriptionID ) > -1 ), true );
|
||||
assert.hasError( field, "required" );
|
||||
var errorElement = form.validate().errorsFor( field[ 0 ] );
|
||||
assert.equal( errorElement.attr( "id" ), errorID );
|
||||
|
||||
// Then make it valid again to ensure that the aria-describedby relationship is restored
|
||||
field.val( "foo" );
|
||||
assert.ok( field.valid() );
|
||||
assert.noErrorFor( field );
|
||||
assert.equal( field.attr( "aria-describedby" ), descriptionID );
|
||||
assert.strictEqual( true, errorElement.is( ":hidden" ) );
|
||||
|
||||
// Then make it invalid again
|
||||
field.val( "" ).trigger( "keyup" );
|
||||
assert.ok( !field.valid() );
|
||||
assert.hasError( field, "required" );
|
||||
|
||||
// Make sure there's not more than one error
|
||||
assert.equal( $( "[id=" + errorID + "]" ).length, 1 );
|
||||
assert.equal( ( field.attr( "aria-describedby" ).split( " " ).indexOf( errorID ) > -1 && field.attr( "aria-describedby" ).split( " " ).indexOf( descriptionID ) > -1 ), true );
|
||||
} );
|
||||
QUnit.test( "test aria-describedby cleanup when field becomes valid", function( assert ) {
|
||||
assert.expect( 16 );
|
||||
var form = $( "#ariaDescribedByCleanup" ),
|
||||
field = $( "#ariaDescribedByCleanupText" ),
|
||||
errorID = "ariaDescribedByCleanupText-error";
|
||||
|
||||
// First test an invalid value
|
||||
form.validate( { errorElement: "span", ariaDescribedByCleanup: true } );
|
||||
assert.ok( !field.valid() );
|
||||
assert.equal( field.attr( "aria-describedby" ), "ariaDescribedByCleanupText-error" );
|
||||
assert.hasError( field, "required" );
|
||||
var errorElement = form.validate().errorsFor( field[ 0 ] );
|
||||
assert.equal( field.attr( "aria-describedby" ), errorID );
|
||||
assert.equal( errorElement.attr( "id" ), errorID );
|
||||
|
||||
// Then make it valid again to ensure that the aria-describedby relationship is restored
|
||||
field.val( "foo" );
|
||||
|
||||
assert.ok( field.valid() );
|
||||
assert.noErrorFor( field );
|
||||
assert.notOk( field.attr( "aria-describedby" ) );
|
||||
assert.strictEqual( true, errorElement.is( ":hidden" ) );
|
||||
|
||||
// Then make it invalid again
|
||||
field.val( "" ).trigger( "keyup" );
|
||||
assert.ok( !field.valid() );
|
||||
assert.equal( field.attr( "aria-describedby" ), "ariaDescribedByCleanupText-error" );
|
||||
assert.hasError( field, "required" );
|
||||
errorElement = form.validate().errorsFor( field[ 0 ] );
|
||||
assert.ok( errorElement );
|
||||
|
||||
// Make sure there's not more than one error
|
||||
assert.equal( $( "[id=" + errorID + "]" ).length, 1 );
|
||||
assert.ok( field.attr( "aria-describedby" ) );
|
||||
assert.equal( field.attr( "aria-describedby" ), errorID );
|
||||
} );
|
||||
QUnit.test( "test aria-describedby cleanup on group", function( assert ) {
|
||||
assert.expect( 34 );
|
||||
var form = $( "#ariaDescribedByCleanupGroup" ),
|
||||
firstID = "ariaDescribedByCleanupGroupFirst",
|
||||
first = $( "#" + firstID ),
|
||||
middleID = "ariaDescribedByCleanupGroupMiddle",
|
||||
middle = $( "#" + middleID ),
|
||||
lastID = "ariaDescribedByCleanupGroupLast",
|
||||
last = $( "#" + lastID ),
|
||||
emailID = "ariaDescribedByCleanupGroupEmail",
|
||||
email = $( "#" + emailID ),
|
||||
groupName = "ariaDescribedByCleanupGroupName",
|
||||
groupOptions = { };
|
||||
groupOptions[ groupName ] = firstID + " " + middleID + " " + lastID;
|
||||
|
||||
// First test an invalid value
|
||||
form.validate( { errorElement: "span", ariaDescribedByCleanup: true, groups: groupOptions } );
|
||||
form.trigger( "submit" );
|
||||
assert.equal( first.attr( "aria-describedby" ), groupName + "-error" );
|
||||
assert.hasError( first, "required" );
|
||||
assert.hasError( middle, "required" );
|
||||
assert.hasError( last, "required" );
|
||||
var errorElement = form.validate().errorsFor( first[ 0 ] );
|
||||
|
||||
// Previous behavior was for error to apply to all group members. It still does that, but now it removes aria-describedby from each individual field (or at least I think that's what's happening because when first name has an error, middle and last are valid and don't have aria-describedby)
|
||||
assert.equal( errorElement.attr( "id" ), groupName + "-error" );
|
||||
assert.equal( middle.attr( "aria-describedby" ), groupName + "-error" );
|
||||
assert.equal( last.attr( "aria-describedby" ), groupName + "-error" );
|
||||
|
||||
// Check email field
|
||||
assert.hasError( email, "required" );
|
||||
assert.equal( email.attr( "aria-describedby" ), emailID + "-error" );
|
||||
|
||||
// Then make it valid again to ensure that the aria-describedby relationship is restored
|
||||
first.val( "Person" );
|
||||
middle.val( "Syntax" );
|
||||
last.val( "Personname" );
|
||||
|
||||
email.val( "aa" );
|
||||
|
||||
form.trigger( "submit" );
|
||||
|
||||
assert.hasError( email, "email" );
|
||||
assert.equal( email.attr( "aria-describedby" ), emailID + "-error" );
|
||||
var emailError = form.validate().errorsFor( email[ 0 ] );
|
||||
assert.equal( emailError.attr( "id" ), email.attr( "aria-describedby" ) );
|
||||
|
||||
assert.ok( first.valid() );
|
||||
assert.noErrorFor( first );
|
||||
assert.notOk( first.attr( "aria-describedby" ) );
|
||||
assert.noErrorFor( middle );
|
||||
assert.notOk( middle.attr( "aria-describedby" ) );
|
||||
assert.noErrorFor( last );
|
||||
assert.notOk( last.attr( "aria-describedby" ) );
|
||||
assert.strictEqual( true, errorElement.is( ":hidden" ) );
|
||||
|
||||
// Then make it invalid again
|
||||
first.val( "" ).trigger( "keyup" );
|
||||
assert.hasError( first, "required" );
|
||||
assert.equal( first.attr( "aria-describedby" ), groupName + "-error" );
|
||||
assert.equal( errorElement.attr( "id" ), groupName + "-error" );
|
||||
assert.equal( middle.attr( "aria-describedby" ), groupName + "-error" );
|
||||
assert.equal( last.attr( "aria-describedby" ), groupName + "-error" );
|
||||
assert.ok( !first.valid() );
|
||||
|
||||
// Make sure there's not more than one error
|
||||
assert.equal( $( "[id=" + groupName + "-error]" ).length, 1 );
|
||||
assert.equal( $( "[id=" + emailID + "-error]" ).length, 1 );
|
||||
|
||||
email.val( "test@test.com" ).trigger( "keyup" );
|
||||
first.val( "Person" ).trigger( "keyup" );
|
||||
|
||||
assert.noErrorFor( first );
|
||||
assert.noErrorFor( email );
|
||||
assert.notOk( email.attr( "aria-describedby" ) );
|
||||
assert.notOk( first.attr( "aria-describedby" ) );
|
||||
assert.notOk( middle.attr( "aria-describedby" ) );
|
||||
assert.notOk( last.attr( "aria-describedby" ) );
|
||||
} );
|
||||
QUnit.test( "test pre-assigned non-error aria-describedby", function( assert ) {
|
||||
assert.expect( 7 );
|
||||
var form = $( "#testForm17" ),
|
||||
|
||||
@@ -188,6 +188,34 @@
|
||||
<input name="testForm17text" id="testForm17text" data-rule-required="true" data-msg="required" aria-describedby="testForm17text-description">
|
||||
<span id="testForm17text-description">This is where you enter your data</span>
|
||||
</form>
|
||||
<form id="ariaDescribedByCleanupWithExistingNonError">
|
||||
<!-- test existing non-error aria-describedby with aria-describedby cleanup on valid fields -->
|
||||
<label for="testCleanupExistingNonErrortext">My Label</label>
|
||||
<input name="testCleanupExistingNonErrortext" id="testCleanupExistingNonErrortext" data-rule-required="true" data-msg="required" aria-describedby="testCleanupExistingNonErrortext-description">
|
||||
<span id="testCleanupExistingNonErrortext-description">This is where you enter your data</span>
|
||||
</form>
|
||||
<form id="ariaDescribedByCleanup">
|
||||
<!-- Test aria-describedby cleanup on valid fields -->
|
||||
<label for="ariaDescribedByCleanupText">My label</label>
|
||||
<input name="ariaDescribedByCleanupText" id="ariaDescribedByCleanupText" data-rule-required="true" data-msg="required"/>
|
||||
</form>
|
||||
<form id="ariaDescribedByCleanupGroup">
|
||||
<!-- Test aria-describedby cleanup on valid fields in a group -->
|
||||
<fieldset>
|
||||
<legend>
|
||||
Name
|
||||
</legend>
|
||||
<label for="ariaDescribedByCleanupGroupFirst">First</label>
|
||||
<input name="ariaDescribedByCleanupGroupFirst" id="ariaDescribedByCleanupGroupFirst" data-rule-required="true" data-msg="required"/>
|
||||
<label for="ariaDescribedByCleanupGroupMiddle">Middle</label>
|
||||
<input name="ariaDescribedByCleanupGroupMiddle" id="ariaDescribedByCleanupGroupMiddle" data-rule-required="true" data-msg="required"/>
|
||||
<label for="ariaDescribedByCleanupGroupLast">Last</label>
|
||||
<input name="ariaDescribedByCleanupGroupLast" id="ariaDescribedByCleanupGroupLast" data-rule-required="true" data-msg="required"/>
|
||||
</fieldset>
|
||||
<label for="ariaDescribedByCleanupGroupEmail">Email</label>
|
||||
<input name="ariaDescribedByCleanupGroupEmail" id="ariaDescribedByCleanupGroupEmail" type="email" data-rule-email="true" data-rule-required="true"/>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
<form id="testForm18">
|
||||
<!-- test id/name containing brackets -->
|
||||
<input name="testForm18[text]" id="testForm18[text]" required>
|
||||
@@ -454,12 +482,12 @@
|
||||
<input name="year"/>
|
||||
<button name="submitForm27" value="someValue" type="submit"><span>Submit</span></button>
|
||||
</form>
|
||||
|
||||
|
||||
<form id="cnhFormTest">
|
||||
<input id="cnhnumber" name="cnhnumber" required>
|
||||
<button name="submitFormCnh" value="submitFormCnh" type="submit"><span>Submit</span></button>
|
||||
</form>
|
||||
|
||||
|
||||
<form id="_contenteditableForm">
|
||||
<div name="first_name" id="first_name" contenteditable placeholder="First Name"></div>
|
||||
<br>
|
||||
|
||||
Reference in New Issue
Block a user