Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #13920: Fixed erroneous validation for specific cases #19909

Merged
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.50 under development
------------------------

- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher)
- Bug #19925: Improved PHP version check when handling MIME types (schmunk42)
- Bug #19940: File Log writer without newline (terabytesoftw)
- Bug #19951: Removed unneeded MIME file tests (schmunk42)
Expand Down
8 changes: 8 additions & 0 deletions framework/assets/yii.activeForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,11 @@
data: $form.serialize() + extData,
dataType: data.settings.ajaxDataType,
complete: function (jqXHR, textStatus) {
currentAjaxRequest = null;
$form.trigger(events.ajaxComplete, [jqXHR, textStatus]);
},
beforeSend: function (jqXHR, settings) {
currentAjaxRequest = jqXHR;
$form.trigger(events.ajaxBeforeSend, [jqXHR, settings]);
},
success: function (msgs) {
Expand Down Expand Up @@ -563,6 +565,9 @@
return;
}

if (currentAjaxRequest !== null) {
currentAjaxRequest.abort();
}
if (data.settings.timer !== undefined) {
clearTimeout(data.settings.timer);
}
Expand Down Expand Up @@ -929,4 +934,7 @@
$form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false');
}
}

var currentAjaxRequest = null;

})(window.jQuery);
12 changes: 12 additions & 0 deletions tests/js/data/yii.activeForm.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,15 @@
<div class="help-block"></div>
</div>
</form>
<form id="w3">
<div class="form-group field-test-text2 required">
<label class="control-label" for="test-text2">Test text</label>
<input type="text" id="test-text2" class="form-control" name="Test[text2]" aria-required="true">
<div class="help-block"></div>
</div>
<div class="form-group field-test-text3 required">
<label class="control-label" for="test-text3">Test text</label>
<input type="text" id="test-text3" class="form-control" name="Test[text3]" aria-required="true">
<div class="help-block"></div>
</div>
</form>
69 changes: 69 additions & 0 deletions tests/js/tests/yii.activeForm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ describe('yii.activeForm', function () {
var script = new vm.Script(code);
var context = new vm.createContext({window: window, document: window.document, yii: yii});
script.runInContext(context);
/** This is a workaround for a jsdom issue, that prevents :hidden and :visible from working as expected.
* @see https://github.com/jsdom/jsdom/issues/1048 */
context.window.Element.prototype.getClientRects = function () {
var node = this;
while(node) {
if(node === document) {
break;
}
if (!node.style || node.style.display === 'none' || node.style.visibility === 'hidden') {
return [];
}
node = node.parentNode;
}
return [{width: 100, height: 100}];
};
}

var activeFormHtml = fs.readFileSync('tests/js/data/yii.activeForm.html', 'utf-8');
Expand Down Expand Up @@ -117,6 +132,60 @@ describe('yii.activeForm', function () {
assert.isFalse($activeForm.data('yiiActiveForm').validated);
});
});

describe('with ajax validation', function () {
describe('with rapid validation of multiple fields', function () {
it('should cancel overlapping ajax requests and not display outdated validation results', function () {
$activeForm = $('#w3');
$activeForm.yiiActiveForm([{
id: 'test-text2',
input: '#test-text2',
container: '.field-test-text2',
enableAjaxValidation: true
}, {
id: 'test-text3',
input: '#test-text3',
container: '.field-test-text3',
enableAjaxValidation: true
}], {
validationUrl: ''
});

let requests = [];
function fakeAjax(object) {
const request = {
jqXHR: {
abort: function () {
request.aborted = true;
}
},
aborted: false,
respond: function (response) {
if (this.aborted) {
return;
}
object.success(response);
object.complete(this.jqXHR, '');
}
};
requests.push(request);
object.beforeSend(request.jqXHR, '');
}

const ajaxStub = sinon.stub($, 'ajax', fakeAjax);
$activeForm.yiiActiveForm('validateAttribute', 'test-text2');
assert.isTrue(requests.length === 1);
$activeForm.yiiActiveForm('validateAttribute', 'test-text3');
// When validateAttribute was called on text2, its value was valid.
// The value of text3 wasn't.
requests[0].respond({'test-text3': ['Field cannot be empty']});
// When validateAttribute was called on text3, its value was valid.
requests[1].respond([]);
assert.isTrue($activeForm.find('.field-test-text3').hasClass('has-success'));
ajaxStub.restore();
});
});
})
});

describe('resetForm method', function () {
Expand Down
Loading