From 6364c78886973ae59b1010cda658c21452dc749b Mon Sep 17 00:00:00 2001 From: Pete Robinson <77154273+peterdrobinson@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:14:34 +0100 Subject: [PATCH 1/2] NGR-1875 - Added Repairs and Fitted Out page --- .../RepairsAndFittingOutController.scala | 80 ++++++++++++ .../forms/RepairsAndFittingOutForm.scala | 47 +++++++ .../navigation/Navigator.scala | 1 + .../pages/RepairsAndFittingOutPage.scala | 27 ++++ .../views/RepairsAndFittingOutView.scala.html | 48 +++++++ conf/app.routes | 9 +- conf/messages | 7 +- .../ConfirmBreakClauseControllerSpec.scala | 8 ++ .../RepairsAndFittingOutControllerSpec.scala | 123 ++++++++++++++++++ .../forms/RepairAndFittingOutFormSpec.scala | 81 ++++++++++++ .../views/RepairAndFittingOutViewSpec.scala | 94 +++++++++++++ 11 files changed, 522 insertions(+), 3 deletions(-) create mode 100644 app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala create mode 100644 app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala create mode 100644 app/uk/gov/hmrc/ngrraldfrontend/pages/RepairsAndFittingOutPage.scala create mode 100644 app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html create mode 100644 test/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutControllerSpec.scala create mode 100644 test/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairAndFittingOutFormSpec.scala create mode 100644 test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala diff --git a/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala b/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala new file mode 100644 index 0000000..eb5de5f --- /dev/null +++ b/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.controllers + +import play.api.i18n.I18nSupport +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} +import uk.gov.hmrc.ngrraldfrontend.actions.{AuthRetrievals, DataRetrievalAction} +import uk.gov.hmrc.ngrraldfrontend.config.AppConfig +import uk.gov.hmrc.ngrraldfrontend.models.components.NGRRadio.{buildRadios, simpleNgrRadio} +import uk.gov.hmrc.ngrraldfrontend.models.forms.RepairsAndFittingOutForm +import uk.gov.hmrc.ngrraldfrontend.models.forms.RepairsAndFittingOutForm.form +import uk.gov.hmrc.ngrraldfrontend.models.{Mode, UserAnswers} +import uk.gov.hmrc.ngrraldfrontend.navigation.Navigator +import uk.gov.hmrc.ngrraldfrontend.pages.RepairsAndFittingOutPage +import uk.gov.hmrc.ngrraldfrontend.repo.SessionRepository +import uk.gov.hmrc.ngrraldfrontend.views.html.RepairsAndFittingOutView +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} + +class RepairsAndFittingOutController @Inject()(repairsAndFittingView: RepairsAndFittingOutView, + authenticate: AuthRetrievals, + getData: DataRetrievalAction, + sessionRepository: SessionRepository, + navigator: Navigator, + mcc: MessagesControllerComponents)(implicit appConfig: AppConfig, ec: ExecutionContext) + extends FrontendController(mcc) with I18nSupport { + + + def show(mode: Mode): Action[AnyContent] = { + (authenticate andThen getData).async { implicit request => + val preparedForm = request.userAnswers.getOrElse(UserAnswers(request.credId)).get(RepairsAndFittingOutPage) match { + case None => form + case Some(value) => form.fill(RepairsAndFittingOutForm(value.toString)) + } + Future.successful(Ok(repairsAndFittingView( + selectedPropertyAddress = request.property.addressFull, + form = preparedForm, + ngrRadio = buildRadios(preparedForm, simpleNgrRadio(RepairsAndFittingOutForm.radio)), + mode = mode + ))) + } + } + + def submit(mode: Mode): Action[AnyContent] = + (authenticate andThen getData).async { implicit request => + form.bindFromRequest().fold( + formWithErrors => { + Future.successful(BadRequest(repairsAndFittingView( + form = formWithErrors, + ngrRadio = buildRadios(formWithErrors, simpleNgrRadio(RepairsAndFittingOutForm.radio)), + selectedPropertyAddress = request.property.addressFull, + mode = mode + ))) + }, + radioValue => + for { + updatedAnswers <- Future.fromTry(request.userAnswers.getOrElse(UserAnswers(request.credId)) + .set(RepairsAndFittingOutPage, radioValue.radioValue.toBoolean)) + _ <- sessionRepository.set(updatedAnswers) + } yield Redirect(navigator.nextPage(RepairsAndFittingOutPage, mode, updatedAnswers)) + + ) + } +} diff --git a/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala b/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala new file mode 100644 index 0000000..866d955 --- /dev/null +++ b/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.models.forms + +import play.api.data.Form +import play.api.data.Forms.mapping +import play.api.i18n.* +import play.api.libs.json.{Json, OFormat} +import uk.gov.hmrc.ngrraldfrontend.models.forms.mappings.Mappings + +final case class RepairsAndFittingOutForm(radioValue: String) + +object RepairsAndFittingOutForm extends CommonFormValidators with Mappings { + implicit val format: OFormat[RepairsAndFittingOutForm] = Json.format[RepairsAndFittingOutForm] + + private lazy val radioUnselectedError = "repairsAndFittingOut.empty.error" + val radio = "repairsAndFittingOut-radio-value" + + val messagesApi: MessagesApi = new DefaultMessagesApi() + val lang: Lang = Lang.defaultLang + val messages: Messages = MessagesImpl(lang, messagesApi) + + def unapply(repairsAndFittingOutForm: RepairsAndFittingOutForm): Option[(String)] = + Some(repairsAndFittingOutForm.radioValue) + + def form: Form[RepairsAndFittingOutForm] = { + Form( + mapping( + radio -> radioText(radioUnselectedError), + )(RepairsAndFittingOutForm.apply)(RepairsAndFittingOutForm.unapply) + ) + } +} diff --git a/app/uk/gov/hmrc/ngrraldfrontend/navigation/Navigator.scala b/app/uk/gov/hmrc/ngrraldfrontend/navigation/Navigator.scala index 1ca2124..995ca63 100644 --- a/app/uk/gov/hmrc/ngrraldfrontend/navigation/Navigator.scala +++ b/app/uk/gov/hmrc/ngrraldfrontend/navigation/Navigator.scala @@ -156,6 +156,7 @@ class Navigator @Inject()() { case ConfirmBreakClausePage => _ => uk.gov.hmrc.ngrraldfrontend.controllers.routes.LandlordController.show(NormalMode) //TODO This needs to be amended when the journey is completed //TODO Next page not made yet case RentReviewPage => _ => uk.gov.hmrc.ngrraldfrontend.controllers.routes.CheckRentFreePeriodController.show(NormalMode) + case RepairsAndFittingOutPage => _ => uk.gov.hmrc.ngrraldfrontend.controllers.routes.LandlordController.show(NormalMode) //TODO This needs to be amended when the journey is completed } //TODO change to check your answers page diff --git a/app/uk/gov/hmrc/ngrraldfrontend/pages/RepairsAndFittingOutPage.scala b/app/uk/gov/hmrc/ngrraldfrontend/pages/RepairsAndFittingOutPage.scala new file mode 100644 index 0000000..429be91 --- /dev/null +++ b/app/uk/gov/hmrc/ngrraldfrontend/pages/RepairsAndFittingOutPage.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.pages + +import play.api.libs.json.JsPath + +case object RepairsAndFittingOutPage extends QuestionPage[Boolean] { + + override def toString: String = "repairsAndFittingOutPage" + + override def path: JsPath = JsPath \ toString + +} diff --git a/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html b/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html new file mode 100644 index 0000000..da584ea --- /dev/null +++ b/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html @@ -0,0 +1,48 @@ +@* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *@ + +@import uk.gov.hmrc.ngrraldfrontend.config.AppConfig + +@import uk.gov.hmrc.govukfrontend.views.html.components._ +@import uk.gov.hmrc.govukfrontend.views.Aliases._ +@import uk.gov.hmrc.ngrraldfrontend.views.html.components._ +@import uk.gov.hmrc.ngrraldfrontend.viewmodels.govuk.all._ +@import uk.gov.hmrc.ngrraldfrontend.config.AppConfig +@import uk.gov.hmrc.ngrraldfrontend.models.components.NavigationBarContent +@import uk.gov.hmrc.ngrraldfrontend.models.forms.RepairsAndFittingOutForm + +@this( + layout: Layout, + formHelper: FormWithCSRF, + govukErrorSummary: GovukErrorSummary, + govukRadios : GovukRadios, + saveAndContinueButton: saveAndContinueButton +) + +@(selectedPropertyAddress: String, form: Form[RepairsAndFittingOutForm], ngrRadio: Radios, mode: Mode)(implicit request: RequestHeader, messages: Messages, appConfig: AppConfig) + +@layout(pageTitle = Some(messages("repairsAndFittingOut.title")), showBackLink = true, fullWidth = false) { + @formHelper(action = uk.gov.hmrc.ngrraldfrontend.controllers.routes.RepairsAndFittingOutController.submit(mode), Symbol("autoComplete") -> "off") { + @if(form.errors.nonEmpty) { + @govukErrorSummary(ErrorSummaryViewModel(form)) + } + @selectedPropertyAddress +
@messages("repairsAndFittingOut.p1")
+ @govukRadios(ngrRadio) + @saveAndContinueButton(msg = messages("service.continue"), isStartButton = false) + } +} \ No newline at end of file diff --git a/conf/app.routes b/conf/app.routes index fc7de14..5d1a680 100644 --- a/conf/app.routes +++ b/conf/app.routes @@ -118,7 +118,6 @@ POST /does-rent-include-parking-spaces-or-garages uk.gov.hmrc.n GET /does-rent-include-parking-spaces-or-garages/change uk.gov.hmrc.ngrraldfrontend.controllers.DoesYourRentIncludeParkingController.show(mode: Mode = CheckMode) POST /does-rent-include-parking-spaces-or-garages/change uk.gov.hmrc.ngrraldfrontend.controllers.DoesYourRentIncludeParkingController.submit(mode: Mode = CheckMode) - #How many parking spaces or garages included in rent GET /how-many-parking-spaces-or-garages-included-in-rent uk.gov.hmrc.ngrraldfrontend.controllers.HowManyParkingSpacesOrGaragesIncludedInRentController.show(mode: Mode = NormalMode) POST /how-many-parking-spaces-or-garages-included-in-rent uk.gov.hmrc.ngrraldfrontend.controllers.HowManyParkingSpacesOrGaragesIncludedInRentController.submit(mode: Mode = NormalMode) @@ -153,4 +152,10 @@ POST /repairs-and-insurance/change uk.gov.hmrc.n GET /rent-review uk.gov.hmrc.ngrraldfrontend.controllers.RentReviewController.show(mode: Mode = NormalMode) POST /rent-review uk.gov.hmrc.ngrraldfrontend.controllers.RentReviewController.submit(mode: Mode = NormalMode) GET /rent-review/change uk.gov.hmrc.ngrraldfrontend.controllers.RentReviewController.show(mode: Mode = CheckMode) -POST /rent-review/change uk.gov.hmrc.ngrraldfrontend.controllers.RentReviewController.submit(mode: Mode = CheckMode) \ No newline at end of file +POST /rent-review/change uk.gov.hmrc.ngrraldfrontend.controllers.RentReviewController.submit(mode: Mode = CheckMode) + +#Rent review +GET /repairs-and-fitting-out uk.gov.hmrc.ngrraldfrontend.controllers.RepairsAndFittingOutController.show(mode: Mode = NormalMode) +POST /repairs-and-fitting-out uk.gov.hmrc.ngrraldfrontend.controllers.RepairsAndFittingOutController.submit(mode: Mode = NormalMode) +GET /repairs-and-fitting-out/change uk.gov.hmrc.ngrraldfrontend.controllers.RepairsAndFittingOutController.show(mode: Mode = CheckMode) +POST /repairs-and-fitting-out/change uk.gov.hmrc.ngrraldfrontend.controllers.RepairsAndFittingOutController.submit(mode: Mode = CheckMode) \ No newline at end of file diff --git a/conf/messages b/conf/messages index 4c53dba..4934219 100644 --- a/conf/messages +++ b/conf/messages @@ -408,4 +408,9 @@ rentReview.rentReviewMonthsYears.months.invalid.error = The number of months for rentReview.rentReviewMonthsYears.months.maximum.12.error = The number of months for how often your rent is reviewed must be 12 or less rentReview.rentReviewMonthsYears.months.maximum.11.error = The number of months for how often your rent is reviewed must be 11 or less rentReview.rentReviewMonthsYears.years.invalid.error = The number of years for how often your rent is reviewed must be a number, like 2 -rentReview.rentReviewMonthsYears.years.maximum.1000.error = How often your rent is reviewed must be 1,000 years or less \ No newline at end of file +rentReview.rentReviewMonthsYears.years.maximum.1000.error = How often your rent is reviewed must be 1,000 years or less + +#Repairs and fitting out +repairsAndFittingOut.title = Repairs and fitting out +repairsAndFittingOut.p1 = Repairs are when you fix things like poor electrical wiring, leaking windows or a broken toilet. Fitting out is when you install things like air conditioning, a kitchen or carpeting. +repairsAndFittingOut.empty.error = Select yes if you have done any repairs of fitting out in the property \ No newline at end of file diff --git a/test/uk/gov/hmrc/ngrraldfrontend/controllers/ConfirmBreakClauseControllerSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/controllers/ConfirmBreakClauseControllerSpec.scala index a017e1e..50be1a8 100644 --- a/test/uk/gov/hmrc/ngrraldfrontend/controllers/ConfirmBreakClauseControllerSpec.scala +++ b/test/uk/gov/hmrc/ngrraldfrontend/controllers/ConfirmBreakClauseControllerSpec.scala @@ -49,6 +49,14 @@ class ConfirmBreakClauseControllerSpec extends ControllerSpecSupport { val content = contentAsString(result) content must include(pageTitle) } + "return OK and the correct view with prepopulated data" in { + val result = controllerProperty(confirmBreakClauseAnswers).show(NormalMode)(authenticatedFakeRequest) + status(result) mustBe OK + val content = contentAsString(result) + val document = Jsoup.parse(content) + document.select("input[type=radio][name=confirmBreakClause-radio-value][value=true]").hasAttr("checked") mustBe true + document.select("input[type=radio][name=confirmBreakClause-radio-value][value=false]").hasAttr("checked") mustBe false + } "Return NotFoundException when property is not found in the mongo" in { when(mockNGRConnector.getLinkedProperty(any[CredId])(any())).thenReturn(Future.successful(None)) val exception = intercept[NotFoundException] { diff --git a/test/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutControllerSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutControllerSpec.scala new file mode 100644 index 0000000..d67258f --- /dev/null +++ b/test/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutControllerSpec.scala @@ -0,0 +1,123 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.controllers + +import org.jsoup.Jsoup +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.when +import play.api.http.Status.{BAD_REQUEST, OK, SEE_OTHER} +import play.api.test.FakeRequest +import play.api.test.Helpers.{await, contentAsString, defaultAwaitTimeout, redirectLocation, status} +import uk.gov.hmrc.auth.core.Nino +import uk.gov.hmrc.http.{HeaderNames, NotFoundException} +import uk.gov.hmrc.ngrraldfrontend.helpers.ControllerSpecSupport +import uk.gov.hmrc.ngrraldfrontend.models.AgreementType.NewAgreement +import uk.gov.hmrc.ngrraldfrontend.models.registration.CredId +import uk.gov.hmrc.ngrraldfrontend.models.{AuthenticatedUserRequest, NormalMode, UserAnswers} +import uk.gov.hmrc.ngrraldfrontend.pages.{RepairsAndFittingOutPage, DoesYourRentIncludeParkingPage} +import uk.gov.hmrc.ngrraldfrontend.views.html.RepairsAndFittingOutView + +import scala.concurrent.Future + +class RepairsAndFittingOutControllerSpec extends ControllerSpecSupport { + val pageTitle = "Repairs and fitting out" + val view: RepairsAndFittingOutView = inject[RepairsAndFittingOutView] + val controllerNoProperty: RepairsAndFittingOutController = new RepairsAndFittingOutController(view, fakeAuth, fakeData(None), mockSessionRepository, mockNavigator, mcc)(mockConfig, ec) + val controllerProperty: Option[UserAnswers] => RepairsAndFittingOutController = answers => new RepairsAndFittingOutController(view, fakeAuth, fakeDataProperty(Some(property),answers), mockSessionRepository, mockNavigator, mcc)(mockConfig, ec) + val confirmBreakClauseAnswers: Option[UserAnswers] = UserAnswers("id").set(RepairsAndFittingOutPage, true).toOption + + + "RepairsAndFittingOutController" must { + "method show" must { + "Return OK and the correct view" in { + val result = controllerProperty(None).show(NormalMode)(authenticatedFakeRequest) + status(result) mustBe OK + val content = contentAsString(result) + content must include(pageTitle) + } + "return OK and the correct view with prepopulated data" in { + val result = controllerProperty(confirmBreakClauseAnswers).show(NormalMode)(authenticatedFakeRequest) + status(result) mustBe OK + val content = contentAsString(result) + val document = Jsoup.parse(content) + document.select("input[type=radio][name=repairsAndFittingOut-radio-value][value=true]").hasAttr("checked") mustBe true + document.select("input[type=radio][name=repairsAndFittingOut-radio-value][value=false]").hasAttr("checked") mustBe false + } + "Return NotFoundException when property is not found in the mongo" in { + when(mockNGRConnector.getLinkedProperty(any[CredId])(any())).thenReturn(Future.successful(None)) + val exception = intercept[NotFoundException] { + await(controllerNoProperty.show(NormalMode)(authenticatedFakeRequest)) + } + exception.getMessage contains "Could not find answers in backend mongo" mustBe true + } + } + + "method submit" must { + "Return See_Other and the correct view after submitting yes" in { + when(mockSessionRepository.set(any())).thenReturn(Future.successful(true)) + val result = controllerProperty(None).submit(NormalMode)(AuthenticatedUserRequest(FakeRequest(routes.ConfirmBreakClauseController.submit(NormalMode)) + .withFormUrlEncodedBody( + "repairsAndFittingOut-radio-value" -> "true", + ) + .withHeaders(HeaderNames.authorisation -> "Bearer 1"), None, None, None, Some(property), credId = Some(credId.value), None, None, nino = Nino(true, Some("")))) + result.map(result => { + result.header.headers.get("Location") mustBe Some("/ngr-rald-frontend/landlord") //TODO this is currently going to the wrong page as the journey hasn't yet been completed + }) + status(result) mustBe SEE_OTHER + redirectLocation(result) mustBe Some(routes.LandlordController.show(NormalMode).url) + } + "Return See_Other and the correct view after submitting no" in { + when(mockSessionRepository.set(any())).thenReturn(Future.successful(true)) + val result = controllerProperty(None).submit(NormalMode)(AuthenticatedUserRequest(FakeRequest(routes.ConfirmBreakClauseController.submit(NormalMode)) + .withFormUrlEncodedBody( + "repairsAndFittingOut-radio-value" -> "false", + ) + .withHeaders(HeaderNames.authorisation -> "Bearer 1"), None, None, None, Some(property), credId = Some(credId.value), None, None, nino = Nino(true, Some("")))) + result.map(result => { + result.header.headers.get("Location") mustBe Some("/ngr-rald-frontend/landlord") //TODO this is currently going to the wrong page as the journey hasn't yet been completed + }) + status(result) mustBe SEE_OTHER + redirectLocation(result) mustBe Some(routes.LandlordController.show(NormalMode).url) + } + "Return Form with Errors when no radio selection is input" in { + val result = controllerProperty(None).submit(NormalMode)(AuthenticatedUserRequest(FakeRequest(routes.ConfirmBreakClauseController.submit(NormalMode)) + .withFormUrlEncodedBody( + "repairsAndFittingOut-radio-value" -> "", + ) + .withHeaders(HeaderNames.authorisation -> "Bearer 1"), None, None, None, Some(property), credId = Some(credId.value), None, None, nino = Nino(true, Some("")))) + result.map(result => { + result.header.headers.get("Location") mustBe Some("/ngr-rald-frontend/landlord") //TODO this is currently going to the wrong page as the journey hasn't yet been completed + }) + status(result) mustBe BAD_REQUEST + val content = contentAsString(result) + content must include(pageTitle) + content must include("Select yes if you have done any repairs of fitting out in the property") + } + + "Return Exception if no address is in the mongo" in { + when(mockNGRConnector.getLinkedProperty(any[CredId])(any())).thenReturn(Future.successful(None)) + val exception = intercept[NotFoundException] { + await(controllerNoProperty.submit(NormalMode)(AuthenticatedUserRequest(FakeRequest(routes.LandlordController.submit(NormalMode)) + .withFormUrlEncodedBody(("what-type-of-agreement-radio", "")) + .withHeaders(HeaderNames.authorisation -> "Bearer 1"), None, None, None, Some(property), credId = Some(credId.value), None, None, nino = Nino(true, Some(""))))) + } + exception.getMessage contains "Could not find answers in backend mongo" mustBe true + } + } + } +} + diff --git a/test/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairAndFittingOutFormSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairAndFittingOutFormSpec.scala new file mode 100644 index 0000000..16b869f --- /dev/null +++ b/test/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairAndFittingOutFormSpec.scala @@ -0,0 +1,81 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.models.forms + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import play.api.data.FormError +import play.api.libs.json.Json + +class RepairAndFittingOutFormSpec extends AnyFlatSpec with Matchers { + + val validData = Map( + "repairsAndFittingOut-radio-value" -> "true" + ) + + "RepairAndFittingOutForm" should "bind valid data successfully" in { + val boundForm = RepairsAndFittingOutForm.form.bind(validData) + + boundForm.errors shouldBe empty + boundForm.value shouldBe Some(RepairsAndFittingOutForm("true")) + } + + it should "fail when repairsAndFittingOut (radio) is missing" in { + val data = validData - "repairsAndFittingOut-radio-value" + val boundForm = RepairsAndFittingOutForm.form.bind(data) + + boundForm.errors shouldBe List(FormError("repairsAndFittingOut-radio-value", List("repairsAndFittingOut.empty.error"), List())) + } + + it should "fail to bind when repairsAndFittingOut is empty" in { + val data = Map("repairsAndFittingOut-radio-value" -> "") + val boundForm = RepairsAndFittingOutForm.form.bind(data) + + boundForm.hasErrors shouldBe true + boundForm.errors should contain(FormError("repairsAndFittingOut-radio-value", List("repairsAndFittingOut.empty.error"))) + } + + it should "fail when no is selected" in { + val data = Map( + "repairsAndFittingOut-radio-value" -> "false", + ) + + val boundForm = RepairsAndFittingOutForm.form.bind(data) + + boundForm.errors shouldBe empty + boundForm.value shouldBe Some(RepairsAndFittingOutForm("false")) + } + + "DoesYourRentIncludeParkingForm.format" should "serialize to JSON correctly" in { + val form = RepairsAndFittingOutForm("true") + val json = Json.toJson(form) + + json shouldBe Json.obj( + "radioValue" -> "true", + ) + } + + it should "deserialize from JSON correctly" in { + val json = Json.obj( + "radioValue" -> "false", + ) + + val result = json.validate[RepairsAndFittingOutForm] + result.isSuccess shouldBe true + result.get shouldBe RepairsAndFittingOutForm("false") + } +} diff --git a/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala new file mode 100644 index 0000000..7336c3d --- /dev/null +++ b/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala @@ -0,0 +1,94 @@ +/* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.ngrraldfrontend.views + +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import uk.gov.hmrc.govukfrontend.views.viewmodels.radios.Radios +import uk.gov.hmrc.ngrraldfrontend.helpers.ViewBaseSpec +import uk.gov.hmrc.ngrraldfrontend.models.NormalMode +import uk.gov.hmrc.ngrraldfrontend.models.components.NGRRadio +import uk.gov.hmrc.ngrraldfrontend.models.components.NGRRadio.{buildRadios, simpleNgrRadio} +import uk.gov.hmrc.ngrraldfrontend.models.forms.RepairsAndFittingOutForm +import uk.gov.hmrc.ngrraldfrontend.views.html.RepairsAndFittingOutView + +class RepairAndFittingOutViewSpec extends ViewBaseSpec { + lazy val view: RepairsAndFittingOutView = inject[RepairsAndFittingOutView] + + object Strings { + val heading = "Repairs and fitting out" + val hint = "Repairs are when you fix things like poor electrical wiring, leaking windows or a broken toilet. Fitting out is when you install things like air conditioning, a kitchen or carpeting." + val radio1 = "Yes" + val radio2 = "No" + val continue = "Continue" + } + + object Selectors { + val heading = "#main-content > div > div.govuk-grid-column-two-thirds > form > h1" + val hint = "#main-content > div > div.govuk-grid-column-two-thirds > form > p" + val radio1 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > div > div:nth-child(1) > label" + val radio2 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > div > div:nth-child(2) > label" + val continue = "#continue" + } + + val address = "5 Brixham Marina, Berry Head Road, Brixham, Devon, TQ5 9BW" + private val ngrRadio: NGRRadio = simpleNgrRadio(RepairsAndFittingOutForm.radio) + val form = RepairsAndFittingOutForm.form.fillAndValidate(RepairsAndFittingOutForm("true")) + val radio: Radios = buildRadios(form, ngrRadio) + + "ConfirmBreakClauseView" must { + val repairsAndFittingOutView = view(address, form, radio, NormalMode) + lazy implicit val document: Document = Jsoup.parse(repairsAndFittingOutView.body) + val htmlApply = view.apply(address, form, radio, NormalMode).body + val htmlRender = view.render(address, form, radio, NormalMode, request, messages, mockConfig).body + lazy val htmlF = view.f(address, form, radio, NormalMode) + + "htmlF is not empty" in { + htmlF.toString() must not be empty + } + + "apply must be the same as render" in { + htmlApply mustBe htmlRender + } + + "render is not empty" in { + htmlRender must not be empty + } + + "show correct heading" in { + elementText(Selectors.heading) mustBe Strings.heading + } + + "show correct hint" in { + elementText(Selectors.hint) mustBe Strings.hint + } + + + "show correct radio 1" in { + elementText(Selectors.radio1) mustBe Strings.radio1 + } + + "show correct radio 2" in { + elementText(Selectors.radio2) mustBe Strings.radio2 + } + + "show correct continue button" in { + elementText(Selectors.continue) mustBe Strings.continue + } + } +} + From 639f80767bada557d9dd43fe12536465e4419df2 Mon Sep 17 00:00:00 2001 From: Pete Robinson <77154273+peterdrobinson@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:50:24 +0100 Subject: [PATCH 2/2] NGR-1875 - Amended as per Anna's comments --- .../RepairsAndFittingOutController.scala | 4 ++-- .../forms/RepairsAndFittingOutForm.scala | 14 +++++++++++ .../views/RepairsAndFittingOutView.scala.html | 18 +++++++++++++- conf/messages | 1 + .../views/RepairAndFittingOutViewSpec.scala | 24 ++++++++++++------- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala b/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala index eb5de5f..35d6b6d 100644 --- a/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala +++ b/app/uk/gov/hmrc/ngrraldfrontend/controllers/RepairsAndFittingOutController.scala @@ -51,7 +51,7 @@ class RepairsAndFittingOutController @Inject()(repairsAndFittingView: RepairsAn Future.successful(Ok(repairsAndFittingView( selectedPropertyAddress = request.property.addressFull, form = preparedForm, - ngrRadio = buildRadios(preparedForm, simpleNgrRadio(RepairsAndFittingOutForm.radio)), + ngrRadio = buildRadios(preparedForm, RepairsAndFittingOutForm.repairsAndFittingOutRadio), mode = mode ))) } @@ -63,7 +63,7 @@ class RepairsAndFittingOutController @Inject()(repairsAndFittingView: RepairsAn formWithErrors => { Future.successful(BadRequest(repairsAndFittingView( form = formWithErrors, - ngrRadio = buildRadios(formWithErrors, simpleNgrRadio(RepairsAndFittingOutForm.radio)), + ngrRadio = buildRadios(formWithErrors, RepairsAndFittingOutForm.repairsAndFittingOutRadio), selectedPropertyAddress = request.property.addressFull, mode = mode ))) diff --git a/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala b/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala index 866d955..4496f89 100644 --- a/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala +++ b/app/uk/gov/hmrc/ngrraldfrontend/models/forms/RepairsAndFittingOutForm.scala @@ -21,6 +21,8 @@ import play.api.data.Forms.mapping import play.api.i18n.* import play.api.libs.json.{Json, OFormat} import uk.gov.hmrc.ngrraldfrontend.models.forms.mappings.Mappings +import uk.gov.hmrc.ngrraldfrontend.models.components.NGRRadio +import uk.gov.hmrc.ngrraldfrontend.models.components.NGRRadio.{ngrRadio, noButton, yesButton} final case class RepairsAndFittingOutForm(radioValue: String) @@ -37,6 +39,16 @@ object RepairsAndFittingOutForm extends CommonFormValidators with Mappings { def unapply(repairsAndFittingOutForm: RepairsAndFittingOutForm): Option[(String)] = Some(repairsAndFittingOutForm.radioValue) + def repairsAndFittingOutRadio(implicit messages: Messages): NGRRadio = + ngrRadio( + radioName = radio, + radioButtons = Seq( + yesButton(), + noButton() + ), + ngrTitle = "repairsAndFittingOut.header" + ) + def form: Form[RepairsAndFittingOutForm] = { Form( mapping( @@ -44,4 +56,6 @@ object RepairsAndFittingOutForm extends CommonFormValidators with Mappings { )(RepairsAndFittingOutForm.apply)(RepairsAndFittingOutForm.unapply) ) } + + } diff --git a/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html b/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html index da584ea..e9b732c 100644 --- a/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html +++ b/app/uk/gov/hmrc/ngrraldfrontend/views/RepairsAndFittingOutView.scala.html @@ -14,6 +14,22 @@ * limitations under the License. *@ +@* + * Copyright 2025 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *@ + @import uk.gov.hmrc.ngrraldfrontend.config.AppConfig @import uk.gov.hmrc.govukfrontend.views.html.components._ @@ -41,7 +57,7 @@ } @selectedPropertyAddress@messages("repairsAndFittingOut.p1")
+@messages("repairsAndFittingOut.p1")
@govukRadios(ngrRadio) @saveAndContinueButton(msg = messages("service.continue"), isStartButton = false) } diff --git a/conf/messages b/conf/messages index 4934219..67adb54 100644 --- a/conf/messages +++ b/conf/messages @@ -412,5 +412,6 @@ rentReview.rentReviewMonthsYears.years.maximum.1000.error = How often your rent #Repairs and fitting out repairsAndFittingOut.title = Repairs and fitting out +repairsAndFittingOut.header = Have you done any repairs or fitting out in the property? repairsAndFittingOut.p1 = Repairs are when you fix things like poor electrical wiring, leaking windows or a broken toilet. Fitting out is when you install things like air conditioning, a kitchen or carpeting. repairsAndFittingOut.empty.error = Select yes if you have done any repairs of fitting out in the property \ No newline at end of file diff --git a/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala b/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala index 7336c3d..d8b073a 100644 --- a/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala +++ b/test/uk/gov/hmrc/ngrraldfrontend/views/RepairAndFittingOutViewSpec.scala @@ -30,23 +30,25 @@ class RepairAndFittingOutViewSpec extends ViewBaseSpec { lazy val view: RepairsAndFittingOutView = inject[RepairsAndFittingOutView] object Strings { - val heading = "Repairs and fitting out" - val hint = "Repairs are when you fix things like poor electrical wiring, leaking windows or a broken toilet. Fitting out is when you install things like air conditioning, a kitchen or carpeting." + val title = "Repairs and fitting out" + val body = "Repairs are when you fix things like poor electrical wiring, leaking windows or a broken toilet. Fitting out is when you install things like air conditioning, a kitchen or carpeting." + val heading = "Have you done any repairs or fitting out in the property?" val radio1 = "Yes" val radio2 = "No" val continue = "Continue" } object Selectors { - val heading = "#main-content > div > div.govuk-grid-column-two-thirds > form > h1" - val hint = "#main-content > div > div.govuk-grid-column-two-thirds > form > p" - val radio1 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > div > div:nth-child(1) > label" - val radio2 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > div > div:nth-child(2) > label" + val title = "#main-content > div > div.govuk-grid-column-two-thirds > form > h1" + val body = "#main-content > div > div.govuk-grid-column-two-thirds > form > p" + val heading = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > fieldset > legend > h1" + val radio1 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > fieldset > div > div:nth-child(1) > label" + val radio2 = "#main-content > div > div.govuk-grid-column-two-thirds > form > div > fieldset > div > div:nth-child(2) > label" val continue = "#continue" } val address = "5 Brixham Marina, Berry Head Road, Brixham, Devon, TQ5 9BW" - private val ngrRadio: NGRRadio = simpleNgrRadio(RepairsAndFittingOutForm.radio) + private val ngrRadio: NGRRadio = RepairsAndFittingOutForm.repairsAndFittingOutRadio val form = RepairsAndFittingOutForm.form.fillAndValidate(RepairsAndFittingOutForm("true")) val radio: Radios = buildRadios(form, ngrRadio) @@ -69,12 +71,16 @@ class RepairAndFittingOutViewSpec extends ViewBaseSpec { htmlRender must not be empty } + "show correct title" in { + elementText(Selectors.title) mustBe Strings.title + } + "show correct heading" in { elementText(Selectors.heading) mustBe Strings.heading } - "show correct hint" in { - elementText(Selectors.hint) mustBe Strings.hint + "show correct body" in { + elementText(Selectors.body) mustBe Strings.body }