Skip to content

Commit

Permalink
Merge pull request #423 from DFE-Digital/fix/date-question-validation
Browse files Browse the repository at this point in the history
Rework of date validation errors - now able to show multiple errors at the same time
  • Loading branch information
DanielClarkeEducation authored Oct 16, 2024
2 parents 89c5f80 + dbb526b commit ba42dfd
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task<IActionResult> WhenWasTheQualificationStarted(DateQuestionMode
{
var questionPage = await contentService.GetDateQuestionPage(QuestionPages.WhenWasTheQualificationStarted);
var dateModelValidationResult = questionModelValidator.IsValid(model, questionPage!);
if (!dateModelValidationResult.IsValid)
if (!dateModelValidationResult.MonthValid || !dateModelValidationResult.YearValid)
{
// ReSharper disable once InvertIf
if (questionPage is not null)
Expand Down Expand Up @@ -291,10 +291,18 @@ private static List<IOptionItemModel> MapOptionItems(List<IOptionItem> questionO
private async Task<DateQuestionModel> MapDateModel(DateQuestionModel model, DateQuestionPage question,
string actionName,
string controllerName,
ValidationResult? validationResult,
DateValidationResult? validationResult,
int? selectedMonth,
int? selectedYear)
{
var bannerErrorText = validationResult is { BannerErrorMessages.Count: > 0 }
? string.Join("<br />", validationResult!.BannerErrorMessages)
: null;

var errorMessageText = validationResult is { ErrorMessages.Count: > 0 }
? string.Join("<br />", validationResult!.ErrorMessages)
: null;

model.Question = question.Question;
model.CtaButtonText = question.CtaButtonText;
model.ActionName = actionName;
Expand All @@ -305,11 +313,13 @@ private async Task<DateQuestionModel> MapDateModel(DateQuestionModel model, Date
model.BackButton = MapToNavigationLinkModel(question.BackButton);
model.ErrorBannerHeading = question.ErrorBannerHeading;
model.ErrorBannerLinkText =
placeholderUpdater.Replace(validationResult?.BannerErrorMessage ?? question.ErrorBannerLinkText);
model.ErrorMessage = placeholderUpdater.Replace(validationResult?.ErrorMessage ?? question.ErrorMessage);
placeholderUpdater.Replace(bannerErrorText ?? question.ErrorBannerLinkText);
model.ErrorMessage = placeholderUpdater.Replace(errorMessageText ?? question.ErrorMessage);
model.AdditionalInformationHeader = question.AdditionalInformationHeader;
model.AdditionalInformationBody = await contentParser.ToHtml(question.AdditionalInformationBody);

model.MonthError = !validationResult?.MonthValid ?? false;
model.YearError = !validationResult?.YearValid ?? false;

// ReSharper disable once InvertIf
if (selectedMonth.HasValue && selectedYear.HasValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,92 +5,65 @@ namespace Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels.Validato

public class DateQuestionModelValidator(IDateTimeAdapter dateTimeAdapter) : IDateQuestionModelValidator
{
public ValidationResult IsValid(DateQuestionModel model, DateQuestionPage questionPage)
public DateValidationResult IsValid(DateQuestionModel model, DateQuestionPage questionPage)
{
model.MonthError = false;
model.YearError = false;
var resultToReturn = new DateValidationResult();

if (model is { SelectedYear: null, SelectedMonth: null })
{
model.MonthError = true;
model.YearError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.ErrorMessage,
BannerErrorMessage = questionPage.ErrorBannerLinkText
};
resultToReturn.MonthValid = false;
resultToReturn.YearValid = false;
resultToReturn.ErrorMessages.Add(questionPage.ErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.ErrorBannerLinkText);

return resultToReturn;
}

if (model.SelectedMonth == null)
{
model.MonthError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.MissingMonthErrorMessage,
BannerErrorMessage = questionPage.MissingMonthBannerLinkText
};
resultToReturn.MonthValid = false;
resultToReturn.ErrorMessages.Add(questionPage.MissingMonthErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.MissingMonthBannerLinkText);
}

if (model.SelectedYear == null)
if (model.SelectedMonth is <= 0 or > 12)
{
model.YearError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.MissingYearErrorMessage,
BannerErrorMessage = questionPage.MissingYearBannerLinkText
};
resultToReturn.MonthValid = false;
resultToReturn.ErrorMessages.Add(questionPage.MonthOutOfBoundsErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.MonthOutOfBoundsErrorLinkText);
}

if (model.SelectedMonth is <= 0 or > 12)
if (model.SelectedYear == null)
{
model.MonthError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.MonthOutOfBoundsErrorMessage,
BannerErrorMessage = questionPage.MonthOutOfBoundsErrorLinkText
};
resultToReturn.YearValid = false;
resultToReturn.ErrorMessages.Add(questionPage.MissingYearErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.MissingYearBannerLinkText);
}

var now = dateTimeAdapter.Now();

if (model.SelectedYear < 1900 || model.SelectedYear > now.Year)
{
model.YearError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.YearOutOfBoundsErrorMessage,
BannerErrorMessage = questionPage.YearOutOfBoundsErrorLinkText
};
resultToReturn.YearValid = false;
resultToReturn.ErrorMessages.Add(questionPage.YearOutOfBoundsErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.YearOutOfBoundsErrorLinkText);
}

if (resultToReturn.ErrorMessages.Count != 0)
{
return resultToReturn;
}

var selectedDate = new DateOnly(model.SelectedYear!.Value, model.SelectedMonth!.Value, 1);

if (selectedDate > DateOnly.FromDateTime(now))
{
model.MonthError = true;
model.YearError = true;

return new ValidationResult
{
IsValid = false,
ErrorMessage = questionPage.FutureDateErrorMessage,
BannerErrorMessage = questionPage.FutureDateErrorBannerLinkText
};
resultToReturn.MonthValid = false;
resultToReturn.YearValid = false;
resultToReturn.ErrorMessages.Add(questionPage.FutureDateErrorMessage);
resultToReturn.BannerErrorMessages.Add(questionPage.FutureDateErrorBannerLinkText);
}

return new ValidationResult
{
IsValid = true
};

return resultToReturn;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels.Validators;

public class DateValidationResult
{
public bool MonthValid { get; set; } = true;

public bool YearValid { get; set; } = true;

public List<string> BannerErrorMessages { get; set; } = [];

public List<string> ErrorMessages { get; set; } = [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace Dfe.EarlyYearsQualification.Web.Models.Content.QuestionModels.Validato

public interface IDateQuestionModelValidator
{
ValidationResult IsValid(DateQuestionModel model, DateQuestionPage questionPage);
DateValidationResult IsValid(DateQuestionModel model, DateQuestionPage questionPage);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{
ErrorBannerHeading = Model.ErrorBannerHeading,
ErrorBannerLinkText = Model.ErrorBannerLinkText,
ElementLinkId = "date-started-month"
ElementLinkId = Model.MonthError ? "date-started-month" : "date-started-year"
});
}
@using (Html.BeginForm(Model.ActionName, Model.ControllerName, FormMethod.Post, new { id = "date-question-form"}))
Expand All @@ -45,7 +45,7 @@
@if (Model.MonthError || Model.YearError)
{
<p id="date-error" class="govuk-error-message">
<span class="govuk-visually-hidden">Error:</span> @Model.ErrorMessage
<span class="govuk-visually-hidden">Error:</span> @Html.Raw(Model.ErrorMessage)
</p>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="govuk-error-summary__body">
<ul class="govuk-list govuk-error-summary__list">
<li>
<a id="error-banner-link" href="#@{@Model.ElementLinkId}">@Model.ErrorBannerLinkText</a>
<a id="error-banner-link" href="#@{@Model.ElementLinkId}">@Html.Raw(Model.ErrorBannerLinkText)</a>
</li>
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe("A spec that tests question pages", () => {
cy.get("#date-error").should("not.exist");
cy.get(".govuk-form-group").should("not.have.class", "govuk-form-group--error");

cy.get('#date-started-year').type(24);
cy.get('#date-started-year').type(2024);

cy.get('button[id="question-submit"]').click();
cy.location().should((loc) => {
Expand Down Expand Up @@ -162,7 +162,7 @@ describe("A spec that tests question pages", () => {
cy.get(".govuk-form-group").should("not.have.class", "govuk-form-group--error");

cy.get('#date-started-month').type(value);
cy.get('#date-started-year').type(24);
cy.get('#date-started-year').type(2024);

cy.get('button[id="question-submit"]').click();
cy.location().should((loc) => {
Expand Down Expand Up @@ -219,6 +219,85 @@ describe("A spec that tests question pages", () => {
})
})

it("shows the month out of bound error message and the year out of bounds error message when a user types an invalid month and year on the when-was-the-qualification-started page", () => {
cy.visit("/questions/when-was-the-qualification-started");

cy.get(".govuk-error-summary").should("not.exist");
cy.get("#date-error").should("not.exist");
cy.get(".govuk-form-group").should("not.have.class", "govuk-form-group--error");

cy.get('#date-started-month').type(0);
cy.get('#date-started-year').type(20);

cy.get('button[id="question-submit"]').click();
cy.location().should((loc) => {
expect(loc.pathname).to.eq("/questions/when-was-the-qualification-started");
})

cy.get(".govuk-error-summary").should("be.visible");
cy.get(".govuk-error-summary__title").should("contain.text", "There is a problem");

cy.get("#error-banner-link").should("contain.text", "Month Out Of Bounds Error Link TextYear Out Of Bounds Error Link Text");

cy.get('#date-error').should("exist");
cy.get('#date-error').should("contain.text", "Month Out Of Bounds Error MessageYear Out Of Bounds Error Message");
cy.get(".govuk-form-group").should("have.class", "govuk-form-group--error");
cy.get("#date-started-month").should("have.class", "govuk-input--error");
cy.get("#date-started-year").should("have.class", "govuk-input--error");
})

it("shows the month out of bound error message and the year missing error message when a user types an invalid month and doesnt type a year on the when-was-the-qualification-started page", () => {
cy.visit("/questions/when-was-the-qualification-started");

cy.get(".govuk-error-summary").should("not.exist");
cy.get("#date-error").should("not.exist");
cy.get(".govuk-form-group").should("not.have.class", "govuk-form-group--error");

cy.get('#date-started-month').type(0);

cy.get('button[id="question-submit"]').click();
cy.location().should((loc) => {
expect(loc.pathname).to.eq("/questions/when-was-the-qualification-started");
})

cy.get(".govuk-error-summary").should("be.visible");
cy.get(".govuk-error-summary__title").should("contain.text", "There is a problem");

cy.get("#error-banner-link").should("contain.text", "Month Out Of Bounds Error Link TextMissing Year Banner Link Text");

cy.get('#date-error').should("exist");
cy.get('#date-error').should("contain.text", "Month Out Of Bounds Error MessageMissing Year Error Message");
cy.get(".govuk-form-group").should("have.class", "govuk-form-group--error");
cy.get("#date-started-month").should("have.class", "govuk-input--error");
cy.get("#date-started-year").should("have.class", "govuk-input--error");
})

it("shows the month missing error message and the year out of bounds error message when a user doesnt type a year and types an invalid month on the when-was-the-qualification-started page", () => {
cy.visit("/questions/when-was-the-qualification-started");

cy.get(".govuk-error-summary").should("not.exist");
cy.get("#date-error").should("not.exist");
cy.get(".govuk-form-group").should("not.have.class", "govuk-form-group--error");

cy.get('#date-started-year').type(20);

cy.get('button[id="question-submit"]').click();
cy.location().should((loc) => {
expect(loc.pathname).to.eq("/questions/when-was-the-qualification-started");
})

cy.get(".govuk-error-summary").should("be.visible");
cy.get(".govuk-error-summary__title").should("contain.text", "There is a problem");

cy.get("#error-banner-link").should("contain.text", "Missing Month Banner Link TextYear Out Of Bounds Error Link Text");

cy.get('#date-error').should("exist");
cy.get('#date-error').should("contain.text", "Missing Month Error MessageYear Out Of Bounds Error Message");
cy.get(".govuk-form-group").should("have.class", "govuk-form-group--error");
cy.get("#date-started-month").should("have.class", "govuk-input--error");
cy.get("#date-started-year").should("have.class", "govuk-input--error");
})

/// What level is the qualification page
it("Checks the content on what-level-is-the-qualification page", () => {
cy.setCookie('user_journey', '%7B%22WhenWasQualificationStarted%22%3A%227%2F2015%22%7D');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ public async Task Post_WhenWasTheQualificationStarted_InvalidModel_ReturnsDateQu
mockQuestionModelValidator.Object, mockPlaceholderUpdater.Object);

mockQuestionModelValidator.Setup(x => x.IsValid(It.IsAny<DateQuestionModel>(), It.IsAny<DateQuestionPage>()))
.Returns(new ValidationResult
{ IsValid = false, ErrorMessage = "Test error message" });
.Returns(new DateValidationResult
{ MonthValid = false, YearValid = false, ErrorMessages = ["Test error message"] });

mockPlaceholderUpdater.Setup(x => x.Replace(It.IsAny<string>())).Returns<string>(x => x);

Expand Down Expand Up @@ -465,7 +465,7 @@ public async Task Post_WhenWasTheQualificationStarted_ValidModel_ReturnsRedirect

mockQuestionModelValidator
.Setup(x => x.IsValid(It.IsAny<DateQuestionModel>(), It.IsAny<DateQuestionPage>()))
.Returns(new ValidationResult { IsValid = true });
.Returns(new DateValidationResult { MonthValid = true, YearValid = true, });

var controller = new QuestionsController(mockLogger.Object, mockContentService.Object, mockContentParser.Object,
mockUserJourneyCookieService.Object, mockRepository.Object,
Expand Down
Loading

0 comments on commit ba42dfd

Please sign in to comment.