From d7e1bf644dcb40596e80b56ddcaf57918223bed1 Mon Sep 17 00:00:00 2001 From: RGlossop <75027918+RGlossop@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:25:01 +0000 Subject: [PATCH] SASS-9703: Expenses section: Create 'Other allowable property expenses' page --- ...rAllowablePropertyExpensesController.scala | 72 ++++++++ ...llowablePropertyExpensesFormProvider.scala | 36 ++++ app/pages/PageConstants.scala | 2 + ...gnOtherAllowablePropertyExpensesPage.scala | 29 +++ ...therAllowablePropertyExpensesSummary.scala | 42 +++++ ...erAllowablePropertyExpensesView.scala.html | 61 +++++++ conf/foreign.routes | 5 + conf/messages.cy | 25 ++- conf/messages.en | 23 +++ ...owablePropertyExpensesControllerSpec.scala | 166 ++++++++++++++++++ ...ablePropertyExpensesFormProviderSpec.scala | 62 +++++++ 11 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 app/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesController.scala create mode 100644 app/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProvider.scala create mode 100644 app/pages/foreign/expenses/ForeignOtherAllowablePropertyExpensesPage.scala create mode 100644 app/viewmodels/checkAnswers/foreign/expenses/ForeignOtherAllowablePropertyExpensesSummary.scala create mode 100644 app/views/foreign/expenses/ForeignOtherAllowablePropertyExpensesView.scala.html create mode 100644 test/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesControllerSpec.scala create mode 100644 test/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProviderSpec.scala diff --git a/app/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesController.scala b/app/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesController.scala new file mode 100644 index 00000000..2640580d --- /dev/null +++ b/app/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesController.scala @@ -0,0 +1,72 @@ +/* + * Copyright 2024 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 controllers.foreign.expenses + +import controllers.actions._ +import forms.foreign.expenses.ForeignOtherAllowablePropertyExpensesFormProvider +import models.Mode +import navigation.ForeignPropertyNavigator +import pages.foreign.expenses.ForeignOtherAllowablePropertyExpensesPage +import play.api.i18n.{I18nSupport, MessagesApi} +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} +import repositories.SessionRepository +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import views.html.foreign.expenses.ForeignOtherAllowablePropertyExpensesView + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} + +class ForeignOtherAllowablePropertyExpensesController @Inject()( + override val messagesApi: MessagesApi, + sessionRepository: SessionRepository, + foreignNavigator: ForeignPropertyNavigator, + identify: IdentifierAction, + getData: DataRetrievalAction, + requireData: DataRequiredAction, + formProvider: ForeignOtherAllowablePropertyExpensesFormProvider, + val controllerComponents: MessagesControllerComponents, + view: ForeignOtherAllowablePropertyExpensesView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + + + + def onPageLoad(taxYear: Int, countryCode: String,mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData) { + implicit request => + val form = formProvider(request.user.isAgentMessageKey) + val preparedForm = request.userAnswers.get(ForeignOtherAllowablePropertyExpensesPage(countryCode)) match { + case None => form + case Some(value) => form.fill(value) + } + + Ok(view(preparedForm, taxYear, countryCode, request.user.isAgentMessageKey, mode)) + } + + def onSubmit(taxYear: Int, countryCode: String, mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData).async { + implicit request => + val form = formProvider(request.user.isAgentMessageKey) + form.bindFromRequest().fold( + formWithErrors => + Future.successful(BadRequest(view(formWithErrors, taxYear, countryCode, request.user.isAgentMessageKey, mode))), + + value => + for { + updatedAnswers <- Future.fromTry(request.userAnswers.set(ForeignOtherAllowablePropertyExpensesPage(countryCode), value)) + _ <- sessionRepository.set(updatedAnswers) + } yield Redirect(foreignNavigator.nextPage(ForeignOtherAllowablePropertyExpensesPage(countryCode), taxYear, mode, request.userAnswers, updatedAnswers)) + ) + } +} diff --git a/app/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProvider.scala b/app/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProvider.scala new file mode 100644 index 00000000..78aa2965 --- /dev/null +++ b/app/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProvider.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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 forms.foreign.expenses + +import forms.mappings.Mappings +import play.api.data.Form + +import javax.inject.Inject + +class ForeignOtherAllowablePropertyExpensesFormProvider @Inject() extends Mappings { + + val minimum = 0 + val maximum = 100000000 + def apply(individualOrAgent: String): Form[BigDecimal] = + Form( + "otherAllowablePropertyExpensesAmount" -> currency( + s"foreignOtherAllowablePropertyExpenses.error.required.$individualOrAgent", + s"foreignOtherAllowablePropertyExpenses.error.nonNumeric.$individualOrAgent", + s"foreignOtherAllowablePropertyExpenses.error.nonNumeric.$individualOrAgent") + .verifying(inRange(BigDecimal(minimum), BigDecimal(maximum), "foreignOtherAllowablePropertyExpenses.error.outOfRange")) + ) +} diff --git a/app/pages/PageConstants.scala b/app/pages/PageConstants.scala index 94086ce6..3c0c77cb 100644 --- a/app/pages/PageConstants.scala +++ b/app/pages/PageConstants.scala @@ -61,6 +61,8 @@ object PageConstants { val foreignTaxPath: PropertyType => String = labelForPropertyType(_, "ForeignTax") + val foreignPropertyExpensesPath: PropertyType => String = labelForPropertyType(_, "expenses") + val foreignPropertySectionFinished: String = "foreignPropertySectionFinished" } diff --git a/app/pages/foreign/expenses/ForeignOtherAllowablePropertyExpensesPage.scala b/app/pages/foreign/expenses/ForeignOtherAllowablePropertyExpensesPage.scala new file mode 100644 index 00000000..a16ce9ea --- /dev/null +++ b/app/pages/foreign/expenses/ForeignOtherAllowablePropertyExpensesPage.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2024 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 pages.foreign.expenses + +import models.ForeignProperty +import pages.PageConstants.foreignPropertyExpensesPath +import pages.QuestionPage +import play.api.libs.json.JsPath + +case class ForeignOtherAllowablePropertyExpensesPage(countryCode: String) extends QuestionPage[BigDecimal] { + + override def path: JsPath = JsPath \ foreignPropertyExpensesPath(ForeignProperty) \ countryCode.toUpperCase \ toString + + override def toString: String = "foreignOtherAllowablePropertyExpenses" +} diff --git a/app/viewmodels/checkAnswers/foreign/expenses/ForeignOtherAllowablePropertyExpensesSummary.scala b/app/viewmodels/checkAnswers/foreign/expenses/ForeignOtherAllowablePropertyExpensesSummary.scala new file mode 100644 index 00000000..44f625b2 --- /dev/null +++ b/app/viewmodels/checkAnswers/foreign/expenses/ForeignOtherAllowablePropertyExpensesSummary.scala @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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 viewmodels.checkAnswers.foreign.expenses + +import controllers.routes +import models.{CheckMode, UserAnswers} +import pages.foreign.expenses.ForeignOtherAllowablePropertyExpensesPage +import play.api.i18n.Messages +import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow +import viewmodels.govuk.summarylist._ +import viewmodels.implicits._ + +object ForeignOtherAllowablePropertyExpensesSummary { + + def row(taxYear: Int, countryCode: String, answers: UserAnswers)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(ForeignOtherAllowablePropertyExpensesPage(countryCode)).map { + answer => + + SummaryListRowViewModel( + key = "foreignOtherAllowablePropertyExpenses.checkYourAnswersLabel", + value = ValueViewModel(answer.toString), + actions = Seq( + ActionItemViewModel("site.change", controllers.foreign.expenses.routes.ForeignOtherAllowablePropertyExpensesController.onPageLoad(taxYear, countryCode, CheckMode).url) + .withVisuallyHiddenText(messages("foreignOtherAllowablePropertyExpenses.change.hidden")) + ) + ) + } +} diff --git a/app/views/foreign/expenses/ForeignOtherAllowablePropertyExpensesView.scala.html b/app/views/foreign/expenses/ForeignOtherAllowablePropertyExpensesView.scala.html new file mode 100644 index 00000000..ad153b37 --- /dev/null +++ b/app/views/foreign/expenses/ForeignOtherAllowablePropertyExpensesView.scala.html @@ -0,0 +1,61 @@ +@* + * Copyright 2024 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 viewmodels.InputWidth._ +@import viewmodels.LabelSize + +@this( + layout: templates.Layout, + formHelper: FormWithCSRF, + govukErrorSummary: GovukErrorSummary, + govukInput: GovukInput, + govukButton: GovukButton +) + +@(form: Form[_], taxYear: Int, countryCode: String, individualOrAgent: String, mode: Mode)(implicit request: Request[_], messages: Messages) + +@layout(pageTitle = title(form, messages("foreignOtherAllowablePropertyExpenses.title"))) { + + @formHelper(action = controllers.foreign.expenses.routes.ForeignOtherAllowablePropertyExpensesController.onSubmit(taxYear, countryCode, mode), Symbol("autoComplete") -> "off") { + + @if(form.errors.nonEmpty) { + @govukErrorSummary(ErrorSummaryViewModel(form)) + } +

@messages("foreignOtherAllowablePropertyExpenses.heading")

+ +

@messages("foreignOtherAllowablePropertyExpenses.hyperlink.text.1") @messages("foreignOtherAllowablePropertyExpenses.hyperlink.text.2")

+ @govukInput( + InputViewModel( + field = form("otherAllowablePropertyExpensesAmount"), + label = LabelViewModel(messages(s"foreignOtherAllowablePropertyExpenses.label.$individualOrAgent")).withCssClass("govuk-label govuk-label--m") + ) + .asNumeric() + .withWidth(Fixed10).withPrefix(PrefixOrSuffix(content = Text("£"))) + ) + + @govukButton( + ButtonViewModel(messages("site.continue")).withId("continue") + ) + } +} diff --git a/conf/foreign.routes b/conf/foreign.routes index 67ff8c60..f7e49b42 100644 --- a/conf/foreign.routes +++ b/conf/foreign.routes @@ -103,3 +103,8 @@ GET /:taxYear/foreign-property/income/:countryCode/foreign-income-section POST /:taxYear/foreign-property/income/:countryCode/foreign-income-section-finished controllers.foreign.income.ForeignIncomeSectionCompleteController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = CheckMode) GET /:taxYear/foreign-property/expenses/:countryCode/foreign-property-expenses-start controllers.foreign.expenses.ForeignPropertyExpensesStartController.onPageLoad(taxYear: Int, countryCode: String) + +GET /:taxYear/foreign-property/expenses/:countryCode/foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onPageLoad(taxYear: Int, countryCode: String, mode: Mode = NormalMode) +POST /:taxYear/foreign-property/expenses/:countryCode/foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = NormalMode) +GET /:taxYear/foreign-property/expenses/:countryCode/change-foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onPageLoad(taxYear: Int, countryCode: String, mode: Mode = CheckMode) +POST /:taxYear/foreign-property/expenses/:countryCode/change-foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = CheckMode) diff --git a/conf/messages.cy b/conf/messages.cy index 27e2dcf1..43540bcf 100644 --- a/conf/messages.cy +++ b/conf/messages.cy @@ -2029,4 +2029,27 @@ foreignPropertyExpensesStart.p4.individual = *Missing Welsh* foreignPropertyExpensesStart.p4.agent = *Missing Welsh* foreignPropertyExpensesStart.p5 = *Missing Welsh* foreignPropertyExpensesStart.p6 = *Missing Welsh* -foreignPropertyExpensesStart.p7 = *Missing Welsh* \ No newline at end of file +foreignPropertyExpensesStart.p7 = *Missing Welsh* + +foreignOtherAllowablePropertyExpenses.title = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.heading = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet1 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet2 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet3 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet4 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet5.individual = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet5.agent = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet6 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet7.individual = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.bullet7.agent = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.hyperlink.text.1 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.hyperlink.text.2 = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.label.individual = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.label.agent = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.checkYourAnswersLabel = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.error.nonNumeric.individual = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.error.nonNumeric.agent = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.error.required.individual = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.error.required.agent = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.error.outOfRange = *Missing Welsh* +foreignOtherAllowablePropertyExpenses.change.hidden = *Missing Welsh* \ No newline at end of file diff --git a/conf/messages.en b/conf/messages.en index 8a5cdf90..55c1579a 100644 --- a/conf/messages.en +++ b/conf/messages.en @@ -2057,3 +2057,26 @@ foreignPropertyExpensesStart.p4.agent = Your client can claim: foreignPropertyExpensesStart.p5 = Consolidated expenses, where you add up all of your expenses and enter one total amount. foreignPropertyExpensesStart.p6 = or foreignPropertyExpensesStart.p7 = Individual expenses, where you enter an amount for each separate expenses type. + +foreignOtherAllowablePropertyExpenses.title = Other allowable property expenses +foreignOtherAllowablePropertyExpenses.heading = Other allowable property expenses +foreignOtherAllowablePropertyExpenses.bullet1 = office costs, for example stationery or phone bills +foreignOtherAllowablePropertyExpenses.bullet2 = clothing expenses, for example uniforms +foreignOtherAllowablePropertyExpenses.bullet3 = things you buy to sell on, for example stock or raw materials +foreignOtherAllowablePropertyExpenses.bullet4 = financial costs, for example insurance or bank charges +foreignOtherAllowablePropertyExpenses.bullet5.individual = costs of your business premises, for example heating, lighting, business rates +foreignOtherAllowablePropertyExpenses.bullet5.agent = costs of your clients business premises, for example heating, lighting, business rates +foreignOtherAllowablePropertyExpenses.bullet6 = advertising or marketing, for example website costs +foreignOtherAllowablePropertyExpenses.bullet7.individual = training courses related to your business, for example refresher courses +foreignOtherAllowablePropertyExpenses.bullet7.agent = training courses related to your clients business, for example refresher courses +foreignOtherAllowablePropertyExpenses.hyperlink.text.1 = Read more about +foreignOtherAllowablePropertyExpenses.hyperlink.text.2 = allowable property expenses at GOV.UK (opens in new tab). +foreignOtherAllowablePropertyExpenses.label.individual = How much were your other allowable property expenses? +foreignOtherAllowablePropertyExpenses.label.agent = How much were your client's other allowable property expenses? +foreignOtherAllowablePropertyExpenses.checkYourAnswersLabel = Other allowable property expenses +foreignOtherAllowablePropertyExpenses.error.nonNumeric.individual = The amount for your other allowable property expenses can only include pounds and pence, for example £600 or £600.20 +foreignOtherAllowablePropertyExpenses.error.nonNumeric.agent = The amount for your other allowable property expenses can only include pounds and pence, for example £600 or £600.20 +foreignOtherAllowablePropertyExpenses.error.required.individual = Enter the amount for your other allowable property expenses +foreignOtherAllowablePropertyExpenses.error.required.agent = Enter the amount for your clients other allowable property expenses +foreignOtherAllowablePropertyExpenses.error.outOfRange = Enter an amount between {0} and {1} +foreignOtherAllowablePropertyExpenses.change.hidden = Other allowable property expenses diff --git a/test/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesControllerSpec.scala b/test/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesControllerSpec.scala new file mode 100644 index 00000000..455a233f --- /dev/null +++ b/test/controllers/foreign/expenses/ForeignOtherAllowablePropertyExpensesControllerSpec.scala @@ -0,0 +1,166 @@ +/* + * Copyright 2024 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 controllers.foreign.expenses + +import base.SpecBase +import controllers.routes +import forms.foreign.expenses.ForeignOtherAllowablePropertyExpensesFormProvider +import models.{NormalMode, UserAnswers} +import navigation.{FakeForeignPropertyNavigator, FakeNavigator, ForeignPropertyNavigator, Navigator} +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar +import pages.foreign.expenses.ForeignOtherAllowablePropertyExpensesPage +import play.api.data.Form +import play.api.inject.bind +import play.api.mvc.Call +import play.api.test.FakeRequest +import play.api.test.Helpers._ +import repositories.SessionRepository +import views.html.foreign.expenses.ForeignOtherAllowablePropertyExpensesView + +import scala.concurrent.Future + +class ForeignOtherAllowablePropertyExpensesControllerSpec extends SpecBase with MockitoSugar { + + val formProvider = new ForeignOtherAllowablePropertyExpensesFormProvider() + private val isAgentMessageKey = "individual" + val form: Form[BigDecimal] = formProvider(isAgentMessageKey) + + def onwardRoute = Call("GET", "/foo") + + val validAnswer: BigDecimal = BigDecimal(0) + val taxYear = 2023 + val countryCode = "AUS" + + lazy val foreignOtherAllowablePropertyExpensesRoute = + controllers.foreign.expenses.routes.ForeignOtherAllowablePropertyExpensesController.onPageLoad(taxYear, countryCode, NormalMode).url + + "ForeignOtherAllowablePropertyExpenses Controller" - { + + "must return OK and the correct view for a GET" in { + + val application = applicationBuilder(userAnswers = Some(emptyUserAnswers), isAgent = false).build() + + running(application) { + val request = FakeRequest(GET, foreignOtherAllowablePropertyExpensesRoute) + + val result = route(application, request).value + + val view = application.injector.instanceOf[ForeignOtherAllowablePropertyExpensesView] + + status(result) mustEqual OK + contentAsString(result) mustEqual view(form, taxYear, countryCode, isAgentMessageKey, NormalMode)(request, messages(application)).toString + } + } + + "must populate the view correctly on a GET when the question has previously been answered" in { + + val userAnswers = UserAnswers(userAnswersId).set(ForeignOtherAllowablePropertyExpensesPage(countryCode), validAnswer).success.value + + val application = applicationBuilder(userAnswers = Some(userAnswers), isAgent = false).build() + + running(application) { + val request = FakeRequest(GET, foreignOtherAllowablePropertyExpensesRoute) + + val view = application.injector.instanceOf[ForeignOtherAllowablePropertyExpensesView] + + val result = route(application, request).value + + status(result) mustEqual OK + contentAsString(result) mustEqual view(form.fill(validAnswer), taxYear, countryCode, isAgentMessageKey, NormalMode)(request, messages(application)).toString + } + } + + "must redirect to the next page when valid data is submitted" in { + + val mockSessionRepository = mock[SessionRepository] + + when(mockSessionRepository.set(any())) thenReturn Future.successful(true) + + val application = + applicationBuilder(userAnswers = Some(emptyUserAnswers), isAgent = false) + .overrides( + bind[ForeignPropertyNavigator].toInstance(new FakeForeignPropertyNavigator(onwardRoute)), + bind[SessionRepository].toInstance(mockSessionRepository) + ) + .build() + + running(application) { + val request = + FakeRequest(POST, foreignOtherAllowablePropertyExpensesRoute) + .withFormUrlEncodedBody(("otherAllowablePropertyExpensesAmount", validAnswer.toString)) + + val result = route(application, request).value + + status(result) mustEqual SEE_OTHER + redirectLocation(result).value mustEqual onwardRoute.url + } + } + + "must return a Bad Request and errors when invalid data is submitted" in { + + val application = applicationBuilder(userAnswers = Some(emptyUserAnswers), isAgent = false).build() + + running(application) { + val request = + FakeRequest(POST, foreignOtherAllowablePropertyExpensesRoute) + .withFormUrlEncodedBody(("otherAllowablePropertyExpensesAmount", "invalid value")) + + val boundForm = form.bind(Map("otherAllowablePropertyExpensesAmount" -> "invalid value")) + + val view = application.injector.instanceOf[ForeignOtherAllowablePropertyExpensesView] + + val result = route(application, request).value + + status(result) mustEqual BAD_REQUEST + contentAsString(result) mustEqual view(boundForm, taxYear, countryCode, isAgentMessageKey, NormalMode)(request, messages(application)).toString + } + } + + "must redirect to Journey Recovery for a GET if no existing data is found" in { + + val application = applicationBuilder(userAnswers = None, isAgent = false).build() + + running(application) { + val request = FakeRequest(GET, foreignOtherAllowablePropertyExpensesRoute) + + val result = route(application, request).value + + status(result) mustEqual SEE_OTHER + redirectLocation(result).value mustEqual routes.JourneyRecoveryController.onPageLoad().url + } + } + + "must redirect to Journey Recovery for a POST if no existing data is found" in { + + val application = applicationBuilder(userAnswers = None, isAgent = false).build() + + running(application) { + val request = + FakeRequest(POST, foreignOtherAllowablePropertyExpensesRoute) + .withFormUrlEncodedBody(("otherAllowablePropertyExpensesAmount", validAnswer.toString)) + + val result = route(application, request).value + + status(result) mustEqual SEE_OTHER + + redirectLocation(result).value mustEqual routes.JourneyRecoveryController.onPageLoad().url + } + } + } +} diff --git a/test/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProviderSpec.scala b/test/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProviderSpec.scala new file mode 100644 index 00000000..800a9316 --- /dev/null +++ b/test/forms/foreign/expenses/ForeignOtherAllowablePropertyExpensesFormProviderSpec.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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 forms.foreign.expenses + +import forms.behaviours.CurrencyFieldBehaviours +import play.api.data.FormError + +class ForeignOtherAllowablePropertyExpensesFormProviderSpec extends CurrencyFieldBehaviours { + + val form = new ForeignOtherAllowablePropertyExpensesFormProvider()("individual") + + ".otherAllowablePropertyExpensesAmount" - { + + val fieldName = "otherAllowablePropertyExpensesAmount" + + val minimum = 0 + val maximum = 100000000 + + val validDataGenerator = intsInRangeWithCommas(minimum, maximum) + + behave like fieldThatBindsValidData( + form, + fieldName, + validDataGenerator + ) + + behave like currencyField( + form, + fieldName, + nonNumericError = FormError(fieldName, "foreignOtherAllowablePropertyExpenses.error.nonNumeric.individual"), + twoDecimalPlacesError = FormError(fieldName, "foreignOtherAllowablePropertyExpenses.error.nonNumeric.individual") + ) + + behave like currencyFieldWithRange( + form, + fieldName, + minimum = minimum, + maximum = maximum, + expectedError = FormError(fieldName, "foreignOtherAllowablePropertyExpenses.error.outOfRange", Seq(minimum, maximum)) + ) + + behave like mandatoryField( + form, + fieldName, + requiredError = FormError(fieldName, "foreignOtherAllowablePropertyExpenses.error.required.individual") + ) + } +}