Today we do the following:
- break up tests into meaningful chunks
- extract simple functions that can be used in multiple places
- and move functions into a separate inputStatus module
Updating the tests
In the last post we updating the code. Now it’s time for the tests to be revisited and improved.
Use describe blocks to break up the tests into meaningful chunks
Breaking up the tests into meaningful chunks makes it easier to understand what they are doing.
login.test.js
describe("When change-password form is reset, update input messages", function () {
//...
describe("email error", function () {
//...
it("Resets the error message", function () {
//...
});
it("Resets the error color", function () {
//...
});
it("Removes the error warning class", function () {
//...
});
it("Adds the error ok class", function () {
//...
});
});
describe("email feedback", function () {
//...
it("Removes the feedback glyphicon class", function () {
//...
});
it("Removes the feedback glyphicon-ok class", function () {
//...
});
it("Removes the feedback ok class", function () {
//...
});
it("Removes the feedback glyphicon-remove class", function () {
//...
});
});
});
Use a single form-group element as the container in which to find other needed parts
Having a single point of entry helps to make it easier to understand where the information is coming from.
const $termsFormgroup = $("#terms").closest(".form-group");
//...
const $termsFeedback = $termsFormgroup.find(".feedback");
//...
const $termcheck = $termsFormgroup.find("#termcheck");
//...
const $termsRequired = $termsFormgroup.find("#termsRequired");
With those last couple, I could have just done $("#termcheck)
and gained access just as easily, but that doesn’t tell me anything about where it is on the page. With this updated way I now know that all of them are somewhere inside of the terms form-group.
We can now move on to bringing functions together into an inputStatus module.
inputStatus module
There are three sections that are being updated in most of the code we’ve already looked at here. They are:
Starting with error
The error section has its text updated, and is set to either warning or ok with a suitable red or green colour.
The login code uses both types, so that’s a good place to put together separate functions for them.
function errorOk(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "green");
$error.removeClass("warning");
$error.addClass("ok");
}
function removeError(inputGroup, message) {
// $(inputGroup).find(".error").html(message);
// $(inputGroup).find(".error").css("color", "green");
// $(inputGroup).find(".error").removeClass("warning").addClass("ok");
errorOk(inputGroup, message);
$(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-remove").addClass("glyphicon glyphicon-ok ok");
}
We can also have an errorWarning function that we use in the addError function.
function errorWarning(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "red");
$error.removeClass("ok");
$error.addClass("warning");
}
function addError(inputGroup, message) {
// $(inputGroup).find(".error").html(message);
// $(inputGroup).find(".error").css("color", "red");
// $(inputGroup).find(".error").removeClass("ok").addClass("warning");
errorWarning(inputGroup, message);
$(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-ok ok").addClass("glyphicon glyphicon-remove warning");
}
Create inputStatus module
The errorOk and errorWarning functions can now be moved out to a separate set of code, that I’ll call inputStatus. I intend that the inputStatus module of code is responsible for handling everything that gets done in regard to the input status, which includes error, feedback, and required sections.
input-status.js
const inputStatus = (function () {
function errorOk(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "green");
$error.removeClass("warning");
$error.addClass("ok");
}
function errorWarning(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "red");
$error.removeClass("ok");
$error.addClass("warning");
}
return {
errorOk,
errorWarning
};
}());
We can add that input-status.js file to the index.html file:
<script src="./setup.js"></script>
<script src="./input-status.js"></script>
<script src="./validate.js"></script>
and we can now use it from the other code.
Using inputStatus in login.js code
The errorOk and errorWarning references in the login code can now be replaced with ones to inputStatus instead.
function removeError(inputGroup, message) {
// errorOk(inputGroup, message);
inputStatus.errorOk(inputGroup, message);
$(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-remove").addClass("glyphicon glyphicon-ok ok");
}
function addError(inputGroup, message) {
// errorWarning(inputGroup, message);
inputStatus.errorWarning(inputGroup, message);
$(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-ok ok").addClass("glyphicon glyphicon-remove warning");
}
which lets us remove the errorOk and errorWarning functions from the login.js file.
More importantly, we can use inputStatus.errorOk and inputStatus.errorWarning from other scripting files too.
Using inputStatus in the change-password code
The change-password code is easy to update, so that the same inputStatus.errorOk function is used
function passwordResetHandler() {
$(".inputboxmodal2").each(function resetInputMessages() {
var inputName = $(this).find(".input-check").attr("name");
// $(this).find(".error").html("Your " + inputName);
// $(this).find(".error").css("color", "green");
// $(this).find(".error").removeClass("warning").addClass("ok");
inputStatus.errorOk(this, "Your " + inputName);
$(this).find(".feedback").removeClass("glyphicon glyphicon-remove glyphicon-ok ok");
});
}
Using inputStatus in the validate.js code
The validate code is also easy to update so that inputStatus.errorOk is used.
function resetMessages() {
const $error = $(this).find(".error");
const name = $(this).find(".check").attr("name");
// $error.html(name);
// resetWarning($error);
inputStatus.errorOk(this, name);
resetFeedback($(this).find(".feedback"));
resetWarning($(this).find(".starrq"));
}
Add feedback code to inputStatus
We can now also move the feedback code into the inputStatus code module.
//...
function feedbackOk(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-remove");
$feedback.addClass("glyphicon glyphicon-ok ok");
}
function feedbackWarning(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-ok ok");
$feedback.addClass("glyphicon glyphicon-remove");
}
return {
errorOk,
errorWarning,
feedbackOk,
feedbackWarning
};
}());
Use feedbackOk and feedbackWarning in the login code
Here is the login code with using the updated inputStatus feedback code:
function removeError(inputGroup, message) {
inputStatus.errorOk(inputGroup, message);
// $(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-remove").addClass("glyphicon glyphicon-ok ok");
inputStatus.feedbackOk(inputGroup);
}
function addError(inputGroup, message) {
inputStatus.errorWarning(inputGroup, message);
// $(inputGroup).find(".feedback").removeClass("glyphicon glyphicon-ok ok").addClass("glyphicon glyphicon-remove warning");
inputStatus.feedbackWarning(inputGroup);
}
Use feedbackNone in the change-password code
With the change-password and validate code though, we are removing all of the feedback entirely, so we could do with a separate feedbackNone function in the inputStatus code:
function feedbackNone(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-remove");
$feedback.removeClass("glyphicon glyphicon-ok ok");
}
//...
return {
//...
feedbackNone,
//...
};
which we can use in the change-password code:
$("#changepw .form-group").each(function resetInputMessages() {
var inputName = $(this).find(".input-check").attr("name");
inputStatus.errorOk(this, "Your " + inputName);
// $(this).find(".feedback").removeClass("glyphicon glyphicon-remove glyphicon-ok ok");
inputStatus.feedbackNone(this);
});
Use feedbackNone in the validate code
When we use feedbackNone in the validate code, there is a slight issue:
function resetMessages() {
const $error = $(this).find(".error");
const name = $(this).find(".check").attr("name");
inputStatus.errorOk(this, name);
// resetFeedback($(this).find(".feedback"));
inputStatus.feedbackNone(this);
resetWarning($(this).find(".starrq"));
}
A test fails, saying that warning
is expected to be removed, but it wasn’t.
One of the feedback examples of code that we used to create feedbackNone was incomplete. Here is an update, ensuring that the warning class is removed too.
function feedbackNone(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
// $feedback.removeClass("glyphicon glyphicon-remove");
$feedback.removeClass("glyphicon glyphicon-remove warning");
$feedback.removeClass("glyphicon glyphicon-ok ok");
}
(this is the wrong approach, which we soon learn and use a different technique with)
We also need to add the removal of warning to the other feedback functions too.
function feedbackOk(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
// $feedback.removeClass("glyphicon glyphicon-remove");
$feedback.removeClass("glyphicon glyphicon-remove warning");
$feedback.addClass("glyphicon glyphicon-ok ok");
}
function feedbackWarning(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-ok ok");
$feedback.addClass("glyphicon glyphicon-remove warning");
}
When it comes to the feedback functions in the inputStatus module, I can’t help but think that there’s a better way. We needed to make changes to three sets of code in three different places, and that’s concerning. There should be a better way to do that without needing quite so many changes.
Improving the feedback none/ok/warning functions
From the feedbackOk and feedbackWarning functions, instead of removing classes I can call feedbackNone instead to remove all of the relevant classes, and then add only what is needed.
function feedbackNone(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-remove warning");
$feedback.removeClass("glyphicon glyphicon-ok ok");
}
function feedbackOk(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-ok ok");
}
function feedbackWarning(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-remove warning");
}
The benefit of doing things that way is that there is exactly one add and one remove for each type of thing. Each addClass has a matching removeClass. That code is a lot better now.
Improve the terms code
The last set of code to improve is the terms and conditions in the validate code:
function removeTermWarning() {
const $termsGroup = $("#terms").closest(".form-group");
resetFeedback($termsGroup.find(".feedback"));
resetWarning($("#termcheck"));
resetWarning($("#termsRequired"));
}
Instead of using resetFeedback, we can use inputStatus.feedbackNone
function removeTermWarning() {
const $termsGroup = $("#terms").closest(".form-group");
inputStatus.feedbackNone($termsGroup);
resetWarning($("#termcheck"));
resetWarning($("#termsRequired"));
}
That lets us delete the old resetFeedback function.
The resetWarning function would be handy to have in the inputStatus function. The errorOk and errorWarning functions can certainly use them, as setOk and setWarning functions.
Add setOk and setWarning to the inputStatus module
We can add setOk to go with the errorOk function
function setOk($el) {
$el.removeClass("warning");
$el.addClass("ok");
}
function errorOk(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "green");
// $error.removeClass("warning");
// $error.addClass("ok");
setOk($error);
}
And, we can do similar to add setWarning to go with the errorOk function:
function errorWarning(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "red");
// $error.removeClass("ok");
// $error.addClass("warning");
setWarning($error);
}
We can also use those setOk and setWarning functions with the feedback code, but we’ll also need a setNone function to keep feedbackNone happy.
function setNone($el) {
$el.removeClass("warning");
$el.removeClass("ok");
}
//...
function feedbackNone(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-remove");
$feedback.removeClass("glyphicon glyphicon-ok");
setNone($feedback);
}
function feedbackOk(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-ok");
setOk($feedback);
}
function feedbackWarning(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-remove");
setWarning($feedback);
}
We can now update the term code to use setOk as well.
function removeTermWarning() {
const $termsGroup = $("#terms").closest(".form-group");
inputStatus.feedbackNone($termsGroup);
// resetWarning($termsGroup.find("#termcheck"));
inputStatus.setOk($termsGroup.find("#termcheck"));
// resetWarning($termsGroup.find("#termsRequired"));
inputStatus.setOk($termsGroup.find("#termsRequired"));
}
Add .starrq for required to the inputStatus
With the aim to delete the resetWarning function, there’s only one thing more left to update, and that’s the starrq required part of the code.
In the validate code we can tell it to use the
function resetMessages() {
const $error = $(this).find(".error");
const name = $(this).find(".check").attr("name");
inputStatus.errorOk(this, name);
inputStatus.feedbackNone(this);
// resetWarning($(this).find(".starrq"));
inputStatus.setWarning($(this).find(".starrq"));
}
and we can now finally remove the removeWarning and replaceClass functions from the validate code.
Summary
The bulk of the code is now being done by the inputStatus function. Here’s what the inputStatus.js code looks like:
const inputStatus = (function () {
function setNone($el) {
$el.removeClass("warning");
$el.removeClass("ok");
}
function setOk($el) {
$el.removeClass("warning");
$el.addClass("ok");
}
function setWarning($el) {
$el.removeClass("ok");
$el.addClass("warning");
}
function errorOk(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "green");
setOk($error);
}
function errorWarning(inputGroup, message) {
const $error = $(inputGroup).find(".error");
$error.html(message);
$error.css("color", "red");
setWarning($error);
}
function feedbackNone(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
$feedback.removeClass("glyphicon glyphicon-remove");
$feedback.removeClass("glyphicon glyphicon-ok");
setNone($feedback);
}
function feedbackOk(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-ok");
setOk($feedback);
}
function feedbackWarning(inputGroup, message) {
const $feedback = $(inputGroup).find(".feedback");
feedbackNone(inputGroup);
$feedback.addClass("glyphicon glyphicon-remove");
setWarning($feedback);
}
return {
setNone,
setOk,
setWarning,
errorOk,
errorWarning,
feedbackNone,
feedbackOk,
feedbackWarning
};
}());
That causes even the most complex code to be rather simple. Here for example is the validate reset code:
function resetMessages() {
const $error = $(this).find(".error");
const name = $(this).find(".check").attr("name");
inputStatus.errorOk(this, name);
inputStatus.feedbackNone(this);
inputStatus.setOk($(this).find(".starrq"));
}
function removeTermWarning() {
const $termsGroup = $("#terms").closest(".form-group");
inputStatus.feedbackNone($termsGroup);
inputStatus.setOk($termsGroup.find("#termcheck"));
inputStatus.setOk($termsGroup.find("#termsRequired"));
}
function registrationResetHandler(evt) {
$(".form-group").each(resetMessages);
removeTermWarning();
}
$("#registration").on("reset", registrationResetHandler);
We should be able to make good use of the inputStatus functions as we work through the rest of the code.
The latest code is found at v0.0.6 in releases
As a reminder, we have only randomly looked at three different sections of code so far, drawn to them by jsInspect looking for cases of duplication. Those three sections being:
- validate reset
- login submit
- change password reset
There are a whole lot of other sections to the code, but ideally we will be able to group together similar behaviour into external libraries like inputStatus.