patternjavascriptMinor
Click event function that checks to see if form field is valid
Viewed 0 times
fieldclickchecksfunctionseethatvalidformevent
Problem
What I'm building is a URL builder. A marketing person inputs a URL and can test that URL to see if it's valid, then they select a demand channel and then hit 'Generate Campaign URL'. I've omitted the other form fields, as I'm only interesting in refactoring the code for the onclick event of the 'Test' URL button.
In an effort to become a better developer, I'd like to start writing unit tests and adopting a more TDD approach to my code. I've been working off the example in Writing Testable JavaScript in an effort to refactor my jQuery onclick event into something that can be tested using a framework like Mocha, Jasmine, etc.
` $.validator.setDefaults({
highlight: function (element) {
$(element).closest('.form-group').addClass('has-error');
},
unhighlight: function (element) {
$(element).closest('.form-group').removeClass('has-error');
},
errorElement: 'span',
errorClass: 'help-block',
errorPlacement: function (error, element) {
if (element.parent('.input-group').length) {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
}
});
$('#form1').validate({
rules: {
baseURL: {
required: true,
url: true
},
demandChannel: {
required: true
},
userEmail: {
required: true,
email: true
}
}
});
$('#testBaseURL').on('click', function () {
var $baseURL = $('#baseURL');
var inputIsValid = $baseURL.valid();
var url = $baseURL.val();
if (inputIsValid) {
window.open(url, '_blank');
}
});
var TestButton = function (el) {
this.el = $(el);
this._bindEvents();
};
TestButton.prototype._handleClick = function (evt) {
var bas
In an effort to become a better developer, I'd like to start writing unit tests and adopting a more TDD approach to my code. I've been working off the example in Writing Testable JavaScript in an effort to refactor my jQuery onclick event into something that can be tested using a framework like Mocha, Jasmine, etc.
` $.validator.setDefaults({
highlight: function (element) {
$(element).closest('.form-group').addClass('has-error');
},
unhighlight: function (element) {
$(element).closest('.form-group').removeClass('has-error');
},
errorElement: 'span',
errorClass: 'help-block',
errorPlacement: function (error, element) {
if (element.parent('.input-group').length) {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
}
});
$('#form1').validate({
rules: {
baseURL: {
required: true,
url: true
},
demandChannel: {
required: true
},
userEmail: {
required: true,
email: true
}
}
});
$('#testBaseURL').on('click', function () {
var $baseURL = $('#baseURL');
var inputIsValid = $baseURL.valid();
var url = $baseURL.val();
if (inputIsValid) {
window.open(url, '_blank');
}
});
var TestButton = function (el) {
this.el = $(el);
this._bindEvents();
};
TestButton.prototype._handleClick = function (evt) {
var bas
Solution
Creating layers of indirection just for a click event with two lines of code seems a bit off and I can't see in any way how that would make it easier to test.
What you may want is to use a higher level of abstraction and which wraps the functionality of your form:
Some key points here:
So to test this in Mocha we could do:
What you may want is to use a higher level of abstraction and which wraps the functionality of your form:
var UrlBuilder = function(el, options) {
this.el = el;
this.options = _.defaults(options || {}, {
rules: {
baseURL: {
required: true,
url: true
},
demandChannel: {
required: true
},
userEmail: {
required: true,
email: true
}
}
});
this.valid = function(){
return this.el.valid();
};
this.el.validate(options.rules);
this.el.on('click', '.test-button', this.test.bind(this));
}
Also we can refactor the test method to have usable output:
UrlBuilder.prototype.test = function(){
if (! this.el.valid()) {
return false;
} else {
this.popup(url);
return this.el.val();
}
};
// The reason is that the browser will not allow you to override anything in the window host object.
UrlBuilder.prototype.popup = function(url){
window.open(url, '_blank');
};Some key points here:
- We can can create
UrlBuilderfrom any fragment.
- We scope from the element when binding handlers.
- We can test with a valid / invalid object just by stubbing the valid method.
- We return meaningful values from
.test
So to test this in Mocha we could do:
describe('UrlBuilder', function(){
before(function(){
// @todo load $fixture which has the HTML content needed.
this.builder = new UrlBuilder($fixture);
});
describe("validation", function{
// ...
});
describe("test", function(){
context("when invalid", function(){
before(function(){
sinon.stub(this.builder, 'valid').returns(false);
});
it("returns false", function(){
expect(this.builder.test()).to.be.falsy;
});
});
context("when invalid", function(){
var spy;
before(function(){
sinon.stub(this.builder, 'valid').returns(true);
spy = sinon.stub(this.builder, 'popup');
});
it("returns the correct url", function(){
expect(this.builder.test()).to.eq("http://example.com?foo=baz...");
});
it("opens the test url when the test button is clicked", function(){
this.builder.el.find('.test-button').click();
expect(spy).calledWith("http://example.com?foo=baz...")
});
});
});
});Code Snippets
var UrlBuilder = function(el, options) {
this.el = el;
this.options = _.defaults(options || {}, {
rules: {
baseURL: {
required: true,
url: true
},
demandChannel: {
required: true
},
userEmail: {
required: true,
email: true
}
}
});
this.valid = function(){
return this.el.valid();
};
this.el.validate(options.rules);
this.el.on('click', '.test-button', this.test.bind(this));
}
Also we can refactor the test method to have usable output:
UrlBuilder.prototype.test = function(){
if (! this.el.valid()) {
return false;
} else {
this.popup(url);
return this.el.val();
}
};
// The reason is that the browser will not allow you to override anything in the window host object.
UrlBuilder.prototype.popup = function(url){
window.open(url, '_blank');
};describe('UrlBuilder', function(){
before(function(){
// @todo load $fixture which has the HTML content needed.
this.builder = new UrlBuilder($fixture);
});
describe("validation", function{
// ...
});
describe("test", function(){
context("when invalid", function(){
before(function(){
sinon.stub(this.builder, 'valid').returns(false);
});
it("returns false", function(){
expect(this.builder.test()).to.be.falsy;
});
});
context("when invalid", function(){
var spy;
before(function(){
sinon.stub(this.builder, 'valid').returns(true);
spy = sinon.stub(this.builder, 'popup');
});
it("returns the correct url", function(){
expect(this.builder.test()).to.eq("http://example.com?foo=baz...");
});
it("opens the test url when the test button is clicked", function(){
this.builder.el.find('.test-button').click();
expect(spy).calledWith("http://example.com?foo=baz...")
});
});
});
});Context
StackExchange Code Review Q#84042, answer score: 2
Revisions (0)
No revisions yet.