diff --git a/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala b/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala index 8cb4255bf..daa573ed3 100644 --- a/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala +++ b/app/uk/gov/hmrc/gform/binders/ValueClassBinder.scala @@ -61,8 +61,11 @@ object ValueClassBinder { implicit val sectionTitle4GaBinder: PathBindable[SectionTitle4Ga] = valueClassBinder(_.value) implicit val sectionNumberBinder: PathBindable[SectionNumber] = new PathBindable[SectionNumber] { override def bind(key: String, value: String): Either[String, SectionNumber] = { - val sectionNumber: Try[SectionNumber] = SectionNumber.parse(value) - sectionNumber.map(_.asRight).getOrElse(s"No valid value in path $key: $value".asLeft) + val sectionNumber: Option[SectionNumber] = SectionNumber.parse(value) + sectionNumber + .map(_.asRight) + .getOrElse(SectionNumber.Legacy(value).asRight) + //.getOrElse(s"No valid value in path $key: $value".asLeft) } override def unbind(key: String, sectionNumber: SectionNumber): String = sectionNumber.value } @@ -173,7 +176,7 @@ object ValueClassBinder { case "-" => Right(None) case a => Try(value.toInt) match { - case Success(a) => Right(Some(SectionNumber.Classic(a))) + case Success(a) => Right(Some(SectionNumber.Classic.NormalPage(TemplateSectionIndex(a)))) case Failure(error) => Left(("No valid value in url binding: " + value + ". Error: " + error)) } } @@ -191,7 +194,8 @@ object ValueClassBinder { SectionNumber .parse(value) .map(_.asRight) - .getOrElse(s"No valid value in path $key: $value".asLeft) + .getOrElse(SectionNumber.Legacy(value).asRight) + //.getOrElse(s"No valid value in path $key: $value".asLeft) } override def unbind(key: String, sectionNumber: SectionNumber): String = @@ -232,21 +236,20 @@ object ValueClassBinder { private def toSectionNumber(key: String, value: String): Either[String, SectionNumber] = SectionNumber .parse(value) - .toOption - .fold[Either[String, SectionNumber]](s"No valid value in path $key: $value".asLeft)(sn => sn.asRight) + .fold[Either[String, SectionNumber]](SectionNumber.Legacy(value).asRight)(sn => sn.asRight) + //.fold[Either[String, SectionNumber]](s"No valid value in path $key: $value".asLeft)(sn => sn.asRight) - private val cyaPat = raw"cya([\d,]+)".r override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, FastForward]] = params.get(key).flatMap(_.headOption).map { case FastForward.ffYes => FastForward.Yes.asRight case FastForward.ffCYAFormSummary => FastForward.CYA(SectionOrSummary.FormSummary).asRight case FastForward.ffCYATaskSummary => FastForward.CYA(SectionOrSummary.TaskSummary).asRight - case cyaPat(from) => + case value if value.startsWith("cya:") => for { - sn2 <- toSectionNumber(key, from) + sn2 <- toSectionNumber(key, value.drop(4)) } yield FastForward.CYA(SectionOrSummary.Section(sn2)) - case value if value.startsWith("back") => - toSectionNumber(key, value.replace("back", "")).map(FastForward.BackUntil) + case value if value.startsWith("back:") => + toSectionNumber(key, value.replace("back:", "")).map(FastForward.BackUntil) case value => toSectionNumber(key, value).map(FastForward.StopAt) } diff --git a/app/uk/gov/hmrc/gform/builder/BuilderController.scala b/app/uk/gov/hmrc/gform/builder/BuilderController.scala index 531643198..4c82cc61b 100644 --- a/app/uk/gov/hmrc/gform/builder/BuilderController.scala +++ b/app/uk/gov/hmrc/gform/builder/BuilderController.scala @@ -67,9 +67,6 @@ class BuilderController( final def apply(html: Html): Json = Json.fromString(html.toString) } - private val compatibilityVersion = - 21 // This is managed manually. Increase it any time API used by builder extension is changed. - // Returns section from raw json which correspond to runtime sectionNumber parameter. def originalSection( formTemplateId: FormTemplateId, @@ -89,7 +86,7 @@ class BuilderController( val bracket = formModel.bracket(sectionNumber) bracket.fold { nonRepeatingPage => - val pageModel = nonRepeatingPage.singleton + val pageModel = nonRepeatingPage.singleton.singleton pageModel.allFormComponentIds match { case Nil => None @@ -97,8 +94,9 @@ class BuilderController( val compName = head.baseComponentId sectionNumber match { - case SectionNumber.Classic(value) => + case SectionNumber.Classic.NormalPage(value) => originalNonRepeatingPageJson(json.hcursor, compName, Nil) + case SectionNumber.TaskList(coordinates, sn) => val acursor: ACursor = json.hcursor .downField("sections") @@ -113,11 +111,18 @@ class BuilderController( List(DownArray, DownField("sections")) originalNonRepeatingPageJson(acursor, compName, historySuffix) + case _ => Option.empty[Json] } } }(repeatingPage => Option.empty[Json]) { addToList => sectionNumber match { - case SectionNumber.Classic(value) => + case SectionNumber.Legacy(_) => Option.empty[Json] + case SectionNumber.Classic.NormalPage(_) | SectionNumber.Classic.RepeatedPage(_, _) => + Option.empty[Json] + case SectionNumber.Classic.AddToListPage.DefaultPage(_) | + SectionNumber.Classic.AddToListPage.Page(_, _, _) | + SectionNumber.Classic.AddToListPage.CyaPage(_, _) | + SectionNumber.Classic.AddToListPage.RepeaterPage(_, _) => originalSectionInAddToList(json, addToList, formModel, sectionNumber, Nil) case SectionNumber.TaskList(coordinates, sn) => json.hcursor @@ -152,8 +157,7 @@ class BuilderController( _.deepMerge( Json.obj( "hiddenComponentIds" := hiddenComponentIds, - "hiddenChoicesLookup" := hiddenChoiceIndexes(formModelOptics, sectionNumber), - "version" := compatibilityVersion + "hiddenChoicesLookup" := hiddenChoiceIndexes(formModelOptics, sectionNumber) ) ) ) @@ -202,8 +206,7 @@ class BuilderController( json .map(json => Json.obj( - "formTemplate" := json, - "version" := compatibilityVersion + "formTemplate" := json ) ) .fold(BadRequest(s"Form template not found for $formTemplateId"))(json => Ok(json)) @@ -1064,11 +1067,11 @@ class BuilderController( val bracket: Bracket[DataExpanded] = formModel.bracket(sectionNumber) bracket match { - case Bracket.NonRepeatingPage(_, _, _) => + case Bracket.NonRepeatingPage(_, _) => badRequest("Expected AddToList bracket, but got NonRepeatingPage").pure[Future] case Bracket.RepeatingPage(_, _) => badRequest("Expected AddToList bracket, but got RepeatingPage").pure[Future] - case bracket @ Bracket.AddToList(_, _) => + case bracket @ Bracket.AddToList(_, _, _) => pageModel .fold(singleton => badRequest("Invalid page model. Expected Repeater got Singleton"))(checkYourAnswers => badRequest("Invalid page model. Expected Repeater got CheckYourAnswers") diff --git a/app/uk/gov/hmrc/gform/controllers/Navigation.scala b/app/uk/gov/hmrc/gform/controllers/Navigation.scala index a82f1f7ba..b7b22faea 100644 --- a/app/uk/gov/hmrc/gform/controllers/Navigation.scala +++ b/app/uk/gov/hmrc/gform/controllers/Navigation.scala @@ -17,7 +17,7 @@ package uk.gov.hmrc.gform.controllers import cats.syntax.eq._ -import uk.gov.hmrc.gform.models.{ Bracket, Visibility } +import uk.gov.hmrc.gform.models.Visibility import uk.gov.hmrc.gform.sharedmodel.formtemplate._ import uk.gov.hmrc.gform.models.FormModel @@ -28,23 +28,24 @@ trait Navigation { val availableSectionNumbers: List[SectionNumber] = formModel.availableSectionNumbers - val minSectionNumber: SectionNumber = availableSectionNumbers.min(Ordering.by((_: SectionNumber).numberValue)) + val minSectionNumber: SectionNumber = availableSectionNumbers.min - val addToListBrackets: List[Bracket.AddToList[Visibility]] = - formModel.brackets.addToListBrackets + val addToListSectionNumbers: List[SectionNumber] = availableSectionNumbers.filter(_.isAddToList) - val addToListSectionNumbers: List[SectionNumber] = - addToListBrackets.flatMap(_.toPageModelWithNumber.toList).map(_._2) - - val addToListRepeaterSectionNumbers: List[SectionNumber] = - addToListBrackets.flatMap(_.iterations.toList).map(_.repeater.sectionNumber) + val addToListRepeaterSectionNumbers: List[SectionNumber] = availableSectionNumbers.filter(_.isAddToListRepeaterPage) val addToListNonRepeaterSectionNumbers: List[SectionNumber] = addToListSectionNumbers.filterNot(addToListRepeaterSectionNumbers.toSet) val samePageRepeatersSectionNumbers: List[List[SectionNumber]] = - formModel.brackets.addToListBrackets - .map(_.iterations.toList.map(_.repeater.sectionNumber)) + addToListRepeaterSectionNumbers + .groupBy( + _.fold(a => (Coordinates(TaskSectionNumber(0), TaskNumber(0)), a.sectionIndex))(a => + (a.coordinates, a.sectionNumber.sectionIndex) + ) + ) + .values + .toList val filteredSectionNumbers: SectionNumber => List[SectionNumber] = sectionNumber => { val availableSn = availableSectionNumbers.filter( @@ -70,32 +71,21 @@ case class Navigator( sectionNumber: SectionNumber, formModel: FormModel[Visibility] ) extends Navigation { - private val maxSectionNumber: SectionNumber = - availableSectionNumbers.max(Ordering.by((_: SectionNumber).numberValue)) val previousSectionNumber: Option[SectionNumber] = filteredSectionNumbers(sectionNumber).findLast(_ < sectionNumber) val nextSectionNumber: SectionNumber = { - val sn = sectionNumber.increment + val sn = sectionNumber.increment(formModel) if (addToListSectionNumbers.contains(sectionNumber)) { sn } else { - addToListRepeaterSectionNumbers + val repeaters = addToListRepeaterSectionNumbers .filter(_.fold(_ => true)(taskList => taskList.coordinates === sn.toCoordinatesUnsafe)) .find(_ >= sn) - .fold(sn) { nrsn => - filteredSectionNumbers(nrsn).filter(_ < nrsn).find(_ >= sn).getOrElse(nrsn) - } + repeaters.fold(sn) { nrsn => + filteredSectionNumbers(nrsn).filter(_ < nrsn).find(_ >= sn).getOrElse(nrsn) + } } } - - require( - sectionNumber >= minSectionNumber, - s"section number is too low: $sectionNumber is not >= $minSectionNumber" - ) - require( - sectionNumber <= maxSectionNumber, - s"section number is too big: $sectionNumber is not <= $maxSectionNumber" - ) } diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala index 071486d28..8c421ad23 100644 --- a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala +++ b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressions.scala @@ -16,7 +16,7 @@ package uk.gov.hmrc.gform.eval -import uk.gov.hmrc.gform.models.{ BracketPlain, PageMode, Repeater, Singleton } +import uk.gov.hmrc.gform.models.{ Bracket, PageMode, Repeater, SingletonWithNumber } import uk.gov.hmrc.gform.sharedmodel.formtemplate.Expr /* @@ -24,10 +24,10 @@ import uk.gov.hmrc.gform.sharedmodel.formtemplate.Expr * This doesn't include expressions in fields */ object AllPageModelExpressions extends ExprExtractorHelpers { - def unapply[A <: PageMode](bracket: BracketPlain[A]): Option[List[ExprMetadata]] = { + def unapply[A <: PageMode](bracket: Bracket[A]): Option[List[ExprMetadata]] = { - def fromSingleton(singleton: Singleton[_]): List[Expr] = { - val page = singleton.page + def fromSingleton(singleton: SingletonWithNumber[_]): List[Expr] = { + val page = singleton.singleton.page val pageExprs = page.title.allInterpolations ++ fromOption( page.description, page.shortName, @@ -51,13 +51,13 @@ object AllPageModelExpressions extends ExprExtractorHelpers { repeater.expandedSummaryName ) - def fromNonRepeatingBracket(bracket: BracketPlain.NonRepeatingPage[A]): List[Expr] = + def fromNonRepeatingBracket(bracket: Bracket.NonRepeatingPage[A]): List[Expr] = fromSingleton(bracket.singleton) - def fromRepeatedBracket(bracket: BracketPlain.RepeatingPage[A]): List[Expr] = + def fromRepeatedBracket(bracket: Bracket.RepeatingPage[A]): List[Expr] = bracket.source.repeats :: bracket.singletons.toList.flatMap(fromSingleton) - def fromAddToListBracket(bracket: BracketPlain.AddToList[A]): List[Expr] = + def fromAddToListBracket(bracket: Bracket.AddToList[A]): List[Expr] = fromSmartStrings( bracket.source.summaryName, bracket.source.addAnotherQuestion.label, @@ -66,7 +66,7 @@ object AllPageModelExpressions extends ExprExtractorHelpers { bracket.source.infoMessage, bracket.source.addAnotherQuestion.helpText ) ++ bracket.iterations.toList.flatMap { iteration => - iteration.singletons.toList.flatMap(fromSingleton) ::: fromRepeater(iteration.repeater) + iteration.singletons.toList.flatMap(fromSingleton) ::: fromRepeater(iteration.repeater.repeater) } val pageExprs: List[Expr] = bracket.fold(fromNonRepeatingBracket)(fromRepeatedBracket)(fromAddToListBracket) diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala index 16da073d8..d81e634ce 100644 --- a/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala +++ b/app/uk/gov/hmrc/gform/eval/AllPageModelExpressionsGetter.scala @@ -16,7 +16,7 @@ package uk.gov.hmrc.gform.eval -import uk.gov.hmrc.gform.models.{ BracketPlain, DataExpanded, FormModel, PageMode, Singleton } +import uk.gov.hmrc.gform.models.{ Bracket, DataExpanded, FormModel, PageMode, Singleton } import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ BooleanExpr, Expr } import uk.gov.hmrc.gform.sharedmodel.formtemplate.CheckYourAnswersPage @@ -24,13 +24,13 @@ object AllPageModelExpressionsGetter extends ExprExtractorHelpers { /* * Returns list of every single expression in a bracket */ - def allExprs[A <: PageMode](formModel: FormModel[DataExpanded])(bracketPlain: BracketPlain[A]): List[Expr] = { + def allExprs[A <: PageMode](formModel: FormModel[DataExpanded])(bracket: Bracket[A]): List[Expr] = { val bracketExprs = - bracketPlain match { + bracket match { case AllPageModelExpressions(exprMetadatas) => exprMetadatas.map(_.expr) case otherwise => List.empty[Expr] } - bracketExprs ++ formComponentsExprs(formModel)(bracketPlain) + bracketExprs ++ formComponentsExprs(formModel)(bracket) } @@ -78,16 +78,17 @@ object AllPageModelExpressionsGetter extends ExprExtractorHelpers { private def formComponentsExprs[A <: PageMode]( formModel: FormModel[DataExpanded] - )(bracketPlain: BracketPlain[A]): List[Expr] = - bracketPlain.fold { nonRepeatingPage => - fromSingleton(formModel)(nonRepeatingPage.singleton) + )(bracket: Bracket[A]): List[Expr] = + bracket.fold { nonRepeatingPage => + fromSingleton(formModel)(nonRepeatingPage.singleton.singleton) } { repeatingPage => - repeatingPage.singletons.toList.flatMap(fromSingleton(formModel)) + repeatingPage.singletons.map(_.singleton).toList.flatMap(fromSingleton(formModel)) } { addToList => - addToList.iterations.toList.flatMap { iteration => - iteration.singletons.toList.flatMap(fromSingleton(formModel)) ++ - addToList.source.cyaPage.map(fromCheckYourAnswerPage).getOrElse(Nil) - } + addToList.defaultPage.map(_.singleton).toList.flatMap(fromSingleton(formModel)) ++ + addToList.iterations.toList.flatMap { iteration => + iteration.singletons.map(_.singleton).toList.flatMap(fromSingleton(formModel)) ++ + addToList.source.cyaPage.map(fromCheckYourAnswerPage).getOrElse(Nil) + } } } diff --git a/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala b/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala index 7a8bd54f4..4b79838af 100644 --- a/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala +++ b/app/uk/gov/hmrc/gform/eval/AllPageModelSums.scala @@ -16,11 +16,11 @@ package uk.gov.hmrc.gform.eval -import uk.gov.hmrc.gform.models.{ BracketPlain, PageMode } +import uk.gov.hmrc.gform.models.{ Bracket, PageMode } import uk.gov.hmrc.gform.sharedmodel.formtemplate.Sum object AllPageModelSums { - def unapply[A <: PageMode](bracket: BracketPlain[A]): Option[Set[Sum]] = bracket match { + def unapply[A <: PageMode](bracket: Bracket[A]): Option[Set[Sum]] = bracket match { case AllPageModelExpressions(exprMetadatas) => Some(exprMetadatas.flatMap(_.expr.sums).toSet).filter(_.nonEmpty) case _ => None } diff --git a/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala b/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala index 098307a67..67ddd7755 100644 --- a/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala +++ b/app/uk/gov/hmrc/gform/eval/StandaloneSumInfo.scala @@ -17,7 +17,7 @@ package uk.gov.hmrc.gform.eval import cats.syntax.eq._ -import uk.gov.hmrc.gform.models.{ BracketPlainCoordinated, PageMode, TaskModelCoordinated } +import uk.gov.hmrc.gform.models.{ BracketPlainCoordinated, PageMode, TaskModel } import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FormComponentId, FormCtx, Sum } /** This represents \${abc.sum} expression, where this expression @@ -41,13 +41,13 @@ object StandaloneSumInfo { val allSums: List[Set[Sum]] = brackets.fold { classic => - classic.bracketPlains.collect { case AllPageModelSums(sums) => + classic.brackets.collect { case AllPageModelSums(sums) => sums } } { taskList => - taskList.bracketPlains + taskList.coordinatedBrackets .map(_._2) - .collect { case TaskModelCoordinated.Editable(brackets) => brackets.toList } + .collect { case TaskModel.Editable(brackets) => brackets.toList } .flatten .collect { case AllPageModelSums(sums) => sums } } diff --git a/app/uk/gov/hmrc/gform/gform/DownloadController.scala b/app/uk/gov/hmrc/gform/gform/DownloadController.scala index f33e25d41..c58941fc0 100644 --- a/app/uk/gov/hmrc/gform/gform/DownloadController.scala +++ b/app/uk/gov/hmrc/gform/gform/DownloadController.scala @@ -52,7 +52,7 @@ class DownloadController( OperationWithForm.DownloadFileByInternalLink ) { _ => _ => _ => _ => formModelOptics => val formModel = formModelOptics.formModelRenderPageOptics.formModel - val allExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel)) + val allExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel)) val extension = fileName.substring(fileName.lastIndexOf('.') + 1) if (!allExprs.contains(LinkCtx(InternalLink.Download(fileName)))) { diff --git a/app/uk/gov/hmrc/gform/gform/FastForwardService.scala b/app/uk/gov/hmrc/gform/gform/FastForwardService.scala index b184dba4b..ce5b9ab5c 100644 --- a/app/uk/gov/hmrc/gform/gform/FastForwardService.scala +++ b/app/uk/gov/hmrc/gform/gform/FastForwardService.scala @@ -160,7 +160,7 @@ class FastForwardService( ) ) case _ => - val maybeCoordinates = maybeSectionNumber.flatMap(_.toCoordinates) + val maybeCoordinates = maybeSectionNumber.flatMap(_.maybeCoordinates) Redirect( routes.SummaryController .summaryById(cache.formTemplate._id, maybeAccessCode, maybeCoordinates, None, ff = fastForward.headOption) diff --git a/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala b/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala index 660645702..68f89cf8f 100644 --- a/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala +++ b/app/uk/gov/hmrc/gform/gform/FormAddToListController.scala @@ -87,7 +87,7 @@ class FormAddToListController( .confirmRemoval(formTemplateId, maybeAccessCode, sectionNumber, index, addToListId) val maybeBracket = formModel.bracket(sectionNumber) maybeBracket match { - case bracket @ Bracket.AddToList(iterations, source) => + case bracket @ Bracket.AddToList(defaultPage, iterations, source) => val (pageError, fieldErrors) = { val errorMessage = source.errorMessage.fold(request.messages.messages("addToList.error.selectOption"))(error => diff --git a/app/uk/gov/hmrc/gform/gform/FormController.scala b/app/uk/gov/hmrc/gform/gform/FormController.scala index 71d8c48c0..c44016669 100644 --- a/app/uk/gov/hmrc/gform/gform/FormController.scala +++ b/app/uk/gov/hmrc/gform/gform/FormController.scala @@ -147,7 +147,7 @@ class FormController( val bracket = formModel.bracket(sectionNumber) bracket match { - case Bracket.NonRepeatingPage(singleton, sectionNumber, _) => + case Bracket.NonRepeatingPage(SingletonWithNumber(singleton, sectionNumber), _) => val formModel = formModelOptics.formModelVisibilityOptics.formModel // section numbers with form components that other components may refer to val sns = formModel.pageModelLookup @@ -196,7 +196,12 @@ class FormController( validateSections(suppressErrors, sectionNumber)( renderSingleton(bracket.singletonForSectionNumber(sectionNumber), sectionNumber, _) ) - case bracket @ Bracket.AddToList(iterations, _) => + case bracket @ Bracket.AddToList(Some(defaultPage), _, _) + if defaultPage.sectionNumber === sectionNumber => + validateSections(suppressErrors, sectionNumber)( + renderSingleton(defaultPage.singleton, sectionNumber, _) + ) + case bracket @ Bracket.AddToList(_, iterations, _) => val iteration: Bracket.AddToListIteration[DataExpanded] = bracket.iterationForSectionNumber(sectionNumber) val (repeater, repeaterSectionNumber) = @@ -338,188 +343,195 @@ class FormController( ) = auth.authAndRetrieveForm[SectionSelectorType.Normal](formTemplateId, maybeAccessCode, OperationWithForm.EditForm) { implicit request => implicit lang => cache => implicit sse => formModelOptics => - val formVisibilityModel = formModelOptics.formModelVisibilityOptics.formModel - val fastForward = filterTerminatedFastForward(sectionNumber, rawFastForward, formVisibilityModel) - val navigator = Navigator(sectionNumber, formModelOptics.formModelVisibilityOptics.formModel) - - def callSelector(call1: => Call, call2: => Call, lastSectionNumber: Option[SectionNumber]): Future[Call] = - for { - invalidSectionNumber <- - fastForwardService.maybeInvalidSectionNumber(lastSectionNumber, cache, formModelOptics) - } yield if (invalidSectionNumber.isEmpty) call1 else call2 - - def goBack(toSectionNumber: SectionNumber) = { - val formModel = formModelOptics.formModelRenderPageOptics.formModel - val sectionTitle4Ga = sectionTitle4GaFactory(formModel(toSectionNumber), sectionNumber) - - def createBackUrl(sectionNumber: SectionNumber, fastForward: List[FastForward]) = - routes.FormController - .form( - cache.formTemplateId, - maybeAccessCode, - sectionNumber, - sectionTitle4Ga, - SuppressErrors.Yes, - fastForward - ) + sectionNumber match { + case SectionNumber.Legacy(_) => + // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost + redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode) + case _ => + val formVisibilityModel = formModelOptics.formModelVisibilityOptics.formModel + val fastForward = filterTerminatedFastForward(sectionNumber, rawFastForward, formVisibilityModel) + val navigator = Navigator(sectionNumber, formModelOptics.formModelVisibilityOptics.formModel) - def backCallF() = { - val firstCYA = fastForward.find { - case _: FastForward.CYA => true - case _ => false - } - firstCYA match { - case ff @ Some(FastForward.CYA(SectionOrSummary.FormSummary)) => - fastForward.last match { - case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 => - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - sectionNumber.toCoordinates, - None, - ff = ff - ) - .pure[Future] - case _ => - callSelector( - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - sectionNumber.toCoordinates, - None, - ff = ff - ), - createBackUrl(toSectionNumber, fastForward), - None - ) - } - case Some(FastForward.CYA(SectionOrSummary.Section(to))) => - fastForward.last match { - case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 => - createBackUrl(to, fastForward).pure[Future] - case _ => - callSelector( - createBackUrl(to, fastForward), - createBackUrl(toSectionNumber, fastForward), - Some(to) - ) - } - case Some(FastForward.StopAt(sn)) => - sectionNumber.fold { classic => - createBackUrl(toSectionNumber, FastForward.StopAt(sectionNumber) :: fastForward).pure[Future] - } { taskList => - val maybePreviousPage = navigator.previousSectionNumber - if (maybePreviousPage.isEmpty) { - uk.gov.hmrc.gform.tasklist.routes.TaskListController - .landingPage(cache.formTemplateId, maybeAccessCode) - .pure[Future] - } else { - createBackUrl(toSectionNumber, fastForward).pure[Future] - } + def callSelector(call1: => Call, call2: => Call, lastSectionNumber: Option[SectionNumber]): Future[Call] = + for { + invalidSectionNumber <- + fastForwardService.maybeInvalidSectionNumber(lastSectionNumber, cache, formModelOptics) + } yield if (invalidSectionNumber.isEmpty) call1 else call2 + def goBack(toSectionNumber: SectionNumber) = { + val formModel = formModelOptics.formModelRenderPageOptics.formModel + val sectionTitle4Ga = sectionTitle4GaFactory(formModel(toSectionNumber), sectionNumber) + + def createBackUrl(sectionNumber: SectionNumber, fastForward: List[FastForward]) = + routes.FormController + .form( + cache.formTemplateId, + maybeAccessCode, + sectionNumber, + sectionTitle4Ga, + SuppressErrors.Yes, + fastForward + ) + + def backCallF() = { + val firstCYA = fastForward.find { + case _: FastForward.CYA => true + case _ => false } - case _ => - sectionNumber.fold { classic => - createBackUrl(toSectionNumber, fastForward).pure[Future] - } { taskList => - fastForward match { - case FastForward.CYA(SectionOrSummary.TaskSummary) :: xs => - xs.lastOption match { - case Some(FastForward.BackUntil(sn)) if sectionNumber.compare(sn) <= 0 => - Future.successful( - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - sectionNumber.toCoordinates, - None, - ff = fastForward.headOption - ) + firstCYA match { + case ff @ Some(FastForward.CYA(SectionOrSummary.FormSummary)) => + fastForward.last match { + case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 => + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None, + ff = ff ) - case _ => - navigator.previousSectionNumber.fold( - Future.successful( - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - sectionNumber.toCoordinates, - None, - ff = fastForward.headOption - ) - ) - )(previousSectionNumber => createBackUrl(previousSectionNumber, fastForward).pure[Future]) - } - case _ => - val maybePreviousPage = navigator.previousSectionNumber - - if (maybePreviousPage.isEmpty) { + .pure[Future] + case _ => callSelector( - uk.gov.hmrc.gform.tasklist.routes.TaskListController - .landingPage(cache.formTemplateId, maybeAccessCode), - uk.gov.hmrc.gform.tasklist.routes.TaskListController - .landingPage(cache.formTemplateId, maybeAccessCode), + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None, + ff = ff + ), + createBackUrl(toSectionNumber, fastForward), None ) + } + case Some(FastForward.CYA(SectionOrSummary.Section(to))) => + fastForward.last match { + case FastForward.BackUntil(sn) if sectionNumber.compare(sn) <= 0 => + createBackUrl(to, fastForward).pure[Future] + case _ => + callSelector( + createBackUrl(to, fastForward), + createBackUrl(toSectionNumber, fastForward), + Some(to) + ) + } + case Some(FastForward.StopAt(sn)) => + sectionNumber.fold { classic => + createBackUrl(toSectionNumber, FastForward.StopAt(sectionNumber) :: fastForward).pure[Future] + } { taskList => + val maybePreviousPage = navigator.previousSectionNumber + if (maybePreviousPage.isEmpty) { + uk.gov.hmrc.gform.tasklist.routes.TaskListController + .landingPage(cache.formTemplateId, maybeAccessCode) + .pure[Future] } else { createBackUrl(toSectionNumber, fastForward).pure[Future] } - } + + } + case _ => + sectionNumber.fold { classic => + createBackUrl(toSectionNumber, fastForward).pure[Future] + } { taskList => + fastForward match { + case FastForward.CYA(SectionOrSummary.TaskSummary) :: xs => + xs.lastOption match { + case Some(FastForward.BackUntil(sn)) if sectionNumber.compare(sn) <= 0 => + Future.successful( + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None, + ff = fastForward.headOption + ) + ) + case _ => + navigator.previousSectionNumber.fold( + Future.successful( + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None, + ff = fastForward.headOption + ) + ) + )(previousSectionNumber => createBackUrl(previousSectionNumber, fastForward).pure[Future]) + } + case _ => + val maybePreviousPage = navigator.previousSectionNumber + + if (maybePreviousPage.isEmpty) { + callSelector( + uk.gov.hmrc.gform.tasklist.routes.TaskListController + .landingPage(cache.formTemplateId, maybeAccessCode), + uk.gov.hmrc.gform.tasklist.routes.TaskListController + .landingPage(cache.formTemplateId, maybeAccessCode), + None + ) + } else { + createBackUrl(toSectionNumber, fastForward).pure[Future] + } + } + } } + } + backCallF().map(Redirect(_)) } - } - backCallF().map(Redirect(_)) - } - val toSectionNumber = navigator.previousSectionNumber.getOrElse(sectionNumber) - val formModel = formModelOptics.formModelRenderPageOptics.formModel - val bracket = formModel.bracket(toSectionNumber) - - bracket match { - case Bracket.NonRepeatingPage(_, _, _) => goBack(toSectionNumber) - case Bracket.RepeatingPage(_, _) => goBack(toSectionNumber) - case bracket @ Bracket.AddToList(iterations, _) => - val iteration: Bracket.AddToListIteration[DataExpanded] = - bracket.iterationForSectionNumber(toSectionNumber) - val lastIteration: Bracket.AddToListIteration[DataExpanded] = iterations.last - if ( - iteration.repeater.sectionNumber === toSectionNumber && iteration.repeater.sectionNumber < lastIteration.repeater.sectionNumber - ) { - val isCommited = - formModelOptics.formModelVisibilityOptics.formModel.bracket(sectionNumber).withAddToListBracket { - addToListBracket => - addToListBracket.iterationForSectionNumber(sectionNumber).isCommited(cache.form.visitsIndex) + val toSectionNumber = navigator.previousSectionNumber.getOrElse(sectionNumber) + val formModel = formModelOptics.formModelRenderPageOptics.formModel + val bracket = formModel.bracket(toSectionNumber) + + bracket match { + case Bracket.NonRepeatingPage(_, _) => goBack(toSectionNumber) + case Bracket.RepeatingPage(_, _) => goBack(toSectionNumber) + case bracket @ Bracket.AddToList(defaultPage, iterations, _) => + val iteration: Bracket.AddToListIteration[DataExpanded] = + bracket.iterationForSectionNumber(toSectionNumber) + val lastIteration: Bracket.AddToListIteration[DataExpanded] = iterations.last + if ( + iteration.repeater.sectionNumber === toSectionNumber && iteration.repeater.sectionNumber < lastIteration.repeater.sectionNumber + ) { + val isCommited = + formModelOptics.formModelVisibilityOptics.formModel.bracket(sectionNumber).withAddToListBracket { + addToListBracket => + addToListBracket.iterationForSectionNumber(sectionNumber).isCommited(cache.form.visitsIndex) + } + if (isCommited) { + goBack(toSectionNumber) + } else { + for { + processData <- processDataService + .getProcessData[SectionSelectorType.Normal]( + formModelOptics.formModelRenderPageOptics.recData.variadicFormData + .asInstanceOf[VariadicFormData[OutOfDate]], + cache, + formModelOptics, + gformConnector.getAllTaxPeriods, + NoSpecificAction + ) + redirect <- formProcessor.processRemoveAddToList( + cache, + maybeAccessCode, + List(FastForward.Yes), + formModelOptics, + processData, + bracket.iterations.size - 1, + bracket.source.id + ) + } yield redirect + } + } else { + goBack(toSectionNumber) } - if (isCommited) { - goBack(toSectionNumber) - } else { - for { - processData <- processDataService - .getProcessData[SectionSelectorType.Normal]( - formModelOptics.formModelRenderPageOptics.recData.variadicFormData - .asInstanceOf[VariadicFormData[OutOfDate]], - cache, - formModelOptics, - gformConnector.getAllTaxPeriods, - NoSpecificAction - ) - redirect <- formProcessor.processRemoveAddToList( - cache, - maybeAccessCode, - List(FastForward.Yes), - formModelOptics, - processData, - bracket.iterations.size - 1, - bracket.source.id - ) - } yield redirect - } - } else { - goBack(toSectionNumber) } } + } def addToListAction( @@ -531,84 +543,102 @@ class FormController( ) = auth.authAndRetrieveForm[SectionSelectorType.Normal](formTemplateId, maybeAccessCode, OperationWithForm.EditForm) { implicit request => implicit l => cache => _ => formModelOptics => - val fastForward = removeDuplications(rawFastForward) - def processEditAddToList(processData: ProcessData, idx: Int, addToListId: AddToListId): Future[Result] = { - - val addToListIteration = processData.formModel.brackets.addToListById(addToListId, idx) - - def defaultNavigation(): (SectionNumber, List[FastForward]) = { - val firstAddToListPageSN: SectionNumber = processData.formModel.brackets - .addToListBracket(addToListId) - .source - .defaultPage - .fold(addToListIteration.firstSectionNumber) { _ => - addToListIteration.secondSectionNumber + sectionNumber match { + case SectionNumber.Legacy(_) => + // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost + redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode) + case _ => + val fastForward = removeDuplications(rawFastForward) + def processEditAddToList(processData: ProcessData, idx: Int, addToListId: AddToListId): Future[Result] = { + + val addToListIteration = + processData.formModelOptics.formModelVisibilityOptics.formModel.brackets.addToListById(addToListId, idx) + + def defaultNavigation(): (SectionNumber, List[FastForward]) = { + val sectionNumber = addToListIteration.firstSectionNumber + (sectionNumber, fastForward) } - val availableSectionNumbers = - processData.formModelOptics.formModelVisibilityOptics.formModel.brackets - .addToListById(addToListId, idx) - .allSingletonSectionNumbers + def checkYourAnswersNavigation( + cya: CheckYourAnswersWithNumber[Visibility] + ): (SectionNumber, List[FastForward]) = + (cya.sectionNumber, FastForward.CYA(SectionOrSummary.Section(sectionNumber)) :: fastForward) - val sectionNumber: SectionNumber = availableSectionNumbers.find(_ >= firstAddToListPageSN).head + val (gotoSectionNumber, ff) = + addToListIteration.checkYourAnswers.fold(defaultNavigation())(checkYourAnswersNavigation) - (sectionNumber, fastForward) - } - def checkYourAnswersNavigation( - cya: CheckYourAnswersWithNumber[DataExpanded] - ): (SectionNumber, List[FastForward]) = - (cya.sectionNumber, FastForward.CYA(SectionOrSummary.Section(sectionNumber)) :: fastForward) - val (gotoSectionNumber, ff) = - addToListIteration.checkYourAnswers.fold(defaultNavigation())(checkYourAnswersNavigation) - val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) - Redirect( - routes.FormController - .form( - cache.formTemplateId, - maybeAccessCode, - gotoSectionNumber, - sectionTitle4Ga, - SuppressErrors.Yes, - ff - ) - .url - ).pure[Future] + val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) + + Redirect( + routes.FormController + .form( + cache.formTemplateId, + maybeAccessCode, + gotoSectionNumber, + sectionTitle4Ga, + SuppressErrors.Yes, + ff + ) + .url + ).pure[Future] + } + + for { + processData <- processDataService + .getProcessData[SectionSelectorType.Normal]( + formModelOptics.formModelVisibilityOptics.recData.variadicFormData + .asInstanceOf[VariadicFormData[OutOfDate]], + cache, + formModelOptics, + gformConnector.getAllTaxPeriods, + NoSpecificAction + ) + res <- direction match { + case EditAddToList(idx, addToListId) => processEditAddToList(processData, idx, addToListId) + case SaveAndContinue | SaveAndExit => + // This request should have been a POST, with user data. However we have sporadically seen GET requests sent instead of POST to this endpoint + // the cause of which is not known yet. We redirect the user the page he/she is currently on, instead of throwing an error page + // e.g: GET /submissions/form/XXXX/-/0?ff=t&action=SaveAndContinue + logger.warn(s"Received GET request with direction $direction. Doing a redirect!") + val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) + Redirect( + routes.FormController + .form( + cache.formTemplateId, + maybeAccessCode, + sectionNumber, + sectionTitle4Ga, + SuppressErrors.Yes, + fastForward + ) + ).pure[Future] + case _ => throw new IllegalArgumentException(s"Direction $direction is not supported here") + } + } yield res } - for { - processData <- processDataService - .getProcessData[SectionSelectorType.Normal]( - formModelOptics.formModelVisibilityOptics.recData.variadicFormData - .asInstanceOf[VariadicFormData[OutOfDate]], - cache, - formModelOptics, - gformConnector.getAllTaxPeriods, - NoSpecificAction - ) - res <- direction match { - case EditAddToList(idx, addToListId) => processEditAddToList(processData, idx, addToListId) - case SaveAndContinue | SaveAndExit => - // This request should have been a POST, with user data. However we have sporadically seen GET requests sent instead of POST to this endpoint - // the cause of which is not known yet. We redirect the user the page he/she is currently on, instead of throwing an error page - // e.g: GET /submissions/form/XXXX/-/0?ff=t&action=SaveAndContinue - logger.warn(s"Received GET request with direction $direction. Doing a redirect!") - val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) - Redirect( - routes.FormController - .form( - cache.formTemplateId, - maybeAccessCode, - sectionNumber, - sectionTitle4Ga, - SuppressErrors.Yes, - fastForward - ) - ).pure[Future] - case _ => throw new IllegalArgumentException(s"Direction $direction is not supported here") - } - } yield res } + private def redirectOnLegacy( + formModelOptics: FormModelOptics[DataOrigin.Mongo], + formTemplateId: FormTemplateId, + maybeAccessCode: Option[AccessCode] + ) = { + val sn = + formModelOptics.formModelVisibilityOptics.formModel.availableSectionNumbers.head // In production this should always return some section + Redirect( + routes.FormController + .form( + formTemplateId, + maybeAccessCode, + sn, + SectionTitle4Ga("welcome"), + SuppressErrors.Yes, + List(FastForward.Yes) + ) + ).pure[Future] + } + def updateFormData( formTemplateId: FormTemplateId, maybeAccessCode: Option[AccessCode], @@ -620,295 +650,318 @@ class FormController( implicit request => implicit l => cache => implicit sse => formModelOptics => val formModel = formModelOptics.formModelVisibilityOptics.formModel val fastForward = filterFastForward(browserSectionNumber, rawFastForward, formModel) - processResponseDataFromBody(request, formModelOptics.formModelRenderPageOptics, Some(browserSectionNumber)) { - requestRelatedData => variadicFormData => enteredVariadicFormData => - val sectionNumber: SectionNumber = - formModelOptics.formModelVisibilityOptics.formModel.visibleSectionNumber(browserSectionNumber) + browserSectionNumber match { + case SectionNumber.Legacy(_) => + // Redirect users on first page of the form if they are currently submitting some page data. Current page data are lost + redirectOnLegacy(formModelOptics, cache.formTemplateId, maybeAccessCode) + case _ => + processResponseDataFromBody( + request, + formModelOptics.formModelRenderPageOptics, + Some(browserSectionNumber) + ) { requestRelatedData => variadicFormData => enteredVariadicFormData => + val sectionNumber: SectionNumber = + formModelOptics.formModelVisibilityOptics.formModel.visibleSectionNumber(browserSectionNumber) - def processSaveAndContinue( - processData: ProcessData - ): Future[Result] = - confirmationService.processConfirmation( - sectionNumber, - processData, - cache.formTemplateId, - maybeAccessCode, - formModelOptics, - fastForward - ) match { - case ConfirmationAction.NotConfirmed(redirect) => redirect.pure[Future] - case ConfirmationAction.UpdateConfirmation(processDataUpdater, isConfirmationPage) => - val processDataUpd = processDataUpdater(processData) - formProcessor.validateAndUpdateData( - cache, - processDataUpd, - sectionNumber, - sectionNumber, - maybeAccessCode, - fastForward, - formModelOptics, - enteredVariadicFormData, - true - ) { updatePostcodeLookup => maybeRedirectUrl => maybeSn => - def processRemoveItemIf() = { - val formModel = formModelOptics.formModelRenderPageOptics.formModel - val bracket = formModel.bracket(sectionNumber) - - bracket match { - case bracket @ Bracket.AddToList(_, _) => - val iteration = bracket.iterationForSectionNumber(sectionNumber) - val sectionNumbers = iteration.allSingletonSectionNumbers - - sectionNumbers.flatMap { sectionNumber => - maybeAtlRemoveIteration(bracket, sectionNumber) - }.headOption - case _ => None + def processSaveAndContinue( + processData: ProcessData + ): Future[Result] = + confirmationService.processConfirmation( + sectionNumber, + processData, + cache.formTemplateId, + maybeAccessCode, + formModelOptics, + fastForward + ) match { + case ConfirmationAction.NotConfirmed(redirect) => redirect.pure[Future] + case ConfirmationAction.UpdateConfirmation(processDataUpdater, isConfirmationPage) => + val processDataUpd = processDataUpdater(processData) + formProcessor.validateAndUpdateData( + cache, + processDataUpd, + sectionNumber, + sectionNumber, + maybeAccessCode, + fastForward, + formModelOptics, + enteredVariadicFormData, + true + ) { updatePostcodeLookup => maybeRedirectUrl => maybeSn => + def processRemoveItemIf() = { + val formModel = formModelOptics.formModelRenderPageOptics.formModel + val bracket = formModel.bracket(sectionNumber) + + bracket match { + case bracket @ Bracket.AddToList(Some(defaultPage), _, _) + if defaultPage.sectionNumber === sectionNumber => + None + case bracket @ Bracket.AddToList(_, _, _) => + val iteration = bracket.iterationForSectionNumber(sectionNumber) + val sectionNumbers = iteration.allSingletonSectionNumbers + + sectionNumbers.flatMap { sectionNumber => + maybeAtlRemoveIteration(bracket, sectionNumber) + }.headOption + case _ => None + } } - } - def maybeAtlRemoveIteration(bracket: Bracket[DataExpanded], sectionNumber: SectionNumber) = - bracket - .atlIterationToRemove( - sectionNumber, - processDataUpd.formModelOptics.formModelVisibilityOptics - ) - .map { case (atlId, index) => - Redirect( - routes.FormAddToListController - .removeItem( - formTemplateId, - maybeAccessCode, + def maybeAtlRemoveIteration( + bracket: Bracket[DataExpanded], + sectionNumber: SectionNumber + ): Option[Result] = + sectionNumber match { + case SectionNumber.Classic.AddToListPage.Page(_, _, _) => + bracket + .atlIterationToRemove( sectionNumber, - index, - atlId + processDataUpd.formModelOptics.formModelVisibilityOptics ) - ) + .map { case (atlId, index) => + Redirect( + routes.FormAddToListController + .removeItem( + formTemplateId, + maybeAccessCode, + sectionNumber, + index, + atlId + ) + ) + } + case _ => None } - def continueJourney = - maybeSn match { - case SectionOrSummary.Section(sn) => - val endOfTask: Option[Coordinates] = - sn.fold[Option[Coordinates]] { classic => - None - } { taskList => - val cur = sectionNumber.unsafeToTaskList.coordinates - if (taskList.coordinates === cur) { + def continueJourney: Result = + maybeSn match { + case SectionOrSummary.Section(sn) => + val endOfTask: Option[Coordinates] = + sn.fold[Option[Coordinates]] { classic => None - } else { - Some(cur) + } { taskList => + val cur = sectionNumber.unsafeToTaskList.coordinates + if (taskList.coordinates === cur) { + None + } else { + Some(cur) + } } - } - if (endOfTask.isDefined) { - Redirect( - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - endOfTask, - None, - ff = fastForward.headOption - ) - ) - } else { - val isFirstLanding = sectionNumber < sn - val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processDataUpd, sn) + if (endOfTask.isDefined) { + Redirect( + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + endOfTask, + None, + ff = fastForward.headOption + ) + ) + } else { + val isFirstLanding = sectionNumber < sn + val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processDataUpd, sn) - val formModel = formModelOptics.formModelRenderPageOptics.formModel - val bracket = formModel.bracket(sectionNumber) + val formModel = formModelOptics.formModelRenderPageOptics.formModel + val bracket = formModel.bracket(sectionNumber) - val isLastBracketIteration = bracket match { - case bracket @ Bracket.AddToList(_, _) => - val iteration = bracket.iterationForSectionNumber(sectionNumber) - sectionNumber === iteration.lastSectionNumber - case _ => false - } + val isLastBracketIteration = bracket match { + case bracket @ Bracket.AddToList(Some(defaultPage), _, _) + if defaultPage.sectionNumber === sectionNumber => + false + case bracket @ Bracket.AddToList(_, _, _) => + val iteration = bracket.iterationForSectionNumber(sectionNumber) + sectionNumber === iteration.lastSectionNumber + case _ => false + } - maybeAtlRemoveIteration(bracket, sectionNumber) - .getOrElse( - Redirect( - routes.FormController - .form( - cache.formTemplateId, - maybeAccessCode, - sn, - sectionTitle4Ga, - SuppressErrors(isFirstLanding), - if (isLastBracketIteration) fastForward - else if (isFirstLanding || sectionNumber.isTaskList) { - fastForward match { - case Nil => Nil - case x :: FastForward.StopAt(s) :: xs - if formModel.availableSectionNumbers.contains(s) => - FastForward.StopAt(s) :: xs - case x :: xs => - x - .next( + maybeAtlRemoveIteration(bracket, sectionNumber) + .getOrElse( + Redirect( + routes.FormController + .form( + cache.formTemplateId, + maybeAccessCode, + sn, + sectionTitle4Ga, + SuppressErrors(isFirstLanding), + if (isLastBracketIteration) fastForward + else if (isFirstLanding || sectionNumber.isTaskList) { + fastForward match { + case Nil => + Nil + case x :: FastForward.StopAt(s) :: xs + if formModel.availableSectionNumbers.contains(s) => + FastForward.StopAt(s) :: xs + case x :: xs => + x.next( processDataUpd.formModelOptics.formModelVisibilityOptics.formModel, sn ) :: xs - } - } else - fastForward - ) + } + } else + fastForward + ) + ) ) + } + case SectionOrSummary.FormSummary => + processRemoveItemIf().getOrElse( + Redirect( + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None, + true + ) ) - } - case SectionOrSummary.FormSummary => - processRemoveItemIf().getOrElse( - Redirect( - routes.SummaryController - .summaryById( - cache.formTemplateId, - maybeAccessCode, - sectionNumber.toCoordinates, - None, - true - ) ) - ) - case SectionOrSummary.TaskSummary => - processRemoveItemIf().getOrElse( + case SectionOrSummary.TaskSummary => + processRemoveItemIf().getOrElse( + Redirect( + routes.SummaryController + .summaryById( + cache.formTemplateId, + maybeAccessCode, + sectionNumber.maybeCoordinates, + None + ) + ) + ) + } + + maybeRedirectUrl match { + case Some(r) => Redirect(r) + case None => + updatePostcodeLookup.fold(continueJourney) { case (formComponentId, _) => Redirect( - routes.SummaryController - .summaryById( - cache.formTemplateId, + uk.gov.hmrc.gform.addresslookup.routes.AddressLookupController + .chooseAddress( + cache.formTemplate._id, maybeAccessCode, - sectionNumber.toCoordinates, - None + formComponentId, + sectionNumber, + fastForward ) ) - ) + } } - - maybeRedirectUrl match { - case Some(r) => Redirect(r) - case None => - updatePostcodeLookup.fold(continueJourney) { case (formComponentId, _) => - Redirect( - uk.gov.hmrc.gform.addresslookup.routes.AddressLookupController - .chooseAddress( - cache.formTemplate._id, - maybeAccessCode, - formComponentId, - sectionNumber, - fastForward - ) - ) - } } - } - } + } - def handleGroup(cacheUpd: AuthCacheWithForm, processData: ProcessData, anchor: String): Future[Result] = - formProcessor.validateAndUpdateData( - cacheUpd, - processData, - sectionNumber, - sectionNumber, - maybeAccessCode, - fastForward, - formModelOptics, - enteredVariadicFormData, - true - ) { _ => _ => _ => - val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) - Redirect( - routes.FormController - .form( - cache.formTemplateId, - maybeAccessCode, - sectionNumber, - sectionTitle4Ga, - SuppressErrors.Yes, - List(FastForward.Yes) - ) - .url + anchor - ) - } + def handleGroup(cacheUpd: AuthCacheWithForm, processData: ProcessData, anchor: String): Future[Result] = + formProcessor.validateAndUpdateData( + cacheUpd, + processData, + sectionNumber, + sectionNumber, + maybeAccessCode, + fastForward, + formModelOptics, + enteredVariadicFormData, + true + ) { _ => _ => _ => + val sectionTitle4Ga = formProcessor.getSectionTitle4Ga(processData, sectionNumber) + Redirect( + routes.FormController + .form( + cache.formTemplateId, + maybeAccessCode, + sectionNumber, + sectionTitle4Ga, + SuppressErrors.Yes, + List(FastForward.Yes) + ) + .url + anchor + ) + } - def processAddGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = { + def processAddGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = { - val incremented = modelComponentId.increment + val incremented = modelComponentId.increment - def anchor(formModelOptics: FormModelOptics[DataOrigin.Browser]): Option[HtmlFieldId] = { - val childs: List[FormComponent] = - formModelOptics.formModelVisibilityOptics.fcLookup - .get(incremented.toFormComponentId) - .toList - .flatMap(_.childrenFormComponents) + def anchor(formModelOptics: FormModelOptics[DataOrigin.Browser]): Option[HtmlFieldId] = { + val childs: List[FormComponent] = + formModelOptics.formModelVisibilityOptics.fcLookup + .get(incremented.toFormComponentId) + .toList + .flatMap(_.childrenFormComponents) - childs - .dropWhile { - case IsInformationMessage(_) => true - case _ => false - } - .headOption - .map { - case fc @ IsChoice(_) => HtmlFieldId.indexed(fc.id, "0") - case fc @ IsRevealingChoice(_) => HtmlFieldId.indexed(fc.id, "0") - case fc => - HtmlFieldId.pure(fc.multiValueId.fold[ModelComponentId](_.modelComponentId)(_.atoms.head)) - } - } + childs + .dropWhile { + case IsInformationMessage(_) => true + case _ => false + } + .headOption + .map { + case fc @ IsChoice(_) => HtmlFieldId.indexed(fc.id, "0") + case fc @ IsRevealingChoice(_) => HtmlFieldId.indexed(fc.id, "0") + case fc => + HtmlFieldId.pure(fc.multiValueId.fold[ModelComponentId](_.modelComponentId)(_.atoms.head)) + } + } - val variadicFormData = processData.formModelOptics.pageOpticsData - val updatedVariadicFormData = variadicFormData.addOne(incremented -> "") - for { - updFormModelOptics <- FormModelOptics - .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal]( - updatedVariadicFormData - .asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]], - cache, - recalculation - ) - res <- handleGroup( - cache, - processData.copy(formModelOptics = updFormModelOptics), - anchor(updFormModelOptics).map("#" + _.toHtmlId).getOrElse("") - ) - } yield res + val variadicFormData = processData.formModelOptics.pageOpticsData + val updatedVariadicFormData = variadicFormData.addOne(incremented -> "") + for { + updFormModelOptics <- FormModelOptics + .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal]( + updatedVariadicFormData + .asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]], + cache, + recalculation + ) + res <- handleGroup( + cache, + processData.copy(formModelOptics = updFormModelOptics), + anchor(updFormModelOptics).map("#" + _.toHtmlId).getOrElse("") + ) + } yield res - } + } + + def processRemoveGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = { + val (updData, componentIdToFileId, filesToDelete) = + GroupUtils.removeRecord(processData, modelComponentId, sectionNumber, cache.form.componentIdToFileId) + val cacheUpd = cache.copy(form = cache.form.copy(componentIdToFileId = componentIdToFileId)) + for { + updFormModelOptics <- FormModelOptics + .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal]( + updData.asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]], + cache, + recalculation + ) + res <- handleGroup(cacheUpd, processData.copy(formModelOptics = updFormModelOptics), "") + _ <- objectStoreAlgebra.deleteFiles(cache.form.envelopeId, filesToDelete) + } yield res + } - def processRemoveGroup(processData: ProcessData, modelComponentId: ModelComponentId): Future[Result] = { - val (updData, componentIdToFileId, filesToDelete) = - GroupUtils.removeRecord(processData, modelComponentId, sectionNumber, cache.form.componentIdToFileId) - val cacheUpd = cache.copy(form = cache.form.copy(componentIdToFileId = componentIdToFileId)) for { - updFormModelOptics <- FormModelOptics - .mkFormModelOptics[DataOrigin.Browser, Future, SectionSelectorType.Normal]( - updData.asInstanceOf[VariadicFormData[SourceOrigin.OutOfDate]], - cache, - recalculation - ) - res <- handleGroup(cacheUpd, processData.copy(formModelOptics = updFormModelOptics), "") - _ <- objectStoreAlgebra.deleteFiles(cache.form.envelopeId, filesToDelete) + processData <- processDataService + .getProcessData[SectionSelectorType.Normal]( + variadicFormData, + cache, + formModelOptics, + gformConnector.getAllTaxPeriods, + NoSpecificAction + ) + res <- save match { + case SaveAndContinue => processSaveAndContinue(processData) + case SaveAndExit => + Redirect( + gform.routes.SaveAcknowledgementController + .saveAndExit(formTemplateId, maybeAccessCode, browserSectionNumber, fastForward) + ).pure[Future] + case AddGroup(modelComponentId) => processAddGroup(processData, modelComponentId) + case RemoveGroup(modelComponentId) => processRemoveGroup(processData, modelComponentId) + case _ => throw new IllegalArgumentException(s"Direction $save is not supported here") + } } yield res } - - for { - processData <- processDataService - .getProcessData[SectionSelectorType.Normal]( - variadicFormData, - cache, - formModelOptics, - gformConnector.getAllTaxPeriods, - NoSpecificAction - ) - res <- save match { - case SaveAndContinue => processSaveAndContinue(processData) - case SaveAndExit => - Redirect( - gform.routes.SaveAcknowledgementController - .saveAndExit(formTemplateId, maybeAccessCode, browserSectionNumber, fastForward) - ).pure[Future] - case AddGroup(modelComponentId) => processAddGroup(processData, modelComponentId) - case RemoveGroup(modelComponentId) => processRemoveGroup(processData, modelComponentId) - case _ => throw new IllegalArgumentException(s"Direction $save is not supported here") - } - } yield res } + } private val formMaxAttachmentSizeMB = appConfig.formMaxAttachmentSizeMB diff --git a/app/uk/gov/hmrc/gform/gform/ImageController.scala b/app/uk/gov/hmrc/gform/gform/ImageController.scala index 97b98f86d..94c3bbd26 100644 --- a/app/uk/gov/hmrc/gform/gform/ImageController.scala +++ b/app/uk/gov/hmrc/gform/gform/ImageController.scala @@ -49,7 +49,7 @@ class ImageController( OperationWithForm.ViewImageByInternalLink ) { _ => _ => _ => _ => formModelOptics => val formModel = formModelOptics.formModelRenderPageOptics.formModel - val allExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel)) + val allExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel)) Future.successful { if (!allExprs.contains(LinkCtx(InternalLink.Image(fileName)))) { diff --git a/app/uk/gov/hmrc/gform/gform/NewFormController.scala b/app/uk/gov/hmrc/gform/gform/NewFormController.scala index d59a4a2ea..bc94e0b2a 100644 --- a/app/uk/gov/hmrc/gform/gform/NewFormController.scala +++ b/app/uk/gov/hmrc/gform/gform/NewFormController.scala @@ -592,7 +592,7 @@ class NewFormController( def formHasAuthItmpReferences(): Boolean = { val formModel = formModelOptics.formModelRenderPageOptics.formModel - val allBracketExprs = formModel.brackets.toBracketsPlains.toList.flatMap(_.allExprs(formModel)) + val allBracketExprs = formModel.brackets.toBrackets.toList.flatMap(_.allExprs(formModel)) val allCustomExprs = cache.formTemplateContext.formTemplate.formKind.allCustomExprs val expressionsOutExprs = cache.formTemplateContext.formTemplate.expressionsOutput.fold(List.empty[Expr])(_.lookup.values.toList) @@ -611,7 +611,7 @@ class NewFormController( val formModel = formModelOptics.formModelRenderPageOptics.formModel val modelComponentIds = formModel.confirmationPageMap.flatMap { case (sectionNumber, confirmation) => val allBracketExprs = - formModel.brackets.withSectionNumber(sectionNumber).toPlainBracket.allExprs(formModel) + formModel.brackets.withSectionNumber(sectionNumber).allExprs(formModel) if (cacheItmpRetrievals.flatMap(_.itmpName) != itmpRetrievals.itmpName && hasItmpNameExpr(allBracketExprs)) { Some(confirmation.question.id.modelComponentId) diff --git a/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala b/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala index 21cc5e6c5..a2f80a423 100644 --- a/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala +++ b/app/uk/gov/hmrc/gform/gform/SectionRenderingService.scala @@ -56,6 +56,7 @@ import uk.gov.hmrc.gform.ops.FormComponentOps import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionTitle4Ga.sectionTitle4GaFactory import uk.gov.hmrc.gform.sharedmodel.formtemplate.destinations.Destinations._ import uk.gov.hmrc.gform.summary.{ AddToListCYARender, AddressRecordLookup, FormComponentRenderDetails, FormComponentSummaryRenderer, SummaryRender } +import uk.gov.hmrc.gform.tasklist.TaskListUtils import uk.gov.hmrc.gform.upscan.{ FormMetaData, UpscanData, UpscanInitiate } import uk.gov.hmrc.gform.validation.HtmlFieldId import uk.gov.hmrc.gform.validation._ @@ -91,7 +92,6 @@ import uk.gov.hmrc.hmrcfrontend.views.html.components.HmrcCharacterCount import uk.gov.hmrc.gform.views.summary.SummaryListRowHelper import uk.gov.hmrc.govukfrontend.views.html.components.{ GovukCharacterCount, GovukInput, GovukSummaryList, GovukTable } import MiniSummaryRow._ -import uk.gov.hmrc.gform.tasklist.TaskListUtils import uk.gov.hmrc.auth.core.ConfidenceLevel import uk.gov.hmrc.gform.sharedmodel.formtemplate.KeyDisplayWidth.KeyDisplayWidth import uk.gov.hmrc.gform.models.helpers.MiniSummaryListHelper @@ -219,10 +219,11 @@ class SectionRenderingService( SectionRenderingService.atlCyaTitles(cache, sectionNumber, checkYourAnswers, formModelOptics) val ff = fastForward match { - case Nil => Nil - case FastForward.CYA(to) :: xs => FastForward.CYA(to) :: xs - case FastForward.StopAt(sectionNumber) :: xs => FastForward.StopAt(sectionNumber.increment) :: xs - case otherwise => otherwise + case Nil => Nil + case FastForward.CYA(to) :: xs => FastForward.CYA(to) :: xs + case FastForward.StopAt(sectionNumber) :: xs => + FastForward.StopAt(sectionNumber.increment(formModelOptics.formModelVisibilityOptics.formModel)) :: xs + case otherwise => otherwise } html.form.addToListCheckYourAnswers( title, @@ -751,33 +752,58 @@ class SectionRenderingService( } val classicPages: List[(PageModel[DataExpanded], SectionNumber.Classic)] = - pages.toList.collect { case (pageModel, c @ SectionNumber.Classic(_)) => + pages.toList.collect { case (pageModel, c: SectionNumber.Classic) => pageModel -> c } + + val specimenLinks: SpecimenLinks = SpecimenLinks.from(classicPages.map(_._2), classic) + specimen.navigation( formTemplate, classic, classicPages, + specimenLinks, maybeIncludeIf ) } { taskList => + val coordinates: Coordinates = taskList.coordinates + val classic: SectionNumber.Classic = taskList.sectionNumber + val pages: NonEmptyList[(PageModel[DataExpanded], SectionNumber)] = formModelRenderPageOptics.formModel.pagesWithIndex + val allSectionNumbers: List[SectionNumber] = + pages.map(_._2).filter(_.maybeCoordinates.exists(_.taskSectionNumber === coordinates.taskSectionNumber)) + + val distinctCoordinates: List[Coordinates] = allSectionNumbers.flatMap(_.maybeCoordinates).distinct + + val isFirstSectionNumber: Set[SectionNumber] = distinctCoordinates.flatMap { coordinates => + allSectionNumbers.find(_.maybeCoordinates.contains(coordinates)) + }.toSet + + val firstSectionNumbers: List[SectionNumber] = allSectionNumbers.filter(isFirstSectionNumber) + val taskListPages: List[(PageModel[DataExpanded], SectionNumber.TaskList)] = pages.toList.collect { case (pageModel, c @ SectionNumber.TaskList(coordinates, _)) if coordinates === taskList.coordinates => pageModel -> c } - val tasks = TaskListUtils.withTaskSection(formTemplate, taskList.coordinates.taskSectionNumber)(section => - section.tasks.toList - ) + val tasks: List[uk.gov.hmrc.gform.sharedmodel.formtemplate.Task] = + TaskListUtils.withTaskSection(formTemplate, taskList.coordinates.taskSectionNumber)(section => + section.tasks.toList + ) + + val tasksWithFirstSectionNumber = tasks.zip(firstSectionNumbers) + + val specimenLinks: SpecimenLinks = SpecimenLinks.from(taskListPages.map(_._2.sectionNumber), classic) + specimen.navigation_tasklist( formTemplate, taskList, taskListPages, - tasks + tasksWithFirstSectionNumber, + specimenLinks ) } } else HtmlFormat.empty diff --git a/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala b/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala new file mode 100644 index 000000000..43e81075f --- /dev/null +++ b/app/uk/gov/hmrc/gform/gform/SpecimenLinks.scala @@ -0,0 +1,31 @@ +/* + * 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 uk.gov.hmrc.gform.gform + +import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionNumber + +final case class SpecimenLinks(previous: Option[SectionNumber.Classic], next: Option[SectionNumber.Classic]) + +object SpecimenLinks { + def from(sectionNumbers: List[SectionNumber.Classic], currentSectionNumber: SectionNumber.Classic): SpecimenLinks = { + + val previous: Option[SectionNumber.Classic] = sectionNumbers.filter(_ < currentSectionNumber).maxOption + val next: Option[SectionNumber.Classic] = sectionNumbers.filter(_ > currentSectionNumber).minOption + + SpecimenLinks(previous, next) + } +} diff --git a/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala b/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala index a6428e787..09c658c3a 100644 --- a/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala +++ b/app/uk/gov/hmrc/gform/gform/handlers/FormValidator.scala @@ -137,7 +137,7 @@ class FormValidator(implicit ec: ExecutionContext) { currentSectionNumber: Option[SectionNumber], formModelOptics: FormModelOptics[DataOrigin.Browser] ): List[SectionNumber] = { - val maybeCoordinates = currentSectionNumber.flatMap(_.toCoordinates) + val maybeCoordinates = currentSectionNumber.flatMap(_.maybeCoordinates) val availableSectionNumbers: List[SectionNumber] = Origin( formModelOptics.formModelVisibilityOptics.formModel @@ -157,7 +157,7 @@ class FormValidator(implicit ec: ExecutionContext) { maybeSectionNumber: Option[SectionNumber] ): Future[SectionOrSummary] = { - val maybeCoordinates = maybeSectionNumber.flatMap(_.toCoordinates) + val maybeCoordinates = maybeSectionNumber.flatMap(_.maybeCoordinates) val formModelOptics: FormModelOptics[DataOrigin.Browser] = processData.formModelOptics def atlHasSectionNumber(sectionNumber: SectionNumber): Boolean = @@ -190,7 +190,7 @@ class FormValidator(implicit ec: ExecutionContext) { maybeSectionNumber ) - lazy val nextFrom = for { + val nextFrom = for { sectionNumber <- maybeSectionNumber next <- availableSectionNumbers.find(_ > sectionNumber) } yield next diff --git a/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala b/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala index b3cc63448..e7456dd25 100644 --- a/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala +++ b/app/uk/gov/hmrc/gform/gform/processor/FormProcessor.scala @@ -117,9 +117,6 @@ class FormProcessor( val isLastIteration = addToListBracket.iterations.size === 1 - val visitsIndex: VisitIndex = VisitIndex - .updateSectionVisits(updFormModel, processData.formModel, processData.visitsIndex) - val visitsIndexUpd = if (isLastIteration) { val iterationForSectionNumber: Bracket.AddToListIteration[DataExpanded] = addToListBracket @@ -129,8 +126,8 @@ class FormProcessor( .map(_.sectionNumber) .toList ++ iterationForSectionNumber.checkYourAnswers.map(_.sectionNumber) - visitsIndex.fold[VisitIndex] { classic => - val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToClassic.sectionNumber) + processData.visitsIndex.fold[VisitIndex] { classic => + val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToClassic) VisitIndex.Classic( classic.visitsIndex -- toBeRemoved ) @@ -138,7 +135,7 @@ class FormProcessor( val toBeRemoved = visitsIndexForLastIteration.map(_.unsafeToTaskList.sectionNumber) val coordinates = sn.toCoordinatesUnsafe - val indexes = + val indexes: Set[SectionNumber.Classic] = taskList.visitsIndex .getOrElse(coordinates, throw new Exception(s"No VisitIndex found for coordinates $coordinates")) @@ -149,7 +146,7 @@ class FormProcessor( ) } } else - visitsIndex + processData.visitsIndex val processDataUpd = processData.copy( formModelOptics = updFormModelOptics, @@ -391,15 +388,16 @@ class FormProcessor( gformConnector.getAllTaxPeriods, NoSpecificAction ) - res <- fastForwardService - .updateUserData( - cacheUpd, - newProcessData, - maybeAccessCode, - fastForward, - envelopeWithMapping, - Some(sectionNumber) - )((a, b) => toResult(updatePostcodeLookup)(redirectUrl.map(_.value()))(a)) + res <- + fastForwardService + .updateUserData( + cacheUpd, + newProcessData, + maybeAccessCode, + fastForward, + envelopeWithMapping, + Some(sectionNumber) + )((sectionOrSummary, _) => toResult(updatePostcodeLookup)(redirectUrl.map(_.value()))(sectionOrSummary)) } yield res } } yield res diff --git a/app/uk/gov/hmrc/gform/models/AllSections.scala b/app/uk/gov/hmrc/gform/models/AllSections.scala index 0532b9c94..ad7f972a3 100644 --- a/app/uk/gov/hmrc/gform/models/AllSections.scala +++ b/app/uk/gov/hmrc/gform/models/AllSections.scala @@ -17,11 +17,20 @@ package uk.gov.hmrc.gform.models import cats.data.NonEmptyList -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, Section, TaskNumber, TaskSectionNumber } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, Section, TaskNumber, TaskSectionNumber, TemplateSectionIndex } + +sealed trait IndexedSection { + def section: Section // Section which knows its position in json template +} + +object IndexedSection { + case class SectionIndex(section: Section, index: TemplateSectionIndex) extends IndexedSection + case class SectionNoIndex(section: Section.NonRepeatingPage) extends IndexedSection +} sealed trait AllSections extends Product with Serializable { - def sections: List[Section] + def sections: List[IndexedSection] def fold[B](f: AllSections.Classic => B)(g: AllSections.TaskList => B): B = this match { @@ -29,9 +38,11 @@ sealed trait AllSections extends Product with Serializable { case r: AllSections.TaskList => g(r) } - def mapSection[A <: PageMode](f: Section => Option[BracketPlain[A]]): BracketPlainCoordinated[A] = + def mapSection[A <: PageMode]( + f: Option[Coordinates] => IndexedSection => Option[Bracket[A]] + ): BracketPlainCoordinated[A] = fold[BracketPlainCoordinated[A]] { classic => - val xs: List[BracketPlain[A]] = classic.sections.map(f).collect { case Some(bracket) => + val xs: List[Bracket[A]] = classic.sections.map(f(None)).collect { case Some(bracket) => bracket } NonEmptyList @@ -40,37 +51,44 @@ sealed trait AllSections extends Product with Serializable { BracketPlainCoordinated.Classic(_) } } { taskList => - val xs: NonEmptyList[(Coordinates, TaskModelCoordinated[A])] = taskList.coordSections.map { - case (coordinates, sections) => - val xs: List[BracketPlain[A]] = sections.map(f).collect { case Some(bracket) => - bracket - } + val xs: NonEmptyList[(Coordinates, TaskModel[A])] = taskList.coordSections.map { case (coordinates, sections) => + val xs: List[Bracket[A]] = sections.map(f(Some(coordinates))).collect { case Some(bracket) => + bracket + } - val taskModelCoordinated: TaskModelCoordinated[A] = NonEmptyList - .fromList(xs) - .fold(TaskModelCoordinated.allHidden[A])(brackets => TaskModelCoordinated.editable[A](brackets)) + val taskModelCoordinated: TaskModel[A] = NonEmptyList + .fromList(xs) + .fold(TaskModel.allHidden[A])(brackets => TaskModel.editable[A](brackets)) - coordinates -> taskModelCoordinated + coordinates -> taskModelCoordinated } BracketPlainCoordinated.TaskList(xs) } - def +(others: List[Section]) = fold[AllSections](_.copy(others = others))(_.copy(others = others)) + def +(others: List[Section.NonRepeatingPage]) = fold[AllSections](_.copy(others = others))(_.copy(others = others)) } object AllSections { - case class Classic(sections0: List[Section], others: List[Section] = Nil) extends AllSections { - val sections = sections0 ++ others - } - case class TaskList(sections0: NonEmptyList[(Coordinates, List[Section])], others: List[Section] = Nil) + case class Classic(sections0: List[IndexedSection], others: List[Section.NonRepeatingPage] = Nil) extends AllSections { - val coordSections: NonEmptyList[(Coordinates, List[Section])] = { + val sections = sections0 ++ others.map(section => IndexedSection.SectionNoIndex(section)) + } + case class TaskList( + sections0: NonEmptyList[(Coordinates, List[IndexedSection])], + others: List[Section.NonRepeatingPage] = Nil + ) extends AllSections { + val coordSections: NonEmptyList[(Coordinates, List[IndexedSection])] = { if (others.isEmpty) { sections0 } else - sections0.append((Coordinates(TaskSectionNumber(999999), TaskNumber(999999)), others)) + sections0.append( + ( + Coordinates(TaskSectionNumber(999999), TaskNumber(999999)), + others.map(section => IndexedSection.SectionNoIndex(section)) + ) + ) } - val sections = sections0.toList.flatMap(_._2) ++ others + val sections = sections0.toList.flatMap(_._2) ++ others.map(section => IndexedSection.SectionNoIndex(section)) } } diff --git a/app/uk/gov/hmrc/gform/models/Bracket.scala b/app/uk/gov/hmrc/gform/models/Bracket.scala index 07ce457e4..15cd8833d 100644 --- a/app/uk/gov/hmrc/gform/models/Bracket.scala +++ b/app/uk/gov/hmrc/gform/models/Bracket.scala @@ -20,11 +20,16 @@ import cats.data.NonEmptyList import cats.syntax.eq._ import uk.gov.hmrc.gform.models.optics.{ DataOrigin, FormModelVisibilityOptics } import uk.gov.hmrc.gform.sharedmodel.form.VisitIndex -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ AddToListId, Section, SectionNumber } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ AddToListId, Expr, Section, SectionNumber } +import uk.gov.hmrc.gform.eval.AllPageModelExpressionsGetter sealed trait Bracket[A <: PageMode] extends Product with Serializable { - def toPlainBracket: BracketPlain[A] + + def allExprs(formModel: FormModel[DataExpanded]): List[Expr] = + AllPageModelExpressionsGetter.allExprs(formModel)(this) + def hasSectionNumber(sectionNumber: SectionNumber): Boolean + def map[B <: PageMode]( e: Singleton[A] => Singleton[B], f: CheckYourAnswers[A] => CheckYourAnswers[B], @@ -44,28 +49,33 @@ sealed trait Bracket[A <: PageMode] extends Product with Serializable { } def isToListById(addToListId: AddToListId): Boolean = this match { - case Bracket.AddToList(_, source) => source.id === addToListId - case _ => false + case Bracket.AddToList(_, _, source) => source.id === addToListId + case _ => false } def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] = - fold[NonEmptyList[(PageModel[A], SectionNumber)]](a => NonEmptyList.one((a.singleton, a.sectionNumber)))( - _.singletons.map(_.toPageModelWithNumber) - )(_.iterations.flatMap(_.toPageModelWithNumber)) + fold[NonEmptyList[(PageModel[A], SectionNumber)]](a => + NonEmptyList.one((a.singleton.singleton, a.singleton.sectionNumber)) + )( + _.singletons.map(_.toPageModelWithNumber4) + ) { aa => + val ss = aa.iterations.flatMap(_.toPageModelWithNumber3) + aa.defaultPage.fold(ss)(dp => NonEmptyList(dp.toPageModelWithNumber4, ss.toList)) + } def toPageModel: NonEmptyList[PageModel[A]] = - fold[NonEmptyList[PageModel[A]]](a => NonEmptyList.one(a.singleton))(_.singletons.map(_.singleton))( + fold[NonEmptyList[PageModel[A]]](a => NonEmptyList.one(a.singleton.singleton))(_.singletons.map(_.singleton))( _.iterations.flatMap(_.toPageModel) ) def filter(predicate: PageModel[A] => Boolean): Option[Bracket[A]] = this match { - case Bracket.AddToList(iterations, source) => + case Bracket.AddToList(defaultPage, iterations, source) => val filtered: Option[NonEmptyList[Bracket.AddToListIteration[A]]] = iterations.traverse(_.filter(predicate)) - filtered.map(Bracket.AddToList(_, source)) + filtered.map(Bracket.AddToList(defaultPage, _, source)) case Bracket.RepeatingPage(singletons, source) => val filtered = singletons.filter(singletonWithNumber => predicate(singletonWithNumber.singleton)) NonEmptyList.fromList(filtered).map(Bracket.RepeatingPage(_, source)) - case b @ Bracket.NonRepeatingPage(singleton, _, _) => if (predicate(singleton)) Some(b) else None + case b @ Bracket.NonRepeatingPage(singleton, _) => if (predicate(singleton.singleton)) Some(b) else None } def withAddToListBracket[B](f: Bracket.AddToList[A] => B): B = @@ -98,20 +108,14 @@ object Bracket { def allSingletonSectionNumbers: List[SectionNumber] = singletons.map(_.sectionNumber).toList - def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] = - singletons.map(_.toPageModelWithNumber) ++ checkYourAnswers.toList.map(c => + def toPageModelWithNumber3: NonEmptyList[(PageModel[A], SectionNumber)] = + singletons.map(_.toPageModelWithNumber4) ++ checkYourAnswers.toList.map(c => c.checkYourAnswers -> c.sectionNumber ) ::: NonEmptyList.one(repeater.repeater -> repeater.sectionNumber) - def toPlainBracket: BracketPlain.AddToListIteration[A] = - BracketPlain.AddToListIteration( - singletons.map(_.singleton), - checkYourAnswers.map(_.checkYourAnswers), - repeater.repeater - ) def hasSectionNumber(sectionNumber: SectionNumber): Boolean = repeater.sectionNumber === sectionNumber || checkYourAnswers.exists( - _.sectionNumber == sectionNumber + _.sectionNumber === sectionNumber ) || singletons.exists(_.sectionNumber === sectionNumber) def singleton(sectionNumber: SectionNumber): Singleton[A] = singletons.toList @@ -139,33 +143,25 @@ object Bracket { def lastSectionNumber: SectionNumber = singletons.last.sectionNumber - def secondSectionNumber: SectionNumber = singletons.tail.head.sectionNumber - def isCommited(visitsIndex: VisitIndex): Boolean = singletons.forall { singletonWithNumber => visitsIndex.contains(singletonWithNumber.sectionNumber) } } - case class NonRepeatingPage[A <: PageMode]( - singleton: Singleton[A], - sectionNumber: SectionNumber, - source: Section.NonRepeatingPage - ) extends Bracket[A] { + case class NonRepeatingPage[A <: PageMode](singleton: SingletonWithNumber[A], source: Section.NonRepeatingPage) + extends Bracket[A] { def map[B <: PageMode]( e: Singleton[A] => Singleton[B], f: CheckYourAnswers[A] => CheckYourAnswers[B], g: Repeater[A] => Repeater[B] ): NonRepeatingPage[B] = NonRepeatingPage( - e(singleton), - sectionNumber, + SingletonWithNumber(e(singleton.singleton), singleton.sectionNumber), source ) - def toPlainBracket: BracketPlain[A] = BracketPlain.NonRepeatingPage(singleton, source) - - def hasSectionNumber(sectionNumber: SectionNumber): Boolean = this.sectionNumber === sectionNumber + def hasSectionNumber(sectionNumber: SectionNumber): Boolean = this.singleton.sectionNumber === sectionNumber } case class RepeatingPage[A <: PageMode]( @@ -182,8 +178,6 @@ object Bracket { source ) - def toPlainBracket: BracketPlain[A] = BracketPlain.RepeatingPage(singletons.map(_.singleton), source) - def hasSectionNumber(sectionNumber: SectionNumber): Boolean = singletons.exists(_.sectionNumber === sectionNumber) def singletonForSectionNumber(sectionNumber: SectionNumber): Singleton[A] = @@ -199,6 +193,7 @@ object Bracket { } case class AddToList[A <: PageMode]( + defaultPage: Option[SingletonWithNumber[A]], iterations: NonEmptyList[ AddToListIteration[A] ], // There must be at least one iteration for Add-to-list to make sense @@ -236,28 +231,35 @@ object Bracket { f: CheckYourAnswers[A] => CheckYourAnswers[B], g: Repeater[A] => Repeater[B] ): AddToList[B] = AddToList( + defaultPage.map(s => SingletonWithNumber(e(s.singleton), s.sectionNumber)), iterations.map(_.map(e, f, g)), source ) - def toPlainBracket: BracketPlain[A] = BracketPlain.AddToList(iterations.map(_.toPlainBracket), source) - - def hasSectionNumber(sectionNumber: SectionNumber): Boolean = iterations.exists { iteration => - iteration.repeater.sectionNumber === sectionNumber || iteration.checkYourAnswers.exists( - _.sectionNumber === sectionNumber - ) || iteration.singletons.exists( - _.sectionNumber === sectionNumber - ) - } + def hasSectionNumber(sectionNumber: SectionNumber): Boolean = + iterations.exists { iteration => + iteration.repeater.sectionNumber === sectionNumber || iteration.checkYourAnswers.exists( + _.sectionNumber === sectionNumber + ) || iteration.singletons.exists( + _.sectionNumber === sectionNumber + ) + } || defaultPage.exists { defaultPage => + defaultPage.sectionNumber === sectionNumber + } def iterationForSectionNumber(sectionNumber: SectionNumber): Bracket.AddToListIteration[A] = - iterations.toList - .collectFirst { - case iteration if iteration.hasSectionNumber(sectionNumber) => iteration - } - .getOrElse( - throw new IllegalArgumentException(s"Invalid sectionNumber: $sectionNumber for Bracket.AddToListIteration") - ) + if (defaultPage.exists(_.sectionNumber === sectionNumber)) { + iterations.head + } else { + iterations.toList + .collectFirst { + case iteration if iteration.hasSectionNumber(sectionNumber) => iteration + } + .getOrElse( + throw new IllegalArgumentException(s"Invalid sectionNumber: $sectionNumber for Bracket.AddToListIteration") + ) + + } def iterationForSectionNumberWithIndex(sectionNumber: SectionNumber): (Bracket.AddToListIteration[A], Int) = iterations.zipWithIndex.toList diff --git a/app/uk/gov/hmrc/gform/models/BracketPlain.scala b/app/uk/gov/hmrc/gform/models/BracketPlain.scala deleted file mode 100644 index 55cc0f470..000000000 --- a/app/uk/gov/hmrc/gform/models/BracketPlain.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 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.gform.models - -import cats.data.NonEmptyList -import uk.gov.hmrc.gform.eval.AllPageModelExpressionsGetter -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Expr, Section } - -sealed trait BracketPlain[A <: PageMode] extends Product with Serializable { - def fold[B]( - f: BracketPlain.NonRepeatingPage[A] => B - )( - g: BracketPlain.RepeatingPage[A] => B - )( - h: BracketPlain.AddToList[A] => B - ): B = - this match { - case b: BracketPlain.NonRepeatingPage[A] => f(b) - case b: BracketPlain.RepeatingPage[A] => g(b) - case b: BracketPlain.AddToList[A] => h(b) - } - - def allExprs(formModel: FormModel[DataExpanded]): List[Expr] = AllPageModelExpressionsGetter.allExprs(formModel)(this) -} - -object BracketPlain { - case class AddToListIteration[A <: PageMode]( - singletons: NonEmptyList[Singleton[A]], - checkYourAnswers: Option[CheckYourAnswers[A]], - repeater: Repeater[A] - ) - - case class NonRepeatingPage[A <: PageMode](singleton: Singleton[A], source: Section.NonRepeatingPage) - extends BracketPlain[A] - case class RepeatingPage[A <: PageMode](singletons: NonEmptyList[Singleton[A]], source: Section.RepeatingPage) - extends BracketPlain[A] - case class AddToList[A <: PageMode](iterations: NonEmptyList[AddToListIteration[A]], source: Section.AddToList) - extends BracketPlain[A] - -} diff --git a/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala b/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala index 924fac572..5ba605c65 100644 --- a/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala +++ b/app/uk/gov/hmrc/gform/models/BracketPlainCoordinated.scala @@ -28,7 +28,7 @@ sealed trait BracketPlainCoordinated[A <: PageMode] extends Product with Seriali } object BracketPlainCoordinated { - case class Classic[A <: PageMode](bracketPlains: NonEmptyList[BracketPlain[A]]) extends BracketPlainCoordinated[A] - case class TaskList[A <: PageMode](bracketPlains: NonEmptyList[(Coordinates, TaskModelCoordinated[A])]) + case class Classic[A <: PageMode](brackets: NonEmptyList[Bracket[A]]) extends BracketPlainCoordinated[A] + case class TaskList[A <: PageMode](coordinatedBrackets: NonEmptyList[(Coordinates, TaskModel[A])]) extends BracketPlainCoordinated[A] } diff --git a/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala b/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala index a9daab5a9..91086c264 100644 --- a/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala +++ b/app/uk/gov/hmrc/gform/models/BracketsWithSectionNumber.scala @@ -33,14 +33,9 @@ sealed trait BracketsWithSectionNumber[A <: PageMode] extends Product with Seria )(identity) def toBracketPlainCoordinated: BracketPlainCoordinated[A] = fold[BracketPlainCoordinated[A]] { classic => - val bracketPlains: NonEmptyList[BracketPlain[A]] = classic.brackets.map(_.toPlainBracket) - BracketPlainCoordinated.Classic(bracketPlains) + BracketPlainCoordinated.Classic(classic.brackets) } { taskList => - val bracketPlains: NonEmptyList[(Coordinates, TaskModelCoordinated[A])] = taskList.brackets.map { - case (coor, taskModel) => - (coor, taskModel.toTaskModelCoordinated) - } - BracketPlainCoordinated.TaskList(bracketPlains) + BracketPlainCoordinated.TaskList(taskList.brackets) } def map[B <: PageMode]( @@ -106,8 +101,8 @@ sealed trait BracketsWithSectionNumber[A <: PageMode] extends Product with Seria } } - def toBracketsPlains: NonEmptyList[BracketPlain[A]] = - fold(_.brackets)(_.allBrackets).map(_.toPlainBracket) + def toBrackets: NonEmptyList[Bracket[A]] = + fold(_.brackets)(_.allBrackets) def toPageModelWithNumber: NonEmptyList[(PageModel[A], SectionNumber)] = fold { classic => @@ -136,44 +131,14 @@ object BracketsWithSectionNumber { ) } - def fromBracketsPlains[A <: PageMode](bracketPlains: BracketPlainCoordinated[A]): BracketsWithSectionNumber[A] = + def fromBracketCoordinated[A <: PageMode](bracketPlains: BracketPlainCoordinated[A]): BracketsWithSectionNumber[A] = bracketPlains match { - case BracketPlainCoordinated.Classic(bracketPlains) => - val iterator: Iterator[SectionNumber] = LazyList.from(0).map(SectionNumber.Classic(_)).iterator - val res: NonEmptyList[Bracket[A]] = mkBrackets(iterator, bracketPlains) - Classic(res) - case BracketPlainCoordinated.TaskList(coordinatedBracketPlains) => + case BracketPlainCoordinated.Classic(brackets) => Classic(brackets) + case BracketPlainCoordinated.TaskList(coordinatedBrackets) => val res: NonEmptyList[(Coordinates, TaskModel[A])] = - coordinatedBracketPlains.map { case (coordinated, taskModelCoordinated) => - val mkSectionNumber = - SectionNumber.TaskList(Coordinates(coordinated.taskSectionNumber, coordinated.taskNumber), _) - val iterator: Iterator[SectionNumber] = LazyList.from(0).map(mkSectionNumber).iterator - val taskModel: TaskModel[A] = taskModelCoordinated.toTaskModel(mkBrackets(iterator, _)) - (coordinated, taskModel) + coordinatedBrackets.map { case (coordinated, taskModelCoordinated) => + (coordinated, taskModelCoordinated.toTaskModel()) } TaskList(res) } - - private def mkBrackets[A <: PageMode]( - iterator: Iterator[SectionNumber], - brackets: NonEmptyList[BracketPlain[A]] - ): NonEmptyList[Bracket[A]] = - brackets.map { - case BracketPlain.AddToList(iterations, source) => - Bracket.AddToList( - iterations.map { it => - Bracket - .AddToListIteration( - it.singletons.map(singleton => SingletonWithNumber(singleton, iterator.next())), - it.checkYourAnswers.map(CheckYourAnswersWithNumber(_, iterator.next())), - RepeaterWithNumber(it.repeater, iterator.next()) - ) - }, - source - ) - case BracketPlain.RepeatingPage(singletons, source) => - Bracket.RepeatingPage(singletons.map(singleton => SingletonWithNumber(singleton, iterator.next())), source) - case BracketPlain.NonRepeatingPage(singleton, source) => - Bracket.NonRepeatingPage(singleton, iterator.next(), source) - } } diff --git a/app/uk/gov/hmrc/gform/models/FastForward.scala b/app/uk/gov/hmrc/gform/models/FastForward.scala index dff7ab706..7c38b6f64 100644 --- a/app/uk/gov/hmrc/gform/models/FastForward.scala +++ b/app/uk/gov/hmrc/gform/models/FastForward.scala @@ -39,8 +39,8 @@ sealed trait FastForward extends Product with Serializable { case FastForward.CYA(SectionOrSummary.FormSummary) => FastForward.ffCYAFormSummary case FastForward.CYA(SectionOrSummary.TaskSummary) => FastForward.ffCYATaskSummary case FastForward.CYA(SectionOrSummary.Section(to)) => - FastForward.ffCYA + to.value - }("back" + _.until.value) + FastForward.ffCYA + ":" + to.value + }("back:" + _.until.value) def next(formModel: FormModel[Visibility], sn: SectionNumber): FastForward = fold[FastForward](identity) { st => diff --git a/app/uk/gov/hmrc/gform/models/FormModel.scala b/app/uk/gov/hmrc/gform/models/FormModel.scala index 08dd5ea97..cddb5b422 100644 --- a/app/uk/gov/hmrc/gform/models/FormModel.scala +++ b/app/uk/gov/hmrc/gform/models/FormModel.scala @@ -74,17 +74,10 @@ case class FormModel[A <: PageMode]( def allFormComponents(coordinates: Coordinates): List[FormComponent] = availablePages(coordinates).flatMap(_.allFormComponents) - - def nextVisibleSectionNumber( - tlSectionNumber: SectionNumber.TaskList - ): SectionNumber.TaskList = - availableSectionNumbers - .collect { case t: SectionNumber.TaskList => t } - .find(sn => tlSectionNumber.coordinates === sn.coordinates && sn.sectionNumber >= tlSectionNumber.sectionNumber) - .getOrElse(throw new Exception("No more visible section numbers in the task")) } val nextVisibleSectionNumber: SectionNumber => Option[SectionNumber] = { + case r: SectionNumber.Legacy => availableSectionNumbers.headOption case sectionNumber: SectionNumber.TaskList => availableSectionNumbers .collect { case t: SectionNumber.TaskList => t } @@ -170,7 +163,7 @@ case class FormModel[A <: PageMode]( fc.modelComponentId }.toSet - val exprsMetadata: List[ExprMetadata] = brackets.toBracketsPlains.toList.flatMap { + val exprsMetadata: List[ExprMetadata] = brackets.toBrackets.toList.flatMap { case AllPageModelExpressions(exprMetadatas) => exprMetadatas case _ => Nil } @@ -207,22 +200,23 @@ case class FormModel[A <: PageMode]( def flatMapRepeater( f: ( - NonEmptyList[BracketPlain.AddToListIteration[A]], + NonEmptyList[Bracket.AddToListIteration[A]], Section.AddToList - ) => NonEmptyList[BracketPlain.AddToListIteration[A]] + ) => NonEmptyList[Bracket.AddToListIteration[A]] ): FormModel[A] = { - def applyFToRepeater(bracketPlains: NonEmptyList[BracketPlain[A]]): NonEmptyList[BracketPlain[A]] = - bracketPlains.map { - case BracketPlain.AddToList(iterations, source) => BracketPlain.AddToList(f(iterations, source), source) - case o => o + def applyFToRepeater(brackets: NonEmptyList[Bracket[A]]): NonEmptyList[Bracket[A]] = + brackets.map { + case Bracket.AddToList(defaultPage, iterations, source) => + Bracket.AddToList(defaultPage, f(iterations, source), source) + case o => o } def bracketPlainCoordinated: BracketPlainCoordinated[A] = brackets.toBracketPlainCoordinated.fold[BracketPlainCoordinated[A]] { classic => - BracketPlainCoordinated.Classic[A](applyFToRepeater(classic.bracketPlains)) + BracketPlainCoordinated.Classic[A](applyFToRepeater(classic.brackets)) } { taskList => - BracketPlainCoordinated.TaskList[A](taskList.bracketPlains.map { case (coor, taskModelCoordinated) => + BracketPlainCoordinated.TaskList[A](taskList.coordinatedBrackets.map { case (coor, taskModelCoordinated) => coor -> taskModelCoordinated.modifyBrackets(applyFToRepeater) }) } @@ -230,7 +224,8 @@ case class FormModel[A <: PageMode]( FormModel.fromPages(bracketPlainCoordinated, staticTypeInfo, revealingChoiceInfo, sumInfo, dataRetrieve) } - def apply(sectionNumber: SectionNumber): PageModel[A] = pageModelLookup(sectionNumber) + def apply(sectionNumber: SectionNumber): PageModel[A] = + pageModelLookup(sectionNumber) def bracket(sectionNumber: SectionNumber): Bracket[A] = brackets.withSectionNumber(sectionNumber) @@ -372,7 +367,12 @@ object FormModel { val singleton = Singleton(enrolmentSection.toPage).asInstanceOf[Singleton[A]] FormModel.fromPages( BracketPlainCoordinated.Classic( - NonEmptyList.one(BracketPlain.NonRepeatingPage(singleton, enrolmentSection.toSection)) + NonEmptyList.one( + Bracket.NonRepeatingPage( + SingletonWithNumber(singleton, SectionNumber.classicZero), + enrolmentSection.toSection + ) + ) ), StaticTypeInfo.empty, RevealingChoiceInfo.empty, @@ -382,16 +382,16 @@ object FormModel { } def fromPages[A <: PageMode]( - bracketPlains: BracketPlainCoordinated[A], + brackets: BracketPlainCoordinated[A], staticTypeInfo: StaticTypeInfo, revealingChoiceInfo: RevealingChoiceInfo, sumInfo: SumInfo, dataRetrieve: Option[NonEmptyList[DataRetrieve]] ): FormModel[A] = { - def standaloneSumInfo: StandaloneSumInfo = StandaloneSumInfo.from(bracketPlains, sumInfo) + def standaloneSumInfo: StandaloneSumInfo = StandaloneSumInfo.from(brackets, sumInfo) - val bracketsWithSectionNumber = BracketsWithSectionNumber.fromBracketsPlains(bracketPlains) + val bracketsWithSectionNumber = BracketsWithSectionNumber.fromBracketCoordinated(brackets) FormModel( bracketsWithSectionNumber, diff --git a/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala b/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala index 3ba2cc687..102d56ef8 100644 --- a/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala +++ b/app/uk/gov/hmrc/gform/models/FormModelBuilder.scala @@ -453,41 +453,81 @@ class FormModelBuilder[E, F[_]: Functor]( includeIf = Some(page.includeIf.fold(includeIf)(inIf => IncludeIf(And(inIf.booleanExpr, includeIf.booleanExpr)))) ) + private def basicDefaultPage[T <: PageMode: FormModelExpander]( + s: Section.AddToList, + templateSectionIndex: TemplateSectionIndex, + maybeCoordinates: Option[Coordinates], + data: VariadicFormData[SourceOrigin.OutOfDate] + ): Option[SingletonWithNumber[T]] = { + val addToListPages: Option[Page[Basic]] = s.defaultPage + + addToListPages.map { page => + val page2: Page[Basic] = mkSingleton(page, 1)(s) + val page3: Page[T] = implicitly[FormModelExpander[T]].lift(page2, data) + val sectionNumber = mkSectionNumber( + SectionNumber.Classic.AddToListPage.DefaultPage(templateSectionIndex), + maybeCoordinates + ) + SingletonWithNumber[T](Singleton(page3), sectionNumber) + } + } + private def basicAddToList[T <: PageMode: FormModelExpander]( s: Section.AddToList, - index: Int, + templateSectionIndex: TemplateSectionIndex, + maybeCoordinates: Option[Coordinates], + iterationIndex: Int, data: VariadicFormData[SourceOrigin.OutOfDate] - ): Option[BracketPlain.AddToListIteration[T]] = { - val singletons: List[Singleton[T]] = { - val addToListPages: NonEmptyList[Page[Basic]] = - s.defaultPage.fold(s.pages) { dp => - val defaultIncludeIf = IncludeIf(Equals(Constant("1"), Count(s.addAnotherQuestion.id))) - s.pages.prepend( - dp.copy(includeIf = - Some( - dp.includeIf.fold(defaultIncludeIf)(inIf => - IncludeIf(And(inIf.booleanExpr, defaultIncludeIf.booleanExpr)) - ) - ) - ) - ) - } + ): Option[Bracket.AddToListIteration[T]] = { + val singletons: List[SingletonWithNumber[T]] = { + val addToListPages: NonEmptyList[Page[Basic]] = s.pages - addToListPages.map { page => + addToListPages.zipWithIndex.map { case (page, pageIndex) => val page1: Page[Basic] = s.includeIf.fold(page)(includeIf => mergeIncludeIfs(includeIf, page)) - val page2: Page[Basic] = mkSingleton(page1, index)(s) + val page2: Page[Basic] = mkSingleton(page1, iterationIndex)(s) val page3: Page[T] = implicitly[FormModelExpander[T]].lift(page2, data) - Singleton[T](page3) + val sectionNumber = mkSectionNumber( + SectionNumber.Classic.AddToListPage.Page(templateSectionIndex, iterationIndex, pageIndex), + maybeCoordinates + ) + SingletonWithNumber[T](Singleton(page3), sectionNumber) }.toList } - val repeater: Repeater[T] = mkRepeater(s, index) + val repeater: Repeater[T] = mkRepeater(s, iterationIndex) - val checkYourAnswers: Option[CheckYourAnswers[T]] = s.cyaPage.map(c => mkCheckYourAnswers(c, s, index)) + val checkYourAnswers: Option[CheckYourAnswersWithNumber[T]] = s.cyaPage.map(c => + CheckYourAnswersWithNumber( + mkCheckYourAnswers(c, s, iterationIndex), + mkSectionNumber( + SectionNumber.Classic.AddToListPage.CyaPage(templateSectionIndex, iterationIndex), + maybeCoordinates + ) + ) + ) - NonEmptyList.fromList(singletons).map(BracketPlain.AddToListIteration(_, checkYourAnswers, repeater)) + NonEmptyList + .fromList(singletons) + .map( + Bracket.AddToListIteration( + _, + checkYourAnswers, + RepeaterWithNumber( + repeater, + mkSectionNumber( + SectionNumber.Classic.AddToListPage.RepeaterPage(templateSectionIndex, iterationIndex), + maybeCoordinates + ) + ) + ) + ) } + private def mkSectionNumber( + sn: SectionNumber.Classic, + coordinates: Option[Coordinates] + ): SectionNumber = coordinates.fold[SectionNumber](sn)(coordinates => SectionNumber.TaskList(coordinates, sn)) + private def basic[T <: PageMode, U <: SectionSelectorType]( data: VariadicFormData[SourceOrigin.OutOfDate] )(implicit formModelExpander: FormModelExpander[T], sectionIncluder: SectionSelector[U]): FormModel[T] = { @@ -495,21 +535,33 @@ class FormModelBuilder[E, F[_]: Functor]( val allSections: AllSections = sectionIncluder.getSections(formTemplate) val staticTypeInfo: StaticTypeInfo = - allSections.sections.foldLeft(StaticTypeInfo.empty)(_ ++ _.staticTypeInfo) + allSections.sections.foldLeft(StaticTypeInfo.empty)(_ ++ _.section.staticTypeInfo) val revealingChoiceInfo: RevealingChoiceInfo = - allSections.sections.foldLeft(RevealingChoiceInfo.empty)(_ ++ _.revealingChoiceInfo) - - val brackets: BracketPlainCoordinated[T] = allSections.mapSection { - case s: Section.NonRepeatingPage => - val page = formModelExpander.lift(s.page, data) - Some(BracketPlain.NonRepeatingPage(Singleton[T](page), s)) - case s: Section.RepeatingPage => formModelExpander.liftRepeating(s, data) - case s: Section.AddToList => - basicAddToList(s, 1, data).map(atl => BracketPlain.AddToList(NonEmptyList.one(atl), s)) + allSections.sections.foldLeft(RevealingChoiceInfo.empty)(_ ++ _.section.revealingChoiceInfo) + + val brackets: BracketPlainCoordinated[T] = allSections.mapSection { maybeCoordinates => indexedSection => + indexedSection match { + case IndexedSection.SectionNoIndex(s) => + val page = formModelExpander.lift(s.page, data) + val sectionNumber = + mkSectionNumber(SectionNumber.classicZero, maybeCoordinates) + Some(Bracket.NonRepeatingPage(SingletonWithNumber[T](Singleton(page), sectionNumber), s)) + case IndexedSection.SectionIndex(s: Section.NonRepeatingPage, index) => + val page = formModelExpander.lift(s.page, data) + val sectionNumber = mkSectionNumber(SectionNumber.Classic.NormalPage(index), maybeCoordinates) + Some(Bracket.NonRepeatingPage(SingletonWithNumber[T](Singleton(page), sectionNumber), s)) + case IndexedSection.SectionIndex(s: Section.RepeatingPage, index) => + formModelExpander.liftRepeating(s, index, data) + case IndexedSection.SectionIndex(s: Section.AddToList, index) => + val defaultPage: Option[SingletonWithNumber[T]] = basicDefaultPage(s, index, maybeCoordinates, data) + basicAddToList(s, index, maybeCoordinates, 1, data).map(atl => + Bracket.AddToList(defaultPage, NonEmptyList.one(atl), s) + ) + } } - val sumInfo: SumInfo = allSections.sections.foldLeft(SumInfo.empty)(_ ++ _.sumInfo) + val sumInfo: SumInfo = allSections.sections.foldLeft(SumInfo.empty)(_ ++ _.section.sumInfo) FormModel.fromPages(brackets, staticTypeInfo, revealingChoiceInfo, sumInfo, formTemplate.dataRetrieve) } @@ -524,16 +576,25 @@ class FormModelBuilder[E, F[_]: Functor]( } private def answeredAddToListIterations[T <: PageMode: FormModelExpander]( - iteration: BracketPlain.AddToListIteration[T], + iteration: Bracket.AddToListIteration[T], data: VariadicFormData[SourceOrigin.OutOfDate], source: Section.AddToList - ): NonEmptyList[BracketPlain.AddToListIteration[T]] = { + ): NonEmptyList[Bracket.AddToListIteration[T]] = { def loop( - repeater: Repeater[T], - acc: NonEmptyList[BracketPlain.AddToListIteration[T]] - ): NonEmptyList[BracketPlain.AddToListIteration[T]] = - if (repeaterIsYes(repeater.addAnotherQuestion.modelComponentId, data)) { - val maybeBracket = basicAddToList(source, repeater.index + 1, data) + repeater: RepeaterWithNumber[T], + acc: NonEmptyList[Bracket.AddToListIteration[T]] + ): NonEmptyList[Bracket.AddToListIteration[T]] = + if (repeaterIsYes(repeater.repeater.addAnotherQuestion.modelComponentId, data)) { + val templateSectionIndex: TemplateSectionIndex = repeater.sectionNumber.templateSectionIndex + val maybeCoordinates: Option[Coordinates] = repeater.sectionNumber.maybeCoordinates + val maybeBracket = + basicAddToList( + source, + templateSectionIndex, + maybeCoordinates, + repeater.repeater.index + 1, + data + ) // Add next iteration maybeBracket .map { bracket => loop(bracket.repeater, acc ::: NonEmptyList.one(bracket)) diff --git a/app/uk/gov/hmrc/gform/models/FormModelExpander.scala b/app/uk/gov/hmrc/gform/models/FormModelExpander.scala index 2b11080cf..5a4de7c76 100644 --- a/app/uk/gov/hmrc/gform/models/FormModelExpander.scala +++ b/app/uk/gov/hmrc/gform/models/FormModelExpander.scala @@ -28,8 +28,9 @@ trait FormModelExpander[T <: PageMode] { def lift(page: Page[Basic], data: VariadicFormData[SourceOrigin.OutOfDate]): Page[T] def liftRepeating( section: Section.RepeatingPage, + templateSectionIndex: TemplateSectionIndex, data: VariadicFormData[SourceOrigin.OutOfDate] - ): Option[BracketPlain.RepeatingPage[T]] + ): Option[Bracket.RepeatingPage[T]] } object FormModelExpander { @@ -56,8 +57,9 @@ object FormModelExpander { // Perfect we have access to FormModelVisibilityOptics, so we can evaluate 'section.repeats' expression def liftRepeating( section: Section.RepeatingPage, + templateSectionIndex: TemplateSectionIndex, data: VariadicFormData[SourceOrigin.OutOfDate] - ): Option[BracketPlain.RepeatingPage[DataExpanded]] = { + ): Option[Bracket.RepeatingPage[DataExpanded]] = { val repeats = section.repeats val bdRepeats: Option[BigDecimal] = fmvo.evalAndApplyTypeInfoFirst(repeats).numberRepresentation val repeatCount = Math.min(bdRepeats.fold(0)(_.toInt), repeatsLimit) @@ -65,7 +67,16 @@ object FormModelExpander { val pageBasic: Page[Basic] = mkSingleton(section.page, index)(section) Singleton(pageBasic.asInstanceOf[Page[DataExpanded]]) } - NonEmptyList.fromList(singletons).map(BracketPlain.RepeatingPage(_, section)) + NonEmptyList + .fromList(singletons) + .map(xs => + Bracket.RepeatingPage( + xs.zipWithIndex.map { case (s, i) => + SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i)) + }, + section + ) + ) } } @@ -82,8 +93,9 @@ object FormModelExpander { // Expand by data, we don't know value of 'section.repeats' expression here def liftRepeating( section: Section.RepeatingPage, + templateSectionIndex: TemplateSectionIndex, data: VariadicFormData[SourceOrigin.OutOfDate] - ): Option[BracketPlain.RepeatingPage[Interim]] = { + ): Option[Bracket.RepeatingPage[Interim]] = { val baseIds: Set[BaseComponentId] = section.allIds.map(_.baseComponentId).toSet @@ -103,13 +115,25 @@ object FormModelExpander { Singleton(pageBasic.asInstanceOf[Page[Interim]]) } - NonEmptyList.fromList(singletons).map(BracketPlain.RepeatingPage(_, section)) + NonEmptyList + .fromList(singletons) + .map(xs => + Bracket.RepeatingPage( + xs.zipWithIndex.map { case (s, i) => + SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i)) + }, + section + ) + ) } } implicit val dependencyGraphVerification: FormModelExpander[DependencyGraphVerification] = new FormModelExpander[DependencyGraphVerification] { - def lift(page: Page[Basic], data: VariadicFormData[SourceOrigin.OutOfDate]): Page[DependencyGraphVerification] = { + def lift( + page: Page[Basic], + data: VariadicFormData[SourceOrigin.OutOfDate] + ): Page[DependencyGraphVerification] = { val expanded = page.fields.flatMap { case fc @ IsRevealingChoice(revealingChoice) => fc :: revealingChoice.options.flatMap(_.revealingFields) @@ -120,11 +144,21 @@ object FormModelExpander { } def liftRepeating( section: Section.RepeatingPage, + templateSectionIndex: TemplateSectionIndex, data: VariadicFormData[SourceOrigin.OutOfDate] - ): Option[BracketPlain.RepeatingPage[DependencyGraphVerification]] = { + ): Option[Bracket.RepeatingPage[DependencyGraphVerification]] = { val pageBasic = mkSingleton(section.page, 1)(section) - val singletons = NonEmptyList.one(Singleton(pageBasic.asInstanceOf[Page[DependencyGraphVerification]])) - Some(BracketPlain.RepeatingPage(singletons, section)) + val singletons = NonEmptyList.one( + Singleton(pageBasic.asInstanceOf[Page[DependencyGraphVerification]]) + ) + Some( + Bracket.RepeatingPage( + singletons.zipWithIndex.map { case (s, i) => + SingletonWithNumber(s, SectionNumber.Classic.RepeatedPage(templateSectionIndex, i)) + }, + section + ) + ) } } diff --git a/app/uk/gov/hmrc/gform/models/ProcessData.scala b/app/uk/gov/hmrc/gform/models/ProcessData.scala index 2f1461287..1fb484afb 100644 --- a/app/uk/gov/hmrc/gform/models/ProcessData.scala +++ b/app/uk/gov/hmrc/gform/models/ProcessData.scala @@ -88,7 +88,6 @@ class ProcessDataService[F[_]: Monad]( val cachedObligations: Obligations = cache.form.thirdPartyData.obligations - val mongoData = formModelOptics for { browserFormModelOptics <- FormModelOptics @@ -106,16 +105,9 @@ class ProcessDataService[F[_]: Monad]( val dataUpd: FormModelOptics[DataOrigin.Browser] = new ObligationValidator {} .validateWithDes(browserFormModelOptics, cachedObligations, obligations) - val newVisitIndex = - VisitIndex.updateSectionVisits( - browserFormModelOptics.formModelRenderPageOptics.formModel, - mongoData.formModelRenderPageOptics.formModel, - cache.form.visitsIndex - ) - ProcessData( dataUpd, - newVisitIndex, + cache.form.visitsIndex, obligations, browserFormModelOptics.formModelVisibilityOptics.booleanExprCache ) diff --git a/app/uk/gov/hmrc/gform/models/SectionIncluder.scala b/app/uk/gov/hmrc/gform/models/SectionIncluder.scala index 26a784eb7..51f4292c2 100644 --- a/app/uk/gov/hmrc/gform/models/SectionIncluder.scala +++ b/app/uk/gov/hmrc/gform/models/SectionIncluder.scala @@ -40,7 +40,7 @@ object SectionSelector { new SectionSelector[SectionSelectorType.WithDeclaration] { def getSections(formTemplate: FormTemplate): AllSections = { - val destinationSections: List[Section] = formTemplate.destinations.fold(destinationList => + val destinationSections: List[Section.NonRepeatingPage] = formTemplate.destinations.fold(destinationList => destinationList.declarationSection.toList.map(_.toSection) )(destinationPrint => Nil) @@ -63,7 +63,7 @@ object SectionSelector { new SectionSelector[SectionSelectorType.WithAcknowledgement] { def getSections(formTemplate: FormTemplate): AllSections = { - val destinationSections: List[Section] = formTemplate.destinations.fold(destinationList => + val destinationSections: List[Section.NonRepeatingPage] = formTemplate.destinations.fold(destinationList => destinationList.declarationSection.toList.map(_.toSection) ++ List( destinationList.acknowledgementSection.toSection ) diff --git a/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala b/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala index ca0acede1..4318291de 100644 --- a/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala +++ b/app/uk/gov/hmrc/gform/models/SingletonWithNumber.scala @@ -25,5 +25,5 @@ case class SingletonWithNumber[A <: PageMode]( def map[B <: PageMode](f: Singleton[A] => Singleton[B]): SingletonWithNumber[B] = SingletonWithNumber(f(singleton), sectionNumber) - def toPageModelWithNumber: (PageModel[A], SectionNumber) = (singleton, sectionNumber) + def toPageModelWithNumber4: (PageModel[A], SectionNumber) = (singleton, sectionNumber) } diff --git a/app/uk/gov/hmrc/gform/models/TaskModel.scala b/app/uk/gov/hmrc/gform/models/TaskModel.scala index e610262fa..fe42b786c 100644 --- a/app/uk/gov/hmrc/gform/models/TaskModel.scala +++ b/app/uk/gov/hmrc/gform/models/TaskModel.scala @@ -34,11 +34,11 @@ sealed trait TaskModel[A <: PageMode] extends Product with Serializable { def mapBracket[B <: PageMode](f: Bracket[A] => Bracket[B]): TaskModel[B] = fold(_ => TaskModel.allHidden[B])(editable => TaskModel.editable(editable.brackets.map(f))) - val toTaskModelCoordinated: TaskModelCoordinated[A] = - fold[TaskModelCoordinated[A]](_ => TaskModelCoordinated.allHidden)(editable => - TaskModelCoordinated.editable(editable.brackets.map(_.toPlainBracket)) - ) + def toTaskModel(): TaskModel[A] = + fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](editable.brackets)) + def modifyBrackets(f: NonEmptyList[Bracket[A]] => NonEmptyList[Bracket[A]]): TaskModel[A] = + fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](f(editable.brackets))) } object TaskModel { diff --git a/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala b/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala deleted file mode 100644 index 0ca78d203..000000000 --- a/app/uk/gov/hmrc/gform/models/TaskModelCoordinated.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 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.gform.models - -import cats.data.NonEmptyList - -sealed trait TaskModelCoordinated[A <: PageMode] extends Product with Serializable { - - def fold[B](f: TaskModelCoordinated.AllHidden[A] => B)(g: TaskModelCoordinated.Editable[A] => B): B = - this match { - case n: TaskModelCoordinated.AllHidden[A] => f(n) - case r: TaskModelCoordinated.Editable[A] => g(r) - } - - def toTaskModel(f: NonEmptyList[BracketPlain[A]] => NonEmptyList[Bracket[A]]): TaskModel[A] = - fold(_ => TaskModel.allHidden[A])(editable => TaskModel.editable[A](f(editable.brackets))) - - def modifyBrackets(f: NonEmptyList[BracketPlain[A]] => NonEmptyList[BracketPlain[A]]): TaskModelCoordinated[A] = - fold(_ => TaskModelCoordinated.allHidden[A])(editable => TaskModelCoordinated.editable[A](f(editable.brackets))) -} - -object TaskModelCoordinated { - - def editable[A <: PageMode](bs: NonEmptyList[BracketPlain[A]]): TaskModelCoordinated[A] = - TaskModelCoordinated.Editable[A](bs) - def allHidden[A <: PageMode]: TaskModelCoordinated[A] = AllHidden[A]() - - case class Editable[A <: PageMode](brackets: NonEmptyList[BracketPlain[A]]) extends TaskModelCoordinated[A] - case class AllHidden[A <: PageMode]() extends TaskModelCoordinated[A] -} diff --git a/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala b/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala index c8871b4cf..03244a23b 100644 --- a/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala +++ b/app/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilder.scala @@ -53,8 +53,8 @@ object PDFPageModelBuilder { buildFromSingleton( cache, envelopeWithMapping, - nonRepeatingPage.singleton, - nonRepeatingPage.sectionNumber, + nonRepeatingPage.singleton.singleton, + nonRepeatingPage.singleton.sectionNumber, nonRepeatingPage.source, validationResult, formModelOptics.formModelVisibilityOptics diff --git a/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala b/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala index cf6cbd6ea..c197ee962 100644 --- a/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala +++ b/app/uk/gov/hmrc/gform/sharedmodel/form/VisitIndex.scala @@ -19,7 +19,6 @@ package uk.gov.hmrc.gform.sharedmodel.form import cats.implicits._ import play.api.libs.json.{ JsArray, JsError, JsObject, JsSuccess, JsValue, Json, OFormat } import scala.util.Try -import uk.gov.hmrc.gform.models.{ DataExpanded, FormModel, PageModel } import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Coordinates, SectionNumber, TaskNumber, TaskSectionNumber } sealed trait VisitIndex extends Product with Serializable { @@ -31,7 +30,10 @@ sealed trait VisitIndex extends Product with Serializable { } private def visitClassic(sectionNumber: SectionNumber.Classic): VisitIndex = - fold[VisitIndex](classic => VisitIndex.Classic(classic.visitsIndex + sectionNumber.sectionNumber))(identity) + fold[VisitIndex] { classic => + val updatedVisits = VisitIndex.Classic(classic.visitsIndex + sectionNumber) + updatedVisits + }(identity) private def visitTaskList(sectionNumber: SectionNumber.TaskList): VisitIndex = fold[VisitIndex](identity) { taskList => @@ -49,13 +51,16 @@ sealed trait VisitIndex extends Product with Serializable { def unvisit(sectionNumber: SectionNumber): VisitIndex = sectionNumber.fold(unvisitClassic)(unvisitTaskList) private def unvisitClassic(sectionNumber: SectionNumber.Classic): VisitIndex = - fold[VisitIndex](classic => VisitIndex.Classic(classic.visitsIndex - sectionNumber.sectionNumber))(identity) + fold[VisitIndex] { classic => + val updatedVisits = VisitIndex.Classic(classic.visitsIndex - sectionNumber) + updatedVisits + }(identity) private def unvisitTaskList(sectionNumber: SectionNumber.TaskList): VisitIndex = fold[VisitIndex](identity)(identity) private def containsClassic(sectionNumber: SectionNumber.Classic): Boolean = - fold[Boolean](classic => classic.visitsIndex.contains(sectionNumber.sectionNumber))(_ => false) + fold[Boolean](classic => classic.visitsIndex.contains(sectionNumber))(_ => false) private def containsTaskList(sectionNumber: SectionNumber.TaskList): Boolean = fold[Boolean](_ => false)(taskList => @@ -67,8 +72,8 @@ sealed trait VisitIndex extends Product with Serializable { object VisitIndex { - final case class Classic(visitsIndex: Set[Int]) extends VisitIndex - final case class TaskList(visitsIndex: Map[Coordinates, Set[Int]]) extends VisitIndex + final case class Classic(visitsIndex: Set[SectionNumber.Classic]) extends VisitIndex + final case class TaskList(visitsIndex: Map[Coordinates, Set[SectionNumber.Classic]]) extends VisitIndex val key: String = "visitsIndex" @@ -77,68 +82,38 @@ object VisitIndex { implicit val format: OFormat[VisitIndex] = OFormat( (jsValue: JsValue) => (jsValue \ key).toOption match { - case None => JsError(s"Missing '$key' field. Failed to decode VisitIndex from: $jsValue") - case Some(a: JsArray) => JsSuccess(Classic(a.value.map(_.as[Int]).toSet)) + case None => JsError(s"Missing '$key' field. Failed to decode VisitIndex from: $jsValue") + case Some(a: JsArray) => + a.validate[Set[SectionNumber.Classic]] match { + case JsSuccess(visits, _) => JsSuccess(Classic(visits)) + case JsError(errors) => JsSuccess(Classic(Set.empty[SectionNumber.Classic])) + } case Some(o: JsObject) => - val res: Try[List[(Coordinates, Set[Int])]] = - o.value.toList.traverse { case (k, v) => - Try(k.split(",").toList.map(_.toInt)).collect { case taskSectionNumber :: taskNumber :: Nil => - (Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber))) -> v.as[Set[Int]] + o.value.toList + .traverse { case (k, v) => + Try(k.split(",").toList).collect { case taskSectionNumber :: taskNumber :: Nil => + val key = Coordinates(TaskSectionNumber(taskSectionNumber.toInt), TaskNumber(taskNumber.toInt)) + val visits = v.validate[Set[SectionNumber.Classic]].getOrElse(Set.empty[SectionNumber.Classic]) + key -> visits } } - res.fold( - error => JsError("Failed to decode VisitIndex for TaskList from json: " + jsValue), - xs => JsSuccess(TaskList(xs.toMap)) - ) + .fold( + error => JsError("Failed to decode VisitIndex for TaskList from json: " + jsValue), + xs => JsSuccess(TaskList(xs.toMap)) + ) case Some(unexpected) => JsError("Unknown type. Failed to decode VisitIndex from json: " + unexpected) }, (visitIndex: VisitIndex) => visitIndex match { case Classic(visitsIndex) => Json.obj(key -> Json.toJson(visitsIndex)) case TaskList(visitsIndex) => - val s: Map[String, JsValue] = visitsIndex.toList.map { - case (Coordinates(TaskSectionNumber(tsc), TaskNumber(tn)), indexes) => - List(tsc, tn).mkString(",") -> Json.toJson(indexes) - }.toMap + val s: Map[String, JsValue] = + visitsIndex.toList.map { case (Coordinates(TaskSectionNumber(tsc), TaskNumber(tn)), indexes) => + List(tsc, tn).mkString(",") -> { + Json.toJson(indexes) + } + }.toMap Json.obj(key -> Json.toJson(s)) } ) - - def updateSectionVisits( - formModel: FormModel[DataExpanded], - mongoFormModel: FormModel[DataExpanded], - visitsIndex: VisitIndex - ): VisitIndex = { - def update( - xs: Set[Int], - f: Int => SectionNumber, - pages: FormModel[DataExpanded] => List[PageModel[DataExpanded]] - ): Set[Int] = - xs.map { index => - Try(mongoFormModel(f(index))).toOption.fold(-1) { page => - page.allFormComponents.headOption.fold(-1) { mongoHead => - val firstComponentId = mongoHead.id - pages(formModel).indexWhere { pageModel => - pageModel.allFormComponents.headOption.fold(false)(_.id === firstComponentId) - } - - } - } - }.filterNot(_ === -1) - - visitsIndex.fold[VisitIndex] { classic => - VisitIndex.Classic(update(classic.visitsIndex, SectionNumber.Classic(_), _.pages.toList)) - } { taskList => - VisitIndex.TaskList { - taskList.visitsIndex.map { case (coordinates, indexes) => - coordinates -> - update( - indexes, - SectionNumber.TaskList(coordinates, _), - _.taskList.availablePages(coordinates).toList - ) - } - } - } - } } diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala index 73a139037..e7e4c8cfd 100644 --- a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala +++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/FormKind.scala @@ -19,7 +19,7 @@ package uk.gov.hmrc.gform.sharedmodel.formtemplate import cats.data.NonEmptyList import julienrf.json.derived import play.api.libs.json.OFormat -import uk.gov.hmrc.gform.models.AllSections +import uk.gov.hmrc.gform.models.{ AllSections, IndexedSection } import uk.gov.hmrc.gform.sharedmodel.formtemplate.JsonUtils._ import uk.gov.hmrc.gform.sharedmodel.SmartString import com.softwaremill.quicklens._ @@ -44,7 +44,9 @@ sealed trait FormKind extends Product with Serializable { } val allSections: AllSections = fold[AllSections] { classic => - AllSections.Classic(classic.sections) + AllSections.Classic(classic.sections.zipWithIndex.map { case (s, i) => + IndexedSection.SectionIndex(s, TemplateSectionIndex(i)) + }) } { taskList => AllSections.TaskList { taskList.sections.zipWithIndex.flatMap { case (taskSection, taskSectionIndex) => @@ -52,7 +54,7 @@ sealed trait FormKind extends Product with Serializable { Coordinates(TaskSectionNumber(taskSectionIndex), TaskNumber(taskIndex)) -> updateIncludeIf( task.sections.toList, task.includeIf - ) + ).zipWithIndex.map { case (s, i) => IndexedSection.SectionIndex(s, TemplateSectionIndex(i)) } } } } @@ -62,9 +64,12 @@ sealed trait FormKind extends Product with Serializable { case _ => Map.empty }.toMap private val repeatingMap = - allSections.sections.collect { case Section.RepeatingPage(page, _) => page.allIds.map(i => (i, i)) }.flatten.toMap + allSections.sections + .collect { case IndexedSection.SectionIndex(Section.RepeatingPage(page, _), _) => page.allIds.map(i => (i, i)) } + .flatten + .toMap private val groupMap = allSections.sections - .collect { case Section.NonRepeatingPage(page) => page.allFieldsNested } + .collect { case IndexedSection.SectionIndex(Section.NonRepeatingPage(page), _) => page.allFieldsNested } .flatten .collect { case fc @ IsGroup(_) => fc.childrenFormComponents.map(c => (c.id, fc.id)) } .flatten diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala index 9c47b6271..6018c3fd1 100644 --- a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala +++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumber.scala @@ -19,51 +19,68 @@ package uk.gov.hmrc.gform.sharedmodel.formtemplate import cats.Eq import cats.implicits._ import play.api.libs.json._ -import scala.util.{ Failure, Success, Try } +import scala.util.Try +import uk.gov.hmrc.gform.models.{ FormModel, Visibility } sealed trait SectionNumber extends Ordered[SectionNumber] with Product with Serializable { + def isAddToList: Boolean = this match { + case _: SectionNumber.Legacy => false + case _: SectionNumber.Classic.NormalPage => false + case _: SectionNumber.Classic.RepeatedPage => false + case _: SectionNumber.Classic.AddToListPage.DefaultPage => true + case _: SectionNumber.Classic.AddToListPage.Page => true + case _: SectionNumber.Classic.AddToListPage.CyaPage => true + case _: SectionNumber.Classic.AddToListPage.RepeaterPage => true + case SectionNumber.TaskList(_, sectionNumber) => sectionNumber.isAddToList + } + + def isAddToListRepeaterPage: Boolean = this match { + case _: SectionNumber.Classic.AddToListPage.RepeaterPage => true + case SectionNumber.TaskList(_, sectionNumber) => sectionNumber.isAddToListRepeaterPage + case _ => false + } + + def templateSectionIndex: TemplateSectionIndex = + fold(_.sectionIndex)(_.sectionNumber.sectionIndex) + def fold[B](f: SectionNumber.Classic => B)(g: SectionNumber.TaskList => B): B = this match { case n: SectionNumber.Classic => f(n) case r: SectionNumber.TaskList => g(r) + case r: SectionNumber.Legacy => throw new Exception("Trying to fold over Legacy") } def isTaskList: Boolean = fold(_ => false)(_ => true) override def compare(that: SectionNumber): Int = (this, that) match { - case (SectionNumber.Classic(sn0), SectionNumber.Classic(sn1)) => sn0.compare(sn1) + case (a: SectionNumber.Classic, b: SectionNumber.Classic) => a.compareClassic(b) case (SectionNumber.TaskList(Coordinates(tsn0, tn0), sn0), SectionNumber.TaskList(Coordinates(tsn1, tn1), sn1)) => if (tsn0 === tsn1 && tn0 === tn1) sn0.compare(sn1) else if (tsn0 === tsn1) tn0.compare(tn1) else tsn0.compare(tsn1) - - case (l, r) => throw new Exception(s"Cannot compare SectionNumber: $l with $r") + case (a: SectionNumber.Legacy, _) => -1 + case (_, a: SectionNumber.Legacy) => 1 + case (l, r) => throw new Exception(s"Cannot compare SectionNumber: $l with $r") } - def toCoordinates: Option[Coordinates] = - fold[Option[Coordinates]](_ => None)(taskList => Some(taskList.coordinates)) + def maybeCoordinates: Option[Coordinates] = + fold(_ => Option.empty[Coordinates])(tl => Some(tl.coordinates)) def toCoordinatesUnsafe: Coordinates = fold[Coordinates](_ => throw new Exception(s"Cannot convert $this to coordinates"))(_.coordinates) - def contains(coordinates: Coordinates): Boolean = toCoordinates.fold(false)(_ === coordinates) + def contains(coordinates: Coordinates): Boolean = maybeCoordinates.fold(false)(_ === coordinates) - def increment: SectionNumber = fold[SectionNumber] { classic => - SectionNumber.Classic(classic.sectionNumber + 1) - } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) => - SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber + 1) - } - def decrement: SectionNumber = fold[SectionNumber] { classic => - SectionNumber.Classic(classic.sectionNumber - 1) - } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) => - SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber - 1) - } - - def +(i: Int): SectionNumber = fold[SectionNumber] { classic => - SectionNumber.Classic(classic.sectionNumber + i) - } { case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) => - SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber + i) + def increment(formModel: FormModel[Visibility]): SectionNumber = { + val as = formModel.availableSectionNumbers + val indexOfThis: Int = as.indexOf(this) + val nextIndex = indexOfThis + 1 + if (as.size === nextIndex) { + this + } else { + formModel.availableSectionNumbers(nextIndex) + } } def unsafeToClassic: SectionNumber.Classic = @@ -72,44 +89,203 @@ sealed trait SectionNumber extends Ordered[SectionNumber] with Product with Seri fold(classic => throw new Exception("unsafeToTaskList invoked on Classic type: " + classic))(identity) def value: String = this match { - case SectionNumber.Classic(value) => value.toString + case SectionNumber.Classic.NormalPage(TemplateSectionIndex(sectionIndex)) => "n" + sectionIndex.toString + case SectionNumber.Classic.AddToListPage.DefaultPage(TemplateSectionIndex(sectionIndex)) => + "ad" + sectionIndex.toString + case SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(sectionIndex), iterationNumber, pageNumber) => + "ap" + sectionIndex.toString + "." + iterationNumber.toString + "." + pageNumber.toString + case SectionNumber.Classic.AddToListPage.CyaPage(TemplateSectionIndex(sectionIndex), iterationNumber) => + "ac" + sectionIndex.toString + "." + iterationNumber.toString + case SectionNumber.Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(sectionIndex), iterationNumber) => + "ar" + sectionIndex.toString + "." + iterationNumber.toString + case SectionNumber.Classic.RepeatedPage(TemplateSectionIndex(sectionIndex), pageNumber) => + "r" + sectionIndex.toString + "." + pageNumber.toString case SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), sectionNumber) => - List(taskSectionNumber.value, taskNumber.value, sectionNumber).mkString(",") - } + List(taskSectionNumber.value.toString, taskNumber.value.toString, sectionNumber.value).mkString(",") - def numberValue: Int = this match { - case SectionNumber.Classic(value) => value - case SectionNumber.TaskList(coordinates, sectionNumber) => coordinates.numberValue + sectionNumber + case r: SectionNumber.Legacy => throw new Exception("Trying to render Legacy") } } object SectionNumber { - final case class Classic(sectionNumber: Int) extends SectionNumber + + sealed trait Classic extends SectionNumber with Product with Serializable { + def sectionIndex: TemplateSectionIndex + + def compareClassic(that: Classic): Int = (this, that) match { + case (Classic.NormalPage(sn0), Classic.NormalPage(sn1)) => sn0.index.compare(sn1.index) + case (Classic.NormalPage(sn0), Classic.AddToListPage.DefaultPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.NormalPage(sn0), Classic.AddToListPage.Page(sn1, _, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.NormalPage(sn0), Classic.AddToListPage.CyaPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.NormalPage(sn0), Classic.AddToListPage.RepeaterPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.NormalPage(sn0), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => sn0.index.compare(sn1.index) + case (Classic.AddToListPage.DefaultPage(sn0), Classic.NormalPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.Page(sn0, _, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.CyaPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.RepeaterPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.RepeatedPage(sn0, _), Classic.NormalPage(sn1)) if sn0 =!= sn1 => sn0.index.compare(sn1.index) + case (Classic.AddToListPage.DefaultPage(sn0), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.Page(sn0, _, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.CyaPage(sn0, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.AddToListPage.RepeaterPage(sn0, _), Classic.RepeatedPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.DefaultPage(sn1)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.Page(sn1, _, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.CyaPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (Classic.RepeatedPage(sn0, _), Classic.AddToListPage.RepeaterPage(sn1, _)) if sn0 =!= sn1 => + sn0.index.compare(sn1.index) + case (a: Classic.AddToListPage, b: Classic.AddToListPage) => a.compareAddToListPage(b) + case (Classic.RepeatedPage(sn0, pn0), Classic.RepeatedPage(sn1, pn1)) => + if (sn0 === sn1) pn0.compare(pn1) + else sn0.index.compare(sn1.index) + case (l, r) => throw new Exception(s"SectionNumber $l cannot have same sectionIndex as $r") + } + } + object Classic { + case class NormalPage(sectionIndex: TemplateSectionIndex) extends Classic + case class RepeatedPage(sectionIndex: TemplateSectionIndex, pageNumber: Int) extends Classic + sealed trait AddToListPage extends Classic { + def compareAddToListPage(that: AddToListPage): Int = (this, that) match { + case (AddToListPage.DefaultPage(sn0), AddToListPage.DefaultPage(sn1)) => compDef(0, sn0, sn1) + case (AddToListPage.DefaultPage(sn0), AddToListPage.Page(sn1, _, _)) => compDef(-1, sn0, sn1) + case (AddToListPage.Page(sn0, _, _), AddToListPage.DefaultPage(sn1)) => compDef(1, sn0, sn1) + case (AddToListPage.DefaultPage(sn0), AddToListPage.CyaPage(sn1, _)) => compDef(-1, sn0, sn1) + case (AddToListPage.CyaPage(sn1, _), AddToListPage.DefaultPage(sn0)) => compDef(1, sn0, sn1) + case (AddToListPage.DefaultPage(sn0), AddToListPage.RepeaterPage(sn1, _)) => compDef(-1, sn0, sn1) + case (AddToListPage.RepeaterPage(sn0, _), AddToListPage.DefaultPage(sn1)) => compDef(1, sn0, sn1) + case (AddToListPage.CyaPage(sn0, in0), AddToListPage.Page(sn1, in1, _)) => comp(1, sn0, in0, sn1, in1) + case (AddToListPage.Page(sn0, in0, _), AddToListPage.CyaPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1) + case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.Page(sn1, in1, _)) => comp(1, sn0, in0, sn1, in1) + case (AddToListPage.Page(sn0, in0, _), AddToListPage.RepeaterPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1) + case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.CyaPage(sn1, in1)) => comp(1, sn0, in0, sn1, in1) + case (AddToListPage.CyaPage(sn0, in0), AddToListPage.RepeaterPage(sn1, in1)) => comp(-1, sn0, in0, sn1, in1) + case (AddToListPage.RepeaterPage(sn0, in0), AddToListPage.RepeaterPage(sn1, in1)) => comp(0, sn0, in0, sn1, in1) + case (AddToListPage.CyaPage(sn0, in0), AddToListPage.CyaPage(sn1, in1)) => comp(0, sn0, in0, sn1, in1) + case (AddToListPage.Page(sn0, in0, pn0), AddToListPage.Page(sn1, in1, pn1)) => + comp(pn0.compare(pn1), sn0, in0, sn1, in1) + } + + private def compDef(default: Int, sn0: TemplateSectionIndex, sn1: TemplateSectionIndex): Int = + if (sn0 === sn1) default + else sn0.index.compare(sn1.index) + + private def comp(default: Int, sn0: TemplateSectionIndex, in0: Int, sn1: TemplateSectionIndex, in1: Int): Int = + if (sn0 === sn1 && in0 === in1) default + else if (sn0 === sn1) in0.compare(in1) + else sn0.index.compare(sn1.index) + } + + object AddToListPage { + case class DefaultPage(sectionIndex: TemplateSectionIndex) extends AddToListPage + case class Page(sectionIndex: TemplateSectionIndex, iterationNumber: Int, pageNumber: Int) extends AddToListPage + case class CyaPage(sectionIndex: TemplateSectionIndex, iterationNumber: Int) extends AddToListPage + case class RepeaterPage(sectionIndex: TemplateSectionIndex, iterationNumber: Int) extends AddToListPage + } + implicit val equal: Eq[SectionNumber.Classic] = Eq.fromUniversalEquals + } + + final case class Legacy( + sectionNumber: String + ) extends SectionNumber + final case class TaskList( coordinates: Coordinates, - sectionNumber: Int + sectionNumber: Classic ) extends SectionNumber - val classicZero = Classic(0) - val taskListZero = TaskList(Coordinates(TaskSectionNumber(0), TaskNumber(0)), 0) + object TaskList { + implicit val equal: Eq[SectionNumber.TaskList] = Eq.fromUniversalEquals + } + + val classicZero = Classic.NormalPage(TemplateSectionIndex(0)) + val taskListZero = TaskList(Coordinates(TaskSectionNumber(0), TaskNumber(0)), classicZero) implicit val equal: Eq[SectionNumber] = Eq.fromUniversalEquals - implicit val format: Format[SectionNumber] = Format[SectionNumber]( - Reads[SectionNumber] { + implicit val format: Format[SectionNumber.Classic] = Format[SectionNumber.Classic]( + Reads[SectionNumber.Classic] { case JsString(value) => - parse(value) match { - case Success(sectionNumber) => JsSuccess(sectionNumber) - case Failure(error) => JsError(s"Invalid section number: $value. Error: $error") + parseClassic(value) match { + case Some(sectionNumber) => JsSuccess(sectionNumber) + case None => JsError(s"Invalid section number: $value") } case unknown => JsError(s"JsString value expected, got: $unknown") }, - Writes[SectionNumber](a => JsString(a.value)) + Writes[SectionNumber.Classic](a => JsString(a.value)) ) - def parse(value: String): Try[SectionNumber] = - value.split(",").toList.traverse(n => Try(n.toInt)).collect { - case sectionNumber :: Nil => SectionNumber.Classic(sectionNumber) - case taskSectionNumber :: taskNumber :: sectionNumber :: Nil => - SectionNumber.TaskList(Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber)), sectionNumber) + implicit val writer: Writes[SectionNumber] = Writes[SectionNumber](a => JsString(a.value)) + + // format: off + private val NormalPageRegex = "^n(\\d+)$".r + private val AddToListDefaultPageRegex = "^ad(\\d+)$".r + private val AddToListPageRegex = "^ap(\\d+)\\.(\\d+)\\.(\\d+)$".r + private val AddToListCyaPageRegex = "^ac(\\d+)\\.(\\d+)$".r + private val AddToListRepeaterPageRegex = "^ar(\\d+)\\.(\\d+)$".r + private val RepeatedPageRegex = "^r(\\d+)\\.(\\d+)$".r + // format: on + + def parseClassic(string: String): Option[SectionNumber.Classic] = + string match { + case NormalPageRegex(sectionIndex) => + Some(SectionNumber.Classic.NormalPage(TemplateSectionIndex(sectionIndex.toInt))) + case AddToListDefaultPageRegex(sectionIndex) => + Some(SectionNumber.Classic.AddToListPage.DefaultPage(TemplateSectionIndex(sectionIndex.toInt))) + case AddToListPageRegex(sectionIndex, iterationNumber, pageNumber) => + Some( + SectionNumber.Classic.AddToListPage + .Page(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt, pageNumber.toInt) + ) + case AddToListCyaPageRegex(sectionIndex, iterationNumber) => + Some( + SectionNumber.Classic.AddToListPage + .CyaPage(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt) + ) + case AddToListRepeaterPageRegex(sectionIndex, iterationNumber) => + Some( + SectionNumber.Classic.AddToListPage + .RepeaterPage(TemplateSectionIndex(sectionIndex.toInt), iterationNumber.toInt) + ) + case RepeatedPageRegex(sectionIndex, pageNumber) => + Some(SectionNumber.Classic.RepeatedPage(TemplateSectionIndex(sectionIndex.toInt), pageNumber.toInt)) + case _ => None + } + + def parse(string: String): Option[SectionNumber] = + if (string.contains(",")) { + string.split(",").toList match { + case maybeTaskSectionNumber :: maybeTaskNumber :: sectionNumber :: Nil => + ( + Try(maybeTaskSectionNumber.toInt).toOption, + Try(maybeTaskNumber.toInt).toOption, + parseClassic(sectionNumber) + ) match { + case (Some(taskSectionNumber), Some(taskNumber), Some(sn: SectionNumber.Classic)) => + Some( + SectionNumber + .TaskList( + Coordinates(TaskSectionNumber(taskSectionNumber), TaskNumber(taskNumber)), + sn + ) + ) + case _ => None + } + case _ => None + } + } else { + parseClassic(string) } } diff --git a/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala new file mode 100644 index 000000000..935d66f74 --- /dev/null +++ b/app/uk/gov/hmrc/gform/sharedmodel/formtemplate/TemplateSectionIndex.scala @@ -0,0 +1,25 @@ +/* + * 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 uk.gov.hmrc.gform.sharedmodel.formtemplate + +import cats.Eq + +final case class TemplateSectionIndex(index: Int) extends AnyVal + +object TemplateSectionIndex { + implicit val equal: Eq[TemplateSectionIndex] = Eq.fromUniversalEquals +} diff --git a/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala b/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala index 49442c06f..0cb25b1d7 100644 --- a/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala +++ b/app/uk/gov/hmrc/gform/summary/SummaryRenderingService.scala @@ -639,7 +639,7 @@ object SummaryRenderingService { val (accumulatedRows, summaryLists) = brackets.foldLeft((Map.empty[HtmlFormat.Appendable, List[SummaryListRow]], List.empty[HtmlFormat.Appendable])) { - case ((accumulatedRows, accList), bracket @ Bracket.AddToList(_, _)) => + case ((accumulatedRows, accList), bracket @ Bracket.AddToList(_, _, _)) => accumulatedRows.headOption match { case Some((heading, rows)) => val updatedList = @@ -668,7 +668,10 @@ object SummaryRenderingService { } } - case ((accumulatedRows, accList), Bracket.NonRepeatingPage(singleton, sectionNumber, source)) => + case ( + (accumulatedRows, accList), + Bracket.NonRepeatingPage(SingletonWithNumber(singleton, sectionNumber), source) + ) => val middleRows = middleSummaryListRows(singleton, sectionNumber) val pageTitle = getPageTitle(source, singleton.page) diff --git a/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala b/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala index 4b2078e53..3c9625df0 100644 --- a/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala +++ b/app/uk/gov/hmrc/gform/tasklist/CannotStartYetResolver.scala @@ -92,7 +92,7 @@ object CannotStartYetResolver { val dependingFcIdLookup: Map[Coordinates, Set[BaseComponentId]] = taskList.brackets.toList.map { case (coordinates, brackets) => val allExprs: List[Expr] = - brackets.toBracketsList.flatMap(_.toPlainBracket.allExprs(formModel)) + brackets.toBracketsList.flatMap(_.allExprs(formModel)) val baseComponentIds: List[BaseComponentId] = allExprs.flatMap(_.leafs(formModel)).collect { diff --git a/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala b/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala index 4689e1773..e1ef12fdc 100644 --- a/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala +++ b/app/uk/gov/hmrc/gform/tasklist/TaskListController.scala @@ -87,7 +87,7 @@ class TaskListController( sectionNumber, sectionTitle4Ga, SuppressErrors.Yes, - List(FastForward.StopAt(sectionNumber.increment)) + List(FastForward.StopAt(sectionNumber.increment(formModelOptics.formModelVisibilityOptics.formModel))) ) if (isCompleted) { @@ -106,10 +106,18 @@ class TaskListController( ) ).pure[Future] } else { + val coordinates = Coordinates(taskSectionNumber, taskNumber) val formModel = formModelOptics.formModelVisibilityOptics.formModel - val nextVisibleSectionNumber = formModel.taskList.nextVisibleSectionNumber( - SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), 0) - ) + + val maybeSn: Option[SectionNumber] = formModel.availableSectionNumbers.collectFirst { + case sectionNumber if sectionNumber.maybeCoordinates.contains(coordinates) => sectionNumber + } + + val nextVisibleSectionNumber = + maybeSn.getOrElse( + throw new Exception(s"Cannot determine first sectionNumber for task with $coordinates") + ) + val isAddToListSectionNumber = formModel.addToListSectionNumbers.contains(nextVisibleSectionNumber) val sn = @@ -122,7 +130,17 @@ class TaskListController( Redirect(sectionUrl(sn)).pure[Future] } } else { - val sn = SectionNumber.TaskList(Coordinates(taskSectionNumber, taskNumber), 0) + + val coordinates = Coordinates(taskSectionNumber, taskNumber) + + val formModel = formModelOptics.formModelVisibilityOptics.formModel + + val maybeSn: Option[SectionNumber] = formModel.availableSectionNumbers.collectFirst { + case sectionNumber if sectionNumber.maybeCoordinates.contains(coordinates) => sectionNumber + } + val sn = + maybeSn.getOrElse(throw new Exception(s"Cannot determine first sectionNumber for task with $coordinates")) + if (cache.formTemplate.isSpecimen) { Redirect(sectionUrl(sn)).pure[Future] diff --git a/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html b/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html index d5a473daa..b2efa7c4b 100644 --- a/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html +++ b/app/uk/gov/hmrc/gform/views/debug/modelOptic.scala.html @@ -21,37 +21,77 @@ - -

@modelName

- + +

@modelName

+ - @for(columnNumber <- 0 until totalColumns) { - @defining(SectionNumber.Classic(columnNumber)) { sectionNumber => - @if(formModel.availableSectionNumbers.contains(sectionNumber)) { - @defining(formModel(sectionNumber)) { pageModel => - @pageModelHeader(pageModel) - } - } else { - - hidden - - } - } - } + @formModel.brackets.fold { classic => + @classic.brackets.toList.map { bracket => + @bracket.fold { nonRepeatingPage => + @nonRepeatingPage.singleton.sectionNumber.value + } { repeatingPage => + RepeatingPage TODO implement me + } { addToList => + @addToList.defaultPage.map { dp => @dp.sectionNumber.value}.getOrElse("") + @addToList.iterations.toList.map { it => + @it.singletons.toList.map{singleton => @singleton.sectionNumber.value} + @it.checkYourAnswers.map{cya => @cya.sectionNumber.value}.getOrElse("") + @it.repeater.sectionNumber.value + } + } + } + } { taskList => + TaskList TODO implement me + } + + + @formModel.brackets.fold { classic => + @classic.brackets.toList.map { bracket => + @bracket.fold { nonRepeatingPage => + @pageModelHeader(nonRepeatingPage.singleton.singleton) + } { repeatingPage => + RepeatingPage TODO implement me + } { addToList => + @addToList.defaultPage.map { dp => @pageModelHeader(dp.singleton)}.getOrElse("") + @addToList.iterations.toList.map { it => + @it.singletons.toList.map{singleton => @pageModelHeader(singleton.singleton)} + @it.checkYourAnswers.map{cya => @pageModelHeader(cya.checkYourAnswers)}.getOrElse("") + @pageModelHeader(it.repeater.repeater) + } + } + } + } { taskList => + TaskList TODO implement me + } + + + @for(columnNumber <- 0 until totalColumns) { + @defining(SectionNumber.Classic.NormalPage(TemplateSectionIndex(columnNumber))) { sectionNumber => + @if(formModel.availableSectionNumbers.contains(sectionNumber)) { + @defining(formModel(sectionNumber)) { pageModel => + @pageModelHeader(pageModel) + } + } else { + - hidden - + } + } + } - @for(columnNumber <- 0 until totalColumns) { - @defining(SectionNumber.Classic(columnNumber)) { sectionNumber => - @if(formModel.availableSectionNumbers.contains(sectionNumber)) { - @defining(formModel(sectionNumber)) { pageModel => - @pageModelBody(pageModel) - } - } else { - - } - } - } + @for(columnNumber <- 0 until totalColumns) { + @defining(SectionNumber.Classic.NormalPage(TemplateSectionIndex(columnNumber))) { sectionNumber => + @if(formModel.availableSectionNumbers.contains(sectionNumber)) { + @defining(formModel(sectionNumber)) { pageModel => + @pageModelBody(pageModel) + } + } else { + + } + } + } diff --git a/app/uk/gov/hmrc/gform/views/specimen/navigation.scala.html b/app/uk/gov/hmrc/gform/views/specimen/navigation.scala.html index 20be9887d..8a489b6d3 100644 --- a/app/uk/gov/hmrc/gform/views/specimen/navigation.scala.html +++ b/app/uk/gov/hmrc/gform/views/specimen/navigation.scala.html @@ -15,8 +15,8 @@ *@ @import cats.syntax.eq._ -@import cats.instances.int._ @import views.html.helper.CSPNonce +@import uk.gov.hmrc.gform.gform.SpecimenLinks @import uk.gov.hmrc.gform.models.{ DataExpanded, PageModel } @import uk.gov.hmrc.gform.sharedmodel.LangADT @import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ ExprPrettyPrint, IncludeIf, FormTemplate, SectionNumber, SectionTitle4Ga } @@ -24,56 +24,50 @@ @(formTemplate: FormTemplate, sectionNumber: SectionNumber.Classic, pages: List[(PageModel[DataExpanded], SectionNumber.Classic)], + specimenLinks: SpecimenLinks, maybeIncludeIf: Option[IncludeIf] )(implicit l: LangADT, sse: SmartStringEvaluator, requestHeader: RequestHeader) -@navigationLink(sectionIndex: Int, label: String, templateId: String) = { - @defining(pages(sectionIndex)) { case (page, sectionNumber) => - @defining(SectionTitle4Ga.sectionTitle4GaFactory(page, sectionNumber).value) { sectionTitle4Ga => - @label - } +@navigationLink(page: PageModel[DataExpanded], sectionNumber: SectionNumber.Classic, label: String, templateId: String) = { + @defining(SectionTitle4Ga.sectionTitle4GaFactory(page, sectionNumber).value) { sectionTitle4Ga => + @label } } @defining( formTemplate._id.value, - pages(sectionNumber.sectionNumber), - sectionNumber.sectionNumber, - pages.size) { case (templateId, section, sectionNumber, total) => - + pages.find(sn => sn._2 === sectionNumber), + pages.size +) { case (templateId, Some((pageModel, _)), total) =>
- @if(sectionNumber >= 1) { - @navigationLink(sectionNumber - 1, "Previous", templateId) - } else { - Previous + @specimenLinks.previous.fold(Html("Previous")) { previous => + @navigationLink(pageModel, previous, "Previous", templateId) } - @if(sectionNumber < total - 1) { - @navigationLink(sectionNumber + 1, "Next", templateId) - } else { - Next + @specimenLinks.next.fold(Html("Next")) { next => + @navigationLink(pageModel, next, "Next", templateId) }
@maybeIncludeIf.fold("")(includeIf => "Only visible if: " + ExprPrettyPrint.prettyPrintBooleanExpr(includeIf.booleanExpr))
- - } +} diff --git a/app/uk/gov/hmrc/gform/views/specimen/navigation_tasklist.scala.html b/app/uk/gov/hmrc/gform/views/specimen/navigation_tasklist.scala.html index 716a87193..8f79f10b9 100644 --- a/app/uk/gov/hmrc/gform/views/specimen/navigation_tasklist.scala.html +++ b/app/uk/gov/hmrc/gform/views/specimen/navigation_tasklist.scala.html @@ -15,7 +15,7 @@ *@ @import cats.syntax.eq._ -@import cats.instances.int._ +@import uk.gov.hmrc.gform.gform.SpecimenLinks @import uk.gov.hmrc.gform.models.{ DataExpanded, PageModel } @import uk.gov.hmrc.gform.sharedmodel.LangADT @import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FormTemplate, SectionNumber, SectionTitle4Ga } @@ -26,65 +26,67 @@ @(formTemplate: FormTemplate, taskList: SectionNumber.TaskList, pages: List[(PageModel[DataExpanded], SectionNumber.TaskList)], - tasks: List[Task] + tasks: List[(Task, SectionNumber)], + specimenLinks: SpecimenLinks, )(implicit l: LangADT, sse: SmartStringEvaluator, requestHeader: RequestHeader) -@navigationLink(sectionNumber: SectionNumber, label: String, templateId: String) = { - @defining(pages(taskList.sectionNumber)) { case (page, pageSectionNumber) => - @defining(SectionTitle4Ga.sectionTitle4GaFactory(page, taskList).value) { sectionTitle4Ga => - @label - } - } +@navigationLink(page: PageModel[DataExpanded], sectionNumber: SectionNumber.TaskList, label: String, templateId: String) = { + @defining(SectionTitle4Ga.sectionTitle4GaFactory(page, sectionNumber).value) { sectionTitle4Ga => + @label + } } -@defining(formTemplate._id.value, pages.size) { case (templateId, total) => +@defining( + formTemplate._id.value, + pages.find(sn => sn._2 === taskList), + pages.size +) { case (templateId, Some((pageModel, _)), total) =>
-
Tasks
-
- + @tasks.map { case (task, sn) => + @defining(if(taskList.maybeCoordinates === sn.maybeCoordinates) "selected" else ""){ case selected => + - }) - } - -
-
-
- @if(taskList.sectionNumber >= 1) { @navigationLink(taskList.decrement, "Previous", templateId) } else { Previous } -
-
- +
+
+
+ @specimenLinks.previous.fold(Html("Previous")) { previous => + @navigationLink(pageModel, SectionNumber.TaskList(taskList.coordinates, previous), "Previous", templateId) + } +
+
+ -
-
- @if(taskList.sectionNumber < total - 1) { @navigationLink(taskList.increment, "Next", templateId) } else { Next } -
-
+ } + +
+
+ @specimenLinks.next.fold(Html("Next")) { next => + @navigationLink(pageModel, SectionNumber.TaskList(taskList.coordinates, next), "Next", templateId) + } +
+
} diff --git a/builder/src/add-to-list/repeater.tsx b/builder/src/add-to-list/repeater.tsx index 2c5d0838b..cf9a1019d 100644 --- a/builder/src/add-to-list/repeater.tsx +++ b/builder/src/add-to-list/repeater.tsx @@ -448,18 +448,6 @@ const RepeaterControllerFactory = setWindowDisplayed(false); }; - const increaseSectionNumber = (): SectionNumber => { - const updatedsectionNumber: SectionNumber = SectionNumber.increase(sectionNumber, atlIterationIndex); - sectionNumberChangeSignal.value = updatedsectionNumber; - return updatedsectionNumber; - }; - - const descreaseSectionNumber = (): SectionNumber => { - const updatedsectionNumber: SectionNumber = SectionNumber.decrease(sectionNumber, atlIterationIndex); - sectionNumberChangeSignal.value = updatedsectionNumber; - return updatedsectionNumber; - }; - const addDefaultPage = (e: MouseEvent) => { const atlRepeaterPart: AtlRepeater = {}; @@ -478,11 +466,8 @@ const RepeaterControllerFactory = repeater.defaultPage = atlRepeaterPart["defaultPage"]; // Update local model - const sectionNumberAfterUpdate: SectionNumber = increaseSectionNumber(); + refreshRepeaterWith(false, atlRepeaterPart, sectionNumber); - refreshRepeaterWith(false, atlRepeaterPart, sectionNumberAfterUpdate); - - sectionNumber = sectionNumberAfterUpdate; setDefaultPageExists(true); }; @@ -493,11 +478,8 @@ const RepeaterControllerFactory = repeater.defaultPage = undefined; // Update local model - const sectionNumberAfterUpdate = descreaseSectionNumber(); - - refreshRepeaterWith(false, atlRepeaterPart, sectionNumberAfterUpdate); + refreshRepeaterWith(false, atlRepeaterPart, sectionNumber); - sectionNumber = sectionNumberAfterUpdate; setDefaultPageExists(false); }; @@ -511,11 +493,8 @@ const RepeaterControllerFactory = repeater.cyaPage = atlRepeaterPart["cyaPage"]; // Update local model - const sectionNumberAfterUpdate = increaseSectionNumber(); + refreshRepeaterWith(false, atlRepeaterPart, sectionNumber); - refreshRepeaterWith(false, atlRepeaterPart, sectionNumberAfterUpdate); - - sectionNumber = sectionNumberAfterUpdate; setCyaPageExists(true); }; @@ -526,11 +505,8 @@ const RepeaterControllerFactory = repeater.cyaPage = undefined; // Update local model - const sectionNumberAfterUpdate = descreaseSectionNumber(); - - refreshRepeaterWith(false, atlRepeaterPart, sectionNumberAfterUpdate); + refreshRepeaterWith(false, atlRepeaterPart, sectionNumber); - sectionNumber = sectionNumberAfterUpdate; setCyaPageExists(false); }; diff --git a/builder/src/section/index.ts b/builder/src/section/index.ts index 6a86b193d..7839cc0d2 100644 --- a/builder/src/section/index.ts +++ b/builder/src/section/index.ts @@ -11,19 +11,41 @@ export const activateSectionBuilder = (urlMatch: RegExpMatchArray, url: string) const host = urlMatch![1]; const formTemplateId: FormTemplateId = urlMatch![2]; - const constructPageIdFromString = (s: string): SectionNumber => { - const pattern = /^\d+,\d+,\d+$/; - const parsedNumber = parseInt(s); + const normalPagePattern = /^n(\d+)$/; + const atlPagePattern = /^ap(\d+).(\d+).(\d+)$/; + const atlDefaultPagePattern = /^ad(\d+)$/; + const atlCyaPagePattern = /^ac(\d+).(\d+)$/; + const atlRepeaterPagePattern = /^ar(\d+).(\d+)$/; - if (pattern.test(s)) { - return SectionNumber.TaskListSectionNumber(s); - } else if (!isNaN(parsedNumber)) { - return SectionNumber.RegularSectionNumber(parsedNumber); + const toSectionNumber = (s: string): SectionNumber => { + let match; + if ((match = normalPagePattern.exec(s)) !== null) { + return SectionNumber.NormalPage(parseInt(match[1])); + } else if ((match = atlPagePattern.exec(s)) !== null) { + return SectionNumber.AddToListPage(parseInt(match[1]), parseInt(match[2]), parseInt(match[3])); + } else if ((match = atlDefaultPagePattern.exec(s)) !== null) { + return SectionNumber.AddToListDefaultPage(parseInt(match[1])); + } else if ((match = atlCyaPagePattern.exec(s)) !== null) { + return SectionNumber.AddToListCyaPage(parseInt(match[1]), parseInt(match[2])); + } else if ((match = atlRepeaterPagePattern.exec(s)) !== null) { + return SectionNumber.AddToListRepeaterPage(parseInt(match[1]), parseInt(match[2])); } else { throw new Error(`Cannot convert ${s} into SectionNumber`); } }; + const constructPageIdFromString = (s: string): SectionNumber => { + const taskListPattern = /^(\d+),(\d+),([a-z.0-9]+)$/; + + let match; + if ((match = taskListPattern.exec(s)) !== null) { + const sn = toSectionNumber(match[3]); + return SectionNumber.TaskListSectionNumber(parseInt(match[1]), parseInt(match[2]), sn); + } else { + return toSectionNumber(s); + } + }; + const queryParams = urlMatch![4]; const urlParams = new URLSearchParams("?" + queryParams); const nParam: string | null = urlParams.get("n"); diff --git a/builder/src/summary/index.tsx b/builder/src/summary/index.tsx index 723483c41..45502ad8e 100644 --- a/builder/src/summary/index.tsx +++ b/builder/src/summary/index.tsx @@ -216,8 +216,12 @@ export const activateSummaryBuilder = (urlMatch: RegExpMatchArray, url: string) if (summarySection.fields !== undefined) { const sectionNumber = coordinates === null - ? SectionNumber.RegularSectionNumber(0) - : SectionNumber.TaskListSectionNumber(coordinates.taskSectionNumber + "," + coordinates.taskNumber); + ? SectionNumber.NormalPage(0) + : SectionNumber.TaskListSectionNumber( + coordinates.taskSectionNumber, + coordinates.taskNumber, + SectionNumber.NormalPage(0), + ); const params: InfoRenderParam = { host, formTemplateId, diff --git a/builder/src/task-landing-page/index.tsx b/builder/src/task-landing-page/index.tsx index be63fd8e5..7c85cd426 100644 --- a/builder/src/task-landing-page/index.tsx +++ b/builder/src/task-landing-page/index.tsx @@ -33,11 +33,11 @@ export const activateTaskLandingPageBuilder = (urlMatch: RegExpMatchArray, url: inputFormComponentId: string, taskSectionHeadingEl: HTMLHeadingElement, taskSection: TaskSection, - sectionNumber: SectionNumber, + taskSectionNumber: number, maybeAccessCode: string | null, ) => { const attachmentDiv = document.createElement("div"); - attachmentDiv.setAttribute("id", `task-list-section-${SectionNumber.asString(sectionNumber)}-shadow-root`); + attachmentDiv.setAttribute("id", `task-list-section-${taskSectionNumber}-shadow-root`); taskSectionHeadingEl.insertAdjacentElement("afterend", attachmentDiv); const content: HTMLDivElement | undefined = attachShadowDom(attachmentDiv); if (content !== undefined) { @@ -46,7 +46,7 @@ export const activateTaskLandingPageBuilder = (urlMatch: RegExpMatchArray, url: host, taskSection, taskSectionHeadingEl, - sectionNumber, + taskSectionNumber, maybeAccessCode, ); render(, content); @@ -108,17 +108,16 @@ export const activateTaskLandingPageBuilder = (urlMatch: RegExpMatchArray, url: const formTemplateData: FormTemplate = replaceWithEnglishValue(formTemplate) as FormTemplate; const taskListSectionNl: NodeListOf = document.querySelectorAll(".govuk-task-list"); - formTemplateData.sections.forEach((section, index) => { - const taskListSectionEl = taskListSectionNl.item(index); + formTemplateData.sections.forEach((section, taskSectionNumnber) => { + const taskListSectionEl = taskListSectionNl.item(taskSectionNumnber); const taskSection = section as TaskSection; - const sectionNumber = SectionNumber.TaskListSectionNumber(index.toString()); if (taskListSectionEl.previousElementSibling) { initiateTaskListSection( formTemplateId, taskListSectionEl.previousElementSibling as HTMLHeadingElement, taskSection, - sectionNumber, + taskSectionNumnber, accessCode, ); } diff --git a/builder/src/task-landing-page/task-list-section-controller.tsx b/builder/src/task-landing-page/task-list-section-controller.tsx index 86cda1d6b..ce8282070 100644 --- a/builder/src/task-landing-page/task-list-section-controller.tsx +++ b/builder/src/task-landing-page/task-list-section-controller.tsx @@ -1,6 +1,7 @@ import * as styles from "bundle-text:../section/content.css"; import { useEffect, useRef, useState } from "preact/hooks"; import { + Coordinates, ContentScriptRequest, UpdateTaskList, TaskSection, @@ -18,8 +19,8 @@ import { MessageKind, SmartString } from "../types"; import { SmartStringDiv, SmartStringInputDeprecated } from "../section/useSmartString"; import { onMessageHandler } from "../background/index"; -const taskPath = (sectionNumber: SectionNumber, taskNumber: number): string => { - return ".sections[" + SectionNumber.asString(sectionNumber) + "].tasks[" + taskNumber.toString() + "]"; +const taskPath = (coordinates: Coordinates): string => { + return ".sections[" + coordinates.taskSectionNumber.toString() + "].tasks[" + coordinates.taskNumber.toString() + "]"; }; export const TaskListSectionControllerFactory = @@ -28,7 +29,7 @@ export const TaskListSectionControllerFactory = host: string, taskSection: TaskSection, taskSectionHeadingElement: HTMLHeadingElement, - sectionNumber: SectionNumber, + taskSectionNumber: number, maybeAccessCode: string | null, ) => () => { @@ -95,7 +96,11 @@ export const TaskListSectionControllerFactory = if (taskCaptionInput.current !== null) { taskSectionPayload["caption"] = taskCaptionInput.current.value; } - const sectionPath = taskPath(sectionNumber, taskNumber); + const coordinates: Coordinates = { + taskSectionNumber: taskSectionNumber, + taskNumber: taskNumber, + }; + const sectionPath = taskPath(coordinates); const payload: UpdateByPath = { payload: taskSectionPayload, path: sectionPath, @@ -111,7 +116,11 @@ export const TaskListSectionControllerFactory = taskSectionPayload["title"] = taskSummarySectionInput.current.checked ? "Check your answers" : ""; } - const sectionPath = taskPath(sectionNumber, taskNumber); + const coordinates: Coordinates = { + taskSectionNumber: taskSectionNumber, + taskNumber: taskNumber, + }; + const sectionPath = taskPath(coordinates); const payload: UpdateByPath = { payload: taskSectionPayload, path: sectionPath, @@ -133,8 +142,11 @@ export const TaskListSectionControllerFactory = taskSectionPayload["fields"] = []; } } - - const sectionPath = taskPath(sectionNumber, taskNumber); + const coordinates: Coordinates = { + taskSectionNumber: taskSectionNumber, + taskNumber: taskNumber, + }; + const sectionPath = taskPath(coordinates); const payload: UpdateByPath = { payload: taskSectionPayload, path: sectionPath, @@ -152,9 +164,13 @@ export const TaskListSectionControllerFactory = taskSectionPayload["title"] = value; } - const sectionPath = ".sections[" + SectionNumber.asString(sectionNumber) + "]"; + const sectionPath = ".sections[" + taskSectionNumber + "]"; - const taskListSectionNumber = SectionNumber.TaskListSectionNumber(`${SectionNumber.asString(sectionNumber)},0,0`); + const taskListSectionNumber = SectionNumber.TaskListSectionNumber( + taskSectionNumber, + 0, + SectionNumber.NormalPage(0), + ); // only taskSectionNumber matters const taskSectionUpdates: UpdateByPath = { payload: taskSectionPayload, @@ -191,8 +207,7 @@ export const TaskListSectionControllerFactory = if (response.taskTitles !== undefined) { response.taskTitles.map((taskTitle, index) => { const liIndex = index + 1; - const ulIndex = SectionNumber.asString(SectionNumber.increase(sectionNumber, 1)); - const selector = `div > :nth-child(${ulIndex} of ul) > :nth-child(${liIndex} of li) > .govuk-task-list__name-and-hint`; + const selector = `div > :nth-child(${taskSectionNumber + 1} of ul) > :nth-child(${liIndex} of li) > .govuk-task-list__name-and-hint`; const taskListNameAndHint: Element | null = document.querySelector(selector); if (taskListNameAndHint !== null) { diff --git a/builder/src/types.ts b/builder/src/types.ts index 0e73df026..7f93be3f1 100644 --- a/builder/src/types.ts +++ b/builder/src/types.ts @@ -3,66 +3,131 @@ import { Signal } from "@preact/signals"; export type FormTemplateId = string; export type FormComponentId = string; -interface RegularSectionNumber { - n: number; +interface NormalPage { + type: "normalPage"; + sectionNumber: number; +} + +interface AddToListPage { + type: "addToListPage"; + sectionNumber: number; + iterationNumber: number; + pageNumber: number; +} + +interface AddToListDefaultPage { + type: "addToListDefaultPage"; + sectionNumber: number; +} + +interface AddToListCyaPage { + type: "addToListCyaPage"; + sectionNumber: number; + iterationNumber: number; +} + +interface AddToListRepeaterPage { + type: "addToListRepeaterPage"; + sectionNumber: number; + iterationNumber: number; +} + +interface RepeatedPage { + type: "repeatedPage"; + sectionNumber: number; + pageNumber: number; } interface TaskListSectionNumber { - s: string; + type: "taskListSectionNumber"; + taskSectionNumber: number; + taskNumber: number; + sectionNumber: SectionNumber; } interface AcknowledgementSectionNumber { - a: "acknowledgement"; + type: "acknowledgement"; } -export type SectionNumber = RegularSectionNumber | TaskListSectionNumber | AcknowledgementSectionNumber; +export type SectionNumber = + | NormalPage + | AddToListDefaultPage + | AddToListPage + | AddToListCyaPage + | AddToListRepeaterPage + | RepeatedPage + | TaskListSectionNumber + | AcknowledgementSectionNumber; export const SectionNumber = { - RegularSectionNumber: (n: number): RegularSectionNumber => ({ n }), - TaskListSectionNumber: (s: string): TaskListSectionNumber => ({ s }), + NormalPage: (n: number): NormalPage => ({ type: "normalPage", sectionNumber: n }), + AddToListDefaultPage: (n: number): AddToListDefaultPage => ({ + type: "addToListDefaultPage", + sectionNumber: n, + }), + AddToListPage: (n: number, iteration: number, page: number): AddToListPage => ({ + type: "addToListPage", + sectionNumber: n, + iterationNumber: iteration, + pageNumber: page, + }), + AddToListCyaPage: (n: number, iteration: number): AddToListCyaPage => ({ + type: "addToListCyaPage", + sectionNumber: n, + iterationNumber: iteration, + }), + AddToListRepeaterPage: (n: number, iteration: number): AddToListRepeaterPage => ({ + type: "addToListRepeaterPage", + sectionNumber: n, + iterationNumber: iteration, + }), + RepeatedPage: (n: number, page: number): RepeatedPage => ({ + type: "repeatedPage", + sectionNumber: n, + pageNumber: page, + }), + TaskListSectionNumber: ( + taskSectionNumber: number, + taskNumber: number, + sectionNumber: SectionNumber, + ): TaskListSectionNumber => ({ type: "taskListSectionNumber", taskSectionNumber, taskNumber, sectionNumber }), AcknowledgementSectionNumber: (): AcknowledgementSectionNumber => ({ - a: "acknowledgement", + type: "acknowledgement", }), asString: (sn: SectionNumber): string => { - if ("n" in sn) { - return sn.n.toString(); - } else if ("a" in sn) { - return sn.a; + if (sn.type === "normalPage") { + return "n" + sn.sectionNumber; + } else if (sn.type === "addToListDefaultPage") { + return "ad" + sn.sectionNumber; + } else if (sn.type === "addToListPage") { + return "ap" + sn.sectionNumber + "." + sn.iterationNumber + "." + sn.pageNumber; + } else if (sn.type === "addToListCyaPage") { + return "ac" + sn.sectionNumber + "." + sn.iterationNumber; + } else if (sn.type === "addToListRepeaterPage") { + return "ar" + sn.sectionNumber + "." + sn.iterationNumber; + } else if (sn.type === "repeatedPage") { + return "r" + sn.sectionNumber + "." + sn.pageNumber; + } else if (sn.type === "taskListSectionNumber") { + return sn.taskSectionNumber + "," + sn.taskNumber + "," + SectionNumber.asString(sn.sectionNumber); } else { - return sn.s; + return ""; } }, - increase: (sn: SectionNumber, offset: number) => { - if ("n" in sn) { - return SectionNumber.RegularSectionNumber(sn.n + offset); - } else if ("a" in sn) { - return sn; - } else { - const lastCommaIndex = sn.s.lastIndexOf(","); - const sectionNumberOnly = parseInt(sn.s.slice(lastCommaIndex + 1)); - const newSectionNumber = sn.s.slice(0, lastCommaIndex + 1) + (sectionNumberOnly + offset); - return SectionNumber.TaskListSectionNumber(newSectionNumber); - } + isRegularListSectionNumber: (sn: SectionNumber): boolean => { + const t = sn.type; + return ( + t === "normalPage" || + t === "addToListDefaultPage" || + t === "addToListPage" || + t === "addToListCyaPage" || + t === "addToListRepeaterPage" || + t === "repeatedPage" + ); }, - - decrease: (sn: SectionNumber, offset: number) => { - if ("n" in sn) { - return SectionNumber.RegularSectionNumber(sn.n - offset); - } else if ("a" in sn) { - return sn; - } else { - const lastCommaIndex = sn.s.lastIndexOf(","); - const sectionNumberOnly = parseInt(sn.s.slice(lastCommaIndex + 1)); - const newSectionNumber = sn.s.slice(0, lastCommaIndex + 1) + (sectionNumberOnly - offset); - return SectionNumber.TaskListSectionNumber(newSectionNumber); - } - }, - - isRegularListSectionNumber: (sn: SectionNumber): boolean => "n" in sn, isTaskListSectionNumber: (sn: SectionNumber): boolean => !SectionNumber.isRegularListSectionNumber(sn), - isAcknowledgementSectionNumber: (sn: SectionNumber): boolean => "a" in sn && sn.a === "acknowledgement", + //isAcknowledgementSectionNumber: (sn: SectionNumber): boolean => "a" in sn && sn.a === "acknowledgement", }; export enum MessageKind { diff --git a/test/uk/gov/hmrc/gform/controllers/NavigationSpec.scala b/test/uk/gov/hmrc/gform/controllers/NavigationSpec.scala index 59f75b426..740b66713 100644 --- a/test/uk/gov/hmrc/gform/controllers/NavigationSpec.scala +++ b/test/uk/gov/hmrc/gform/controllers/NavigationSpec.scala @@ -123,7 +123,7 @@ class NavigationSpec extends Spec with FormModelSupport with VariadicFormDataSup val singleSection = makeSection(toSmartString("Single Page"), mkFormComponent(single)) val result = getAvailableSectionNumbers(singleSection :: Nil, mkVariadicFormData()) - result shouldBe List(Classic(0)) + result shouldBe List(Classic.NormalPage(TemplateSectionIndex(0))) } "Chain of section" should "hide all dependent section in the chain" in { @@ -146,20 +146,24 @@ class NavigationSpec extends Spec with FormModelSupport with VariadicFormDataSup ) result1 shouldBe List( - Classic(0), - Classic(1), - Classic(2), - Classic(3), - Classic(4) + Classic.NormalPage(TemplateSectionIndex(0)), + Classic.NormalPage(TemplateSectionIndex(1)), + Classic.NormalPage(TemplateSectionIndex(2)), + Classic.NormalPage(TemplateSectionIndex(3)), + Classic.NormalPage(TemplateSectionIndex(4)) ) result2 shouldBe List( - Classic(0), - Classic(1), - Classic(2), - Classic(4) + Classic.NormalPage(TemplateSectionIndex(0)), + Classic.NormalPage(TemplateSectionIndex(1)), + Classic.NormalPage(TemplateSectionIndex(2)), + Classic.NormalPage(TemplateSectionIndex(4)) ) - result3 shouldBe List(Classic(0), Classic(1), Classic(4)) - result4 shouldBe List(Classic(0), Classic(4)) + result3 shouldBe List( + Classic.NormalPage(TemplateSectionIndex(0)), + Classic.NormalPage(TemplateSectionIndex(1)), + Classic.NormalPage(TemplateSectionIndex(4)) + ) + result4 shouldBe List(Classic.NormalPage(TemplateSectionIndex(0)), Classic.NormalPage(TemplateSectionIndex(4))) } "Navigator.nextSectionNumber" should "skip ATL non repeater section and jump to RepeaterSection" in { @@ -169,13 +173,29 @@ class NavigationSpec extends Spec with FormModelSupport with VariadicFormDataSup mkVariadicFormData("fcId1" -> One("1"), "fcId2" -> One("1"), "fcIdATL" -> One("1")) ) - val ffNavigator = Navigator(Classic(0), formModel) - ffNavigator.availableSectionNumbers shouldBe List(Classic(0), Classic(1), Classic(2), Classic(3)) - ffNavigator.addToListRepeaterSectionNumbers shouldBe List(Classic(3)) - ffNavigator.addToListNonRepeaterSectionNumbers shouldBe List(Classic(2)) - ffNavigator.addToListSectionNumbers shouldBe List(Classic(2), Classic(3)) - Navigator(Classic(0), formModel).nextSectionNumber shouldBe (Classic(1)) - Navigator(Classic(1), formModel).nextSectionNumber shouldBe (Classic(3)) + val ffNavigator = Navigator(Classic.NormalPage(TemplateSectionIndex(0)), formModel) + ffNavigator.availableSectionNumbers shouldBe List( + Classic.NormalPage(TemplateSectionIndex(0)), + Classic.NormalPage(TemplateSectionIndex(1)), + Classic.AddToListPage.Page(TemplateSectionIndex(2), 1, 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(2), 1) + ) + + ffNavigator.addToListRepeaterSectionNumbers shouldBe List( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(2), 1) + ) + ffNavigator.addToListNonRepeaterSectionNumbers shouldBe List( + Classic.AddToListPage.Page(TemplateSectionIndex(2), 1, 0) + ) + ffNavigator.addToListSectionNumbers shouldBe List( + Classic.AddToListPage.Page(TemplateSectionIndex(2), 1, 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(2), 1) + ) + Navigator(Classic.NormalPage(TemplateSectionIndex(0)), formModel).nextSectionNumber shouldBe (Classic.NormalPage( + TemplateSectionIndex(1) + )) + Navigator(Classic.NormalPage(TemplateSectionIndex(1)), formModel).nextSectionNumber shouldBe (Classic.AddToListPage + .RepeaterPage(TemplateSectionIndex(2), 1)) } } diff --git a/test/uk/gov/hmrc/gform/controllers/helpers/FormDataHelpersSpec.scala b/test/uk/gov/hmrc/gform/controllers/helpers/FormDataHelpersSpec.scala index 8df3b197b..d027f6895 100644 --- a/test/uk/gov/hmrc/gform/controllers/helpers/FormDataHelpersSpec.scala +++ b/test/uk/gov/hmrc/gform/controllers/helpers/FormDataHelpersSpec.scala @@ -25,6 +25,7 @@ import uk.gov.hmrc.gform.controllers.RequestRelatedData import uk.gov.hmrc.gform.eval.{ RevealingChoiceInfo, StandaloneSumInfo, StaticTypeInfo, SumInfo } import uk.gov.hmrc.gform.graph.FormTemplateBuilder._ import uk.gov.hmrc.gform.graph.RecData +import uk.gov.hmrc.gform.models.SingletonWithNumber import uk.gov.hmrc.gform.models.ids.{ BaseComponentId, IndexedComponentId, ModelComponentId } import uk.gov.hmrc.gform.models.optics.FormModelRenderPageOptics import uk.gov.hmrc.gform.models.{ Bracket, BracketsWithSectionNumber, DataExpanded, EnteredVariadicFormData, FormModel, Singleton } @@ -377,8 +378,7 @@ class FormDataHelpersSpec extends Spec { BracketsWithSectionNumber.Classic[DataExpanded]( NonEmptyList.one( Bracket.NonRepeatingPage[DataExpanded]( - Singleton(section.page.asInstanceOf[Page[DataExpanded]]), - SectionNumber.classicZero, + SingletonWithNumber(Singleton(section.page.asInstanceOf[Page[DataExpanded]]), SectionNumber.classicZero), section ) ) diff --git a/test/uk/gov/hmrc/gform/eval/EvaluationResultsSpec.scala b/test/uk/gov/hmrc/gform/eval/EvaluationResultsSpec.scala index ae7b7a8b2..0fd9b807c 100644 --- a/test/uk/gov/hmrc/gform/eval/EvaluationResultsSpec.scala +++ b/test/uk/gov/hmrc/gform/eval/EvaluationResultsSpec.scala @@ -226,24 +226,28 @@ class EvaluationResultsSpec extends Spec with TableDrivenPropertyChecks { ( TypeInfo(LinkCtx(PageLink(PageId("page1"))), StaticTypeData(ExprType.string, None)), recDataEmpty, - buildEvaluationContext(pageIdSectionNumberMap = Map(ModelPageId.Pure("page1") -> SectionNumber.Classic(1))), - StringResult("/form/section/aaa999/-/1"), + buildEvaluationContext(pageIdSectionNumberMap = + Map(ModelPageId.Pure("page1") -> SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))) + ), + StringResult("/form/section/aaa999/-/n1"), "Eval LinkCtx(PageLink(PageId(xxx))) as string (exact match)" ), ( TypeInfo(LinkCtx(PageLink(PageId("1_page1"))), StaticTypeData(ExprType.string, None)), recDataEmpty, - buildEvaluationContext(pageIdSectionNumberMap = Map(ModelPageId.Pure("page1") -> SectionNumber.Classic(1))), - StringResult("/form/section/aaa999/-/1"), + buildEvaluationContext(pageIdSectionNumberMap = + Map(ModelPageId.Pure("page1") -> SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))) + ), + StringResult("/form/section/aaa999/-/n1"), "Eval LinkCtx(PageLink(PageId(xxx))) as string (link from repeating/add-to-list page to non-repeating page)" ), ( TypeInfo(LinkCtx(PageLink(PageId("page1"))), StaticTypeData(ExprType.string, None)), recDataEmpty, buildEvaluationContext(pageIdSectionNumberMap = - Map(ModelPageId.Indexed("page1", 1) -> SectionNumber.Classic(1)) + Map(ModelPageId.Indexed("page1", 1) -> SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))) ), - StringResult("/form/section/aaa999/-/1"), + StringResult("/form/section/aaa999/-/n1"), "Eval LinkCtx(PageLink(PageId(xxx))) as string (link from non-repeating page to repeating page)" ), ( diff --git a/test/uk/gov/hmrc/gform/models/FastForwardSpec.scala b/test/uk/gov/hmrc/gform/models/FastForwardSpec.scala index 565ea4b8f..8e5591d12 100644 --- a/test/uk/gov/hmrc/gform/models/FastForwardSpec.scala +++ b/test/uk/gov/hmrc/gform/models/FastForwardSpec.scala @@ -19,18 +19,16 @@ package uk.gov.hmrc.gform.models import org.scalatest.prop.TableDrivenPropertyChecks.{ Table, forAll } import org.scalatest.freespec.AnyFreeSpecLike import org.scalatest.matchers.should.Matchers -import org.slf4j.{ Logger, LoggerFactory } import play.api.i18n.Messages import play.api.test.Helpers import uk.gov.hmrc.gform.graph.FormTemplateBuilder.{ mkAddToListSection, mkFormComponent, page } import uk.gov.hmrc.gform.models.FastForward.StopAt import uk.gov.hmrc.gform.models.optics.DataOrigin import uk.gov.hmrc.gform.sharedmodel.{ LangADT, SourceOrigin } -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Constant, Equals, FormComponentId, FormCtx, IncludeIf, SectionNumber, Value } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Constant, Equals, FormComponentId, FormCtx, IncludeIf, SectionNumber, TemplateSectionIndex, Value } class FastForwardSpec extends AnyFreeSpecLike with FormModelSupport with VariadicFormDataSupport with Matchers { - private val logger: Logger = LoggerFactory.getLogger(getClass) implicit val lang: LangADT = LangADT.En implicit val messages: Messages = Helpers.stubMessages(Helpers.stubMessagesApi(Map.empty)) @@ -47,8 +45,8 @@ class FastForwardSpec extends AnyFreeSpecLike with FormModelSupport with Variadi ) ), List("1_fc1" -> "a"), - 1, - 2 + SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(0), 1, 0), + SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(0), 1, 1) ), ( "next section is visible (includeIf)", @@ -62,8 +60,8 @@ class FastForwardSpec extends AnyFreeSpecLike with FormModelSupport with Variadi ) ), List("1_fc1" -> "a"), - 1, - 2 + SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(0), 1, 0), + SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(0), 1, 1) ), ( "next section is hidden (includeIf)", @@ -77,20 +75,17 @@ class FastForwardSpec extends AnyFreeSpecLike with FormModelSupport with Variadi ) ), List("1_fc1" -> "a"), - 1, - 3 + SectionNumber.Classic.AddToListPage.Page(TemplateSectionIndex(0), 1, 0), + SectionNumber.Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(0), 1) ) ) forAll(table) { (description, sections, data, stopAt, expectedStopAt) => description in { - logger.info(description) val fmb = mkFormModelFromSections(sections) val variadicData = variadicFormData[SourceOrigin.OutOfDate](data: _*) val fmvo = fmb.visibilityModel[DataOrigin.Mongo, SectionSelectorType.Normal](variadicData, None) - StopAt(SectionNumber.Classic(stopAt)).next(fmvo.formModel, SectionNumber.Classic(stopAt)) shouldBe StopAt( - SectionNumber.Classic(expectedStopAt) - ) + StopAt(stopAt).next(fmvo.formModel, stopAt) shouldBe StopAt(expectedStopAt) } } } diff --git a/test/uk/gov/hmrc/gform/models/FormModelBuilderSpec.scala b/test/uk/gov/hmrc/gform/models/FormModelBuilderSpec.scala index 6ab41c059..d1207f6b2 100644 --- a/test/uk/gov/hmrc/gform/models/FormModelBuilderSpec.scala +++ b/test/uk/gov/hmrc/gform/models/FormModelBuilderSpec.scala @@ -71,16 +71,28 @@ class FormModelBuilderSpec extends AnyFlatSpecLike with Matchers with FormModelS variadicFormData[SourceOrigin.OutOfDate]("a" -> "NotX", "b" -> "B", "c" -> "C"), variadicFormData[SourceOrigin.Current]("a" -> "NotX", "b" -> "B", "c" -> "C"), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB1), SectionNumber.Classic(1), section2) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB1), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ) ) ), ( variadicFormData[SourceOrigin.OutOfDate]("a" -> "X", "b" -> "B", "c" -> "C"), variadicFormData[SourceOrigin.Current]("a" -> "X", "b" -> "B", "c" -> "C"), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB2), SectionNumber.Classic(1), section2) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB2), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ) ) ) ) diff --git a/test/uk/gov/hmrc/gform/models/FormModelSpec.scala b/test/uk/gov/hmrc/gform/models/FormModelSpec.scala index 4749a4a54..37658ba6c 100644 --- a/test/uk/gov/hmrc/gform/models/FormModelSpec.scala +++ b/test/uk/gov/hmrc/gform/models/FormModelSpec.scala @@ -104,7 +104,12 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport val expected: FormModel[Visibility] = FormModel.fromPages( BracketPlainCoordinated.Classic( - NonEmptyList.one(BracketPlain.NonRepeatingPage(Singleton(expectedPage), section1)) + NonEmptyList.one( + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ) + ) ), expectedStaticTypeInfo, expectedRevealinChoiceInfo, @@ -398,9 +403,18 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport FormCtx("c") -> Hidden ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageD), SectionNumber.Classic(3), section4) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageD), SectionNumber.Classic.NormalPage(TemplateSectionIndex(3))), + section4 + ) ) ), ( @@ -413,10 +427,22 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport FormCtx("c") -> StringResult("X") ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageC), SectionNumber.Classic(2), section3), - Bracket.NonRepeatingPage(Singleton(expectedPageD), SectionNumber.Classic(3), section4) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageC), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageD), SectionNumber.Classic.NormalPage(TemplateSectionIndex(3))), + section4 + ) ) ) ) @@ -490,9 +516,18 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport FormCtx("b") -> OptionResult(List("123")) ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPage1), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPage2), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPage3), SectionNumber.Classic(2), section3) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage1), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage2), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage3), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ) ) ), ( @@ -504,8 +539,14 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport FormCtx("c") -> Hidden ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPage1), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPage3), SectionNumber.Classic(2), section3) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage1), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage3), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ) ) ) ) @@ -579,9 +620,18 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport FormCtx("b") -> OptionResult(List("123")) ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPage1), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPage2), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPage3), SectionNumber.Classic(2), section3) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage1), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage2), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPage3), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ) ) ) ) @@ -723,10 +773,22 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport variadicFormData[SourceOrigin.OutOfDate]("a" -> "HELLO", "b" -> "WORLD2", "c" -> "C", "e" -> "E", "d" -> "D"), variadicFormData[SourceOrigin.Current]("a" -> "HELLO", "b" -> "WORLD2", "c" -> "C", "e" -> "E", "d" -> "D"), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageF), SectionNumber.Classic(4), section5), - Bracket.NonRepeatingPage(Singleton(expectedPageG), SectionNumber.Classic(5), section6) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageF), SectionNumber.Classic.NormalPage(TemplateSectionIndex(4))), + section5 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageG), SectionNumber.Classic.NormalPage(TemplateSectionIndex(5))), + section6 + ) ) ), ( @@ -746,11 +808,26 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport "f" -> "C" ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageC), SectionNumber.Classic(2), section3), - Bracket.NonRepeatingPage(Singleton(expectedPageF), SectionNumber.Classic(4), section5), - Bracket.NonRepeatingPage(Singleton(expectedPageG), SectionNumber.Classic(5), section6) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageC), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageF), SectionNumber.Classic.NormalPage(TemplateSectionIndex(4))), + section5 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageG), SectionNumber.Classic.NormalPage(TemplateSectionIndex(5))), + section6 + ) ) ), ( @@ -770,11 +847,26 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport "f" -> "D" ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageD), SectionNumber.Classic(3), section4), - Bracket.NonRepeatingPage(Singleton(expectedPageF), SectionNumber.Classic(4), section5), - Bracket.NonRepeatingPage(Singleton(expectedPageG), SectionNumber.Classic(5), section6) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageD), SectionNumber.Classic.NormalPage(TemplateSectionIndex(3))), + section4 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageF), SectionNumber.Classic.NormalPage(TemplateSectionIndex(4))), + section5 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageG), SectionNumber.Classic.NormalPage(TemplateSectionIndex(5))), + section6 + ) ) ), ( @@ -794,12 +886,30 @@ class FormModelSpec extends AnyFlatSpecLike with Matchers with FormModelSupport "f" -> "C" ), NonEmptyList.of( - Bracket.NonRepeatingPage(Singleton(expectedPageA), SectionNumber.Classic(0), section1), - Bracket.NonRepeatingPage(Singleton(expectedPageB), SectionNumber.Classic(1), section2), - Bracket.NonRepeatingPage(Singleton(expectedPageC), SectionNumber.Classic(2), section3), - Bracket.NonRepeatingPage(Singleton(expectedPageD), SectionNumber.Classic(3), section4), - Bracket.NonRepeatingPage(Singleton(expectedPageF), SectionNumber.Classic(4), section5), - Bracket.NonRepeatingPage(Singleton(expectedPageG), SectionNumber.Classic(5), section6) + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageA), SectionNumber.Classic.NormalPage(TemplateSectionIndex(0))), + section1 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageB), SectionNumber.Classic.NormalPage(TemplateSectionIndex(1))), + section2 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageC), SectionNumber.Classic.NormalPage(TemplateSectionIndex(2))), + section3 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageD), SectionNumber.Classic.NormalPage(TemplateSectionIndex(3))), + section4 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageF), SectionNumber.Classic.NormalPage(TemplateSectionIndex(4))), + section5 + ), + Bracket.NonRepeatingPage( + SingletonWithNumber(Singleton(expectedPageG), SectionNumber.Classic.NormalPage(TemplateSectionIndex(5))), + section6 + ) ) ) ) diff --git a/test/uk/gov/hmrc/gform/models/FormModelSupport.scala b/test/uk/gov/hmrc/gform/models/FormModelSupport.scala index 8b7ccd48e..59b6798f9 100644 --- a/test/uk/gov/hmrc/gform/models/FormModelSupport.scala +++ b/test/uk/gov/hmrc/gform/models/FormModelSupport.scala @@ -28,7 +28,7 @@ import uk.gov.hmrc.gform.eval.{ DbLookupChecker, DelegatedEnrolmentChecker, Seis import uk.gov.hmrc.gform.graph.{ GraphException, Recalculation } import uk.gov.hmrc.gform.graph.FormTemplateBuilder._ import uk.gov.hmrc.gform.models.optics.{ DataOrigin, FormModelVisibilityOptics } -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ IncludeIf, OptionData } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ IncludeIf, OptionData, SectionNumber } import uk.gov.hmrc.gform.sharedmodel.{ AccessCode, BooleanExprCache, LangADT, NotChecked, Obligations, SourceOrigin, UserId, VariadicFormData } import uk.gov.hmrc.gform.sharedmodel.form.{ EnvelopeId, Form, FormComponentIdToFileIdMapping, FormData, FormField, FormId, FormModelOptics, InProgress, ThirdPartyData, VisitIndex } import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FormTemplate, FormTemplateContext, FormTemplateId, Section } @@ -75,7 +75,7 @@ trait FormModelSupport extends GraphSpec { formTemplateVersion = None, formData = FormData(List.empty[FormField]), status = InProgress, - visitsIndex = VisitIndex.Classic(Set.empty[Int]), + visitsIndex = VisitIndex.Classic(Set.empty[SectionNumber.Classic]), thirdPartyData = thirdPartyData, envelopeExpiryDate = None, componentIdToFileId = FormComponentIdToFileIdMapping.empty @@ -129,7 +129,7 @@ trait FormModelSupport extends GraphSpec { formModelOptics: FormModelOptics[DataOrigin.Browser] ): ProcessData = { - val visitsIndex: VisitIndex = VisitIndex.Classic(Set.empty[Int]) + val visitsIndex: VisitIndex = VisitIndex.Classic(Set.empty[SectionNumber.Classic]) val booleanExprCache: BooleanExprCache = BooleanExprCache.empty val obligations: Obligations = NotChecked diff --git a/test/uk/gov/hmrc/gform/models/GroupUtilsSpec.scala b/test/uk/gov/hmrc/gform/models/GroupUtilsSpec.scala index c2d07ea12..23dd5a72e 100644 --- a/test/uk/gov/hmrc/gform/models/GroupUtilsSpec.scala +++ b/test/uk/gov/hmrc/gform/models/GroupUtilsSpec.scala @@ -25,7 +25,7 @@ import uk.gov.hmrc.gform.graph.FormTemplateBuilder._ import uk.gov.hmrc.gform.models.ids.ModelComponentId import uk.gov.hmrc.gform.models.optics.DataOrigin import uk.gov.hmrc.gform.sharedmodel.form.{ FileId, FormModelOptics } -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FileUpload, FormComponentId, Section, SectionNumber, ShortText, Text, Value } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ FileUpload, FormComponentId, Section, SectionNumber, ShortText, TemplateSectionIndex, Text, Value } import uk.gov.hmrc.gform.sharedmodel.{ LangADT, SourceOrigin, VariadicFormData } import uk.gov.hmrc.gform.sharedmodel.VariadicValue.One import uk.gov.hmrc.gform.sharedmodel.form.FormComponentIdToFileIdMapping @@ -118,7 +118,12 @@ class GroupUtilsSpec extends AnyFlatSpecLike with Matchers with FormModelSupport forAll(variations) { case (index, (expectedVariadicData, expectedMapping, expectedFilesToDelete)) => val modelComponentId: ModelComponentId = FormComponentId(s"${index}_group").modelComponentId val (updatedVariadicData, updatedMapping, filesToDelete) = - GroupUtils.removeRecord(processData, modelComponentId, SectionNumber.Classic(1), originalMapping) + GroupUtils.removeRecord( + processData, + modelComponentId, + SectionNumber.Classic.NormalPage(TemplateSectionIndex(1)), + originalMapping + ) updatedMapping shouldBe expectedMapping filesToDelete shouldBe expectedFilesToDelete diff --git a/test/uk/gov/hmrc/gform/pdf/PdfRenderServiceExpectations.scala b/test/uk/gov/hmrc/gform/pdf/PdfRenderServiceExpectations.scala index caa83540d..a35dc7df4 100644 --- a/test/uk/gov/hmrc/gform/pdf/PdfRenderServiceExpectations.scala +++ b/test/uk/gov/hmrc/gform/pdf/PdfRenderServiceExpectations.scala @@ -220,7 +220,7 @@ trait PdfRenderServiceExpectations { def nonRepeatingPageSummaryPDFHTML(signatureBox: String = "")(implicit time: LocalDateTime) = htmlBase( """ - |

Section Name

+ |

Section Name

|
|

| name @@ -234,7 +234,7 @@ trait PdfRenderServiceExpectations { """ | | - | + | | |""".stripMargin, signatureBox @@ -243,7 +243,7 @@ trait PdfRenderServiceExpectations { def nonRepeatingPageTabularSummaryPDFHTML(signatureBox: String = "")(implicit time: LocalDateTime) = htmlTabularBase( s""" - |
+ |
|
|
| Section Name @@ -263,7 +263,7 @@ trait PdfRenderServiceExpectations { """ | | - | + | | |""".stripMargin, s""" @@ -288,7 +288,7 @@ trait PdfRenderServiceExpectations { def nonRepeatingPageInstructionPDFHTML(signatureBox: String = "") = htmlTabularBase( """ - |
+ |
|
|
| page1-instruction @@ -307,7 +307,7 @@ trait PdfRenderServiceExpectations { """ | | - | + | | |""".stripMargin, "", diff --git a/test/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilderSpec.scala b/test/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilderSpec.scala index eb7d5b5b4..6bb096d46 100644 --- a/test/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilderSpec.scala +++ b/test/uk/gov/hmrc/gform/pdf/model/PDFPageModelBuilderSpec.scala @@ -137,7 +137,7 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu SimpleField(Some("name"), List(Html("name&value"))), SimpleField(Some("Key"), List(Html("name&value"))) ), - "0" + "n0" ) ) } @@ -163,8 +163,8 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu envelopeWithMapping, validationResult ) shouldBe List( - PageData(Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value1")))), "0"), - PageData(Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value2")))), "1") + PageData(Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value1")))), "r0.0"), + PageData(Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value2")))), "r0.1") ) } @@ -228,9 +228,13 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu PageData( Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value1"))), SimpleField(Some("age"), List(Html("1")))), - "0" + "ap0.1.0" ), - PageData(Some("Section Name"), List(SimpleField(Some("email"), List(Html("somename1@test.com")))), "1") + PageData( + Some("Section Name"), + List(SimpleField(Some("email"), List(Html("somename1@test.com")))), + "ap0.1.1" + ) ), "addToListQuestion0" ), @@ -240,9 +244,13 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu PageData( Some("Section Name"), List(SimpleField(Some("name"), List(Html("name-value2"))), SimpleField(Some("age"), List(Html("2")))), - "3" + "ap0.2.0" ), - PageData(Some("Section Name"), List(SimpleField(Some("email"), List(Html("name-value2@test.com")))), "4") + PageData( + Some("Section Name"), + List(SimpleField(Some("email"), List(Html("name-value2@test.com")))), + "ap0.2.1" + ) ), "addToListQuestion1" ) @@ -280,7 +288,7 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu List( SimpleField(Some("name"), List(Html("name-value"))) ), - "0" + "n0" ) ) } @@ -323,14 +331,14 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu SimpleField(Some("email-instruction"), List(Html("somename@test.com"))), SimpleField(Some("age-instruction"), List(Html("1"))) ), - "1" + "n1" ), PageData( Some("page1-instruction"), List( SimpleField(Some("name-instruction"), List(Html("name-value"))) ), - "0" + "n0" ) ) } @@ -348,7 +356,7 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu List( SimpleField(Some("name-instruction"), List(Html("name&value"))) ), - "0" + "n0" ) ) } @@ -382,7 +390,7 @@ class PDFPageModelBuilderSpec extends AnyFlatSpec with Matchers with FormModelSu List( SimpleField(Some("email-instruction"), List(Html("somename@test.com"))) ), - "0" + "n0" ) ) } diff --git a/test/uk/gov/hmrc/gform/services/SectionRenderingServiceSpec.scala b/test/uk/gov/hmrc/gform/services/SectionRenderingServiceSpec.scala index 93d7c5e1e..b974878b0 100644 --- a/test/uk/gov/hmrc/gform/services/SectionRenderingServiceSpec.scala +++ b/test/uk/gov/hmrc/gform/services/SectionRenderingServiceSpec.scala @@ -161,7 +161,7 @@ class SectionRenderingServiceSpec extends Spec with ArgumentMatchersSugar with I val generatedHtml = testService .renderSection( Some(accessCode), - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), FormHandlerResult(ValidationResult.empty, EnvelopeWithMapping.empty), formTemplate, None, @@ -202,7 +202,7 @@ class SectionRenderingServiceSpec extends Spec with ArgumentMatchersSugar with I val generatedHtml = testService .renderSection( Some(accessCode), - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), FormHandlerResult(ValidationResult.empty, EnvelopeWithMapping.empty), formTemplate, None, @@ -242,7 +242,7 @@ class SectionRenderingServiceSpec extends Spec with ArgumentMatchersSugar with I val generatedHtml = testService .renderSection( Some(accessCode), - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), FormHandlerResult(ValidationResult.empty, EnvelopeWithMapping.empty), formTemplate.copy(draftRetrievalMethod = NotPermitted), None, @@ -287,7 +287,7 @@ class SectionRenderingServiceSpec extends Spec with ArgumentMatchersSugar with I val generatedHtml = testService .renderSection( Some(accessCode), - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), FormHandlerResult(ValidationResult.empty, EnvelopeWithMapping.empty), formTemplate.copy(draftRetrievalMethod = NotPermitted), None, diff --git a/test/uk/gov/hmrc/gform/sharedmodel/ExampleData.scala b/test/uk/gov/hmrc/gform/sharedmodel/ExampleData.scala index 326bc593c..4e1ebb37d 100644 --- a/test/uk/gov/hmrc/gform/sharedmodel/ExampleData.scala +++ b/test/uk/gov/hmrc/gform/sharedmodel/ExampleData.scala @@ -792,11 +792,11 @@ trait ExampleSection { dependecies: ExampleFieldId with ExampleFieldValue => } trait ExampleSectionNumber { - val `sectionNumber-1` = SectionNumber.Classic(-1) - val sectionNumber0 = SectionNumber.Classic(0) - val sectionNumber1 = SectionNumber.Classic(1) - val sectionNumber2 = SectionNumber.Classic(2) - val sectionNumber3 = SectionNumber.Classic(3) + val `sectionNumber-1` = SectionNumber.Classic.NormalPage(TemplateSectionIndex(-1)) + val sectionNumber0 = SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)) + val sectionNumber1 = SectionNumber.Classic.NormalPage(TemplateSectionIndex(1)) + val sectionNumber2 = SectionNumber.Classic.NormalPage(TemplateSectionIndex(2)) + val sectionNumber3 = SectionNumber.Classic.NormalPage(TemplateSectionIndex(3)) } trait ExampleFormTemplate { diff --git a/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumberSuite.scala b/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumberSuite.scala new file mode 100644 index 000000000..c3ad19729 --- /dev/null +++ b/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/SectionNumberSuite.scala @@ -0,0 +1,241 @@ +/* + * 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 uk.gov.hmrc.gform.sharedmodel.formtemplate + +import munit.FunSuite +import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionNumber._ + +class SectionNumberSuite extends FunSuite { + + val parseSectionNumbers: List[(String, SectionNumber)] = + List( + ("n0" -> Classic.NormalPage(TemplateSectionIndex(0))), + ("n9" -> Classic.NormalPage(TemplateSectionIndex(9))), + ("n10" -> Classic.NormalPage(TemplateSectionIndex(10))), + ("n100" -> Classic.NormalPage(TemplateSectionIndex(100))), + ("ap0.0.0" -> Classic.AddToListPage.Page(TemplateSectionIndex(0), 0, 0)), + ("ap1000.1001.1002" -> Classic.AddToListPage.Page(TemplateSectionIndex(1000), 1001, 1002)), + ("ad0" -> Classic.AddToListPage.DefaultPage(TemplateSectionIndex(0))), + ("ac0.0" -> Classic.AddToListPage.CyaPage(TemplateSectionIndex(0), 0)), + ("ar0.0" -> Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(0), 0)), + ("r0.0" -> Classic.RepeatedPage(TemplateSectionIndex(0), 0)), + ("r100.1001" -> Classic.RepeatedPage(TemplateSectionIndex(100), 1001)) + ) + + test("parse sectionNumbers") { + parseSectionNumbers.foreach { case (toParse, expectedSectionNumber) => + val res = SectionNumber.parse(toParse) + assertEquals(res, Some(expectedSectionNumber)) + } + } + + private val EQ = 0 + private val GT = 1 + private val LT = -1 + + val compareSectionNumbers: List[(SectionNumber, SectionNumber, Int)] = + List( + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.NormalPage(TemplateSectionIndex(0)), EQ), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.NormalPage(TemplateSectionIndex(1)), LT), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), LT), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), LT), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 5), LT), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.RepeatedPage(TemplateSectionIndex(1), 5), LT), + (Classic.NormalPage(TemplateSectionIndex(1)), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.NormalPage(TemplateSectionIndex(1)), Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5), GT), + (Classic.NormalPage(TemplateSectionIndex(1)), Classic.RepeatedPage(TemplateSectionIndex(0), 5), GT), + (Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 5), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 5), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.RepeatedPage(TemplateSectionIndex(1), 5), Classic.NormalPage(TemplateSectionIndex(0)), GT), + (Classic.AddToListPage.DefaultPage(TemplateSectionIndex(0)), Classic.NormalPage(TemplateSectionIndex(1)), LT), + (Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5), Classic.NormalPage(TemplateSectionIndex(1)), LT), + (Classic.AddToListPage.CyaPage(TemplateSectionIndex(0), 5), Classic.NormalPage(TemplateSectionIndex(1)), LT), + (Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(0), 5), Classic.NormalPage(TemplateSectionIndex(1)), LT), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.NormalPage(TemplateSectionIndex(1)), LT), + ( + Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), + Classic.RepeatedPage(TemplateSectionIndex(0), 5), + GT + ), + (Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), Classic.RepeatedPage(TemplateSectionIndex(0), 5), GT), + (Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 5), Classic.RepeatedPage(TemplateSectionIndex(0), 5), GT), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 5), + Classic.RepeatedPage(TemplateSectionIndex(0), 5), + GT + ), + ( + Classic.RepeatedPage(TemplateSectionIndex(0), 5), + Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), + LT + ), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), LT), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 5), LT), + ( + Classic.RepeatedPage(TemplateSectionIndex(0), 5), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 5), + LT + ), + (Classic.RepeatedPage(TemplateSectionIndex(0), 1), Classic.RepeatedPage(TemplateSectionIndex(0), 1), EQ), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.RepeatedPage(TemplateSectionIndex(0), 1), GT), + (Classic.RepeatedPage(TemplateSectionIndex(0), 1), Classic.RepeatedPage(TemplateSectionIndex(0), 5), LT), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 4), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 4), + EQ + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 5), + GT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 5), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 5, 5), + LT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 4), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 5), + LT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 5), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 4, 4), + GT + ), + ( + Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + LT + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + GT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + GT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + Classic.AddToListPage.DefaultPage(TemplateSectionIndex(1)), + GT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + LT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 0, 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + LT + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + EQ + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 1), + LT + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 1), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + GT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + EQ + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 1), + LT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 1), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + GT + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + LT + ), + ( + Classic.AddToListPage.CyaPage(TemplateSectionIndex(2), 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + GT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(1), 0), + GT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.CyaPage(TemplateSectionIndex(2), 0), + LT + ), + ( + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + Classic.AddToListPage.Page(TemplateSectionIndex(1), 1, 0), + LT + ), + ( + Classic.AddToListPage.Page(TemplateSectionIndex(1), 1, 0), + Classic.AddToListPage.RepeaterPage(TemplateSectionIndex(1), 0), + GT + ) + ) + + test("compare sectionNumbers") { + compareSectionNumbers.foreach { case (sn1, sn2, expected) => + val res = sn1.compare(sn2) + + assertEquals(res, expected) + + } + } + + val compareSectionNumbersInvalid: List[(SectionNumber, SectionNumber)] = + List( + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5)), + (Classic.NormalPage(TemplateSectionIndex(0)), Classic.RepeatedPage(TemplateSectionIndex(0), 5)), + (Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5), Classic.NormalPage(TemplateSectionIndex(0))), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.NormalPage(TemplateSectionIndex(0))), + (Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5), Classic.RepeatedPage(TemplateSectionIndex(0), 5)), + (Classic.RepeatedPage(TemplateSectionIndex(0), 5), Classic.AddToListPage.Page(TemplateSectionIndex(0), 5, 5)) + ) + + test("invalid compares") { + compareSectionNumbersInvalid.foreach { case (sn1, sn2) => + intercept[java.lang.Exception] { + sn1.compare(sn2) + } + } + } +} diff --git a/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/generators/VisitIndexGen.scala b/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/generators/VisitIndexGen.scala index 1f48f2569..48f8ff72e 100644 --- a/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/generators/VisitIndexGen.scala +++ b/test/uk/gov/hmrc/gform/sharedmodel/formtemplate/generators/VisitIndexGen.scala @@ -18,9 +18,16 @@ package uk.gov.hmrc.gform.sharedmodel.formtemplate.generators import org.scalacheck.Gen import uk.gov.hmrc.gform.sharedmodel.form.VisitIndex +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ SectionNumber, TemplateSectionIndex } trait VisitIndexGen { - def visitIndexGen = Gen.containerOf[Set, Int](Gen.posNum[Int]).map(VisitIndex.Classic(_)) + def visitIndexGen = + Gen + .oneOf( + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(1)) + ) + .map(sn => VisitIndex.Classic(Set(sn))) } object VisitIndexGen extends VisitIndexGen diff --git a/test/uk/gov/hmrc/gform/summary/FormComponentSummaryRendererSpec.scala b/test/uk/gov/hmrc/gform/summary/FormComponentSummaryRendererSpec.scala index df612f963..3d5a75108 100644 --- a/test/uk/gov/hmrc/gform/summary/FormComponentSummaryRendererSpec.scala +++ b/test/uk/gov/hmrc/gform/summary/FormComponentSummaryRendererSpec.scala @@ -31,7 +31,7 @@ import uk.gov.hmrc.gform.models.{ FastForward, FormModelSupport, SectionSelector import uk.gov.hmrc.gform.models.optics.DataOrigin import uk.gov.hmrc.gform.sharedmodel.ExampleData.{ buildForm, buildFormComponent, buildFormTemplate, destinationList, envelopeWithMapping, nonRepeatingPageSection } import uk.gov.hmrc.gform.sharedmodel.form.{ Form, FormData, FormField, FormModelOptics } -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Constant, DisplayInSummary, Equals, FormComponent, FormComponentId, FormCtx, FormTemplate, FormTemplateContext, IncludeIf, InformationMessage, KeyDisplayWidth, MiniSummaryList, MiniSummaryListValue, NoFormat, SectionNumber, SectionOrSummary, SectionTitle4Ga, Value } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ Constant, DisplayInSummary, Equals, FormComponent, FormComponentId, FormCtx, FormTemplate, FormTemplateContext, IncludeIf, InformationMessage, KeyDisplayWidth, MiniSummaryList, MiniSummaryListValue, NoFormat, SectionNumber, SectionOrSummary, SectionTitle4Ga, TemplateSectionIndex, Value } import uk.gov.hmrc.gform.sharedmodel.formtemplate.MiniSummaryRow.ValueRow import uk.gov.hmrc.gform.sharedmodel.{ AccessCode, LangADT, LocalisedString, NotChecked, SmartString } import uk.gov.hmrc.gform.validation.ValidationResult @@ -189,7 +189,7 @@ class FormComponentSummaryRendererSpec extends FunSuite with FormModelSupport { formTemplate._id, formModelOptics.formModelVisibilityOptics, None, - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), SectionTitle4Ga("page1"), NotChecked, ValidationResult.empty, @@ -247,7 +247,7 @@ class FormComponentSummaryRendererSpec extends FunSuite with FormModelSupport { formTemplate._id, formModelOptics.formModelVisibilityOptics, None, - SectionNumber.Classic(0), + SectionNumber.Classic.NormalPage(TemplateSectionIndex(0)), SectionTitle4Ga("page1"), NotChecked, ValidationResult.empty, diff --git a/test/uk/gov/hmrc/gform/validation/AddressCheckerSpec.scala b/test/uk/gov/hmrc/gform/validation/AddressCheckerSpec.scala index 302524b3a..94e69d39f 100644 --- a/test/uk/gov/hmrc/gform/validation/AddressCheckerSpec.scala +++ b/test/uk/gov/hmrc/gform/validation/AddressCheckerSpec.scala @@ -123,7 +123,7 @@ class AddressCheckerSpec data: VariadicFormData[SourceOrigin.OutOfDate] ) = { - val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections) + val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections.map(_.section)) val fmvo = fmb.visibilityModel[DataOrigin.Mongo, SectionSelectorType.Normal](data, None) diff --git a/test/uk/gov/hmrc/gform/validation/DateCheckerSpec.scala b/test/uk/gov/hmrc/gform/validation/DateCheckerSpec.scala index a893e3638..833e8355f 100644 --- a/test/uk/gov/hmrc/gform/validation/DateCheckerSpec.scala +++ b/test/uk/gov/hmrc/gform/validation/DateCheckerSpec.scala @@ -113,7 +113,7 @@ class DateCheckerSpec extends FunSuite with FormModelSupport with VariadicFormDa data: VariadicFormData[SourceOrigin.OutOfDate] ) = { - val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections) + val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections.map(_.section)) val fmvo = fmb.visibilityModel[DataOrigin.Mongo, SectionSelectorType.Normal](data, None) diff --git a/test/uk/gov/hmrc/gform/validation/OverseasAddressCheckerSpec.scala b/test/uk/gov/hmrc/gform/validation/OverseasAddressCheckerSpec.scala index 44993f905..c50d3fedd 100644 --- a/test/uk/gov/hmrc/gform/validation/OverseasAddressCheckerSpec.scala +++ b/test/uk/gov/hmrc/gform/validation/OverseasAddressCheckerSpec.scala @@ -129,7 +129,7 @@ class OverseasAddressCheckerSpec data: VariadicFormData[SourceOrigin.OutOfDate] ) = { - val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections) + val fmb = mkFormModelFromSections(formTemplate.formKind.allSections.sections.map(_.section)) val fmvo = fmb.visibilityModel[DataOrigin.Mongo, SectionSelectorType.Normal](data, None) diff --git a/test/uk/gov/hmrc/gform/validation/ValidationServiceSpec.scala b/test/uk/gov/hmrc/gform/validation/ValidationServiceSpec.scala index 078fdd2cb..0bc81a4a9 100644 --- a/test/uk/gov/hmrc/gform/validation/ValidationServiceSpec.scala +++ b/test/uk/gov/hmrc/gform/validation/ValidationServiceSpec.scala @@ -42,7 +42,7 @@ import uk.gov.hmrc.gform.sharedmodel._ import uk.gov.hmrc.gform.sharedmodel.email.{ ConfirmationCodeWithEmailService, EmailConfirmationCode, EmailTemplateId } import uk.gov.hmrc.gform.sharedmodel.form._ import uk.gov.hmrc.gform.sharedmodel.formtemplate.SectionNumber.Classic -import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ EmailVerifiedBy, FormComponent, FormComponentId, FormTemplate, ShortText, Text, Value } +import uk.gov.hmrc.gform.sharedmodel.formtemplate.{ EmailVerifiedBy, FormComponent, FormComponentId, FormTemplate, ShortText, TemplateSectionIndex, Text, Value } import uk.gov.hmrc.gform.validation.ValidationUtil.ValidatedType import uk.gov.hmrc.http.HeaderCarrier @@ -190,7 +190,9 @@ class ValidationServiceSpec extends Spec with FormModelSupport with VariadicForm ): ValidationResult = { val formModelOptics: FormModelOptics[DataOrigin.Browser] = mkFormModelOptics(formTemplate, variadicFormData) val visibilityFormModel: FormModel[Visibility] = formModelOptics.formModelVisibilityOptics.formModel - val visibilityPageModel: PageModel[Visibility] = visibilityFormModel(Classic(pageToValidate)) + val visibilityPageModel: PageModel[Visibility] = visibilityFormModel( + Classic.NormalPage(TemplateSectionIndex(pageToValidate)) + ) val result: ValidatedType[ValidatorsResult] = setupAndRun(variadicFormData, icludeEmailAndCodeMap, pageToValidate) ValidationUtil.evaluateValidationResult( @@ -213,7 +215,9 @@ class ValidationServiceSpec extends Spec with FormModelSupport with VariadicForm val formModelOptics: FormModelOptics[DataOrigin.Browser] = mkFormModelOptics(formTemplate, variadicFormData) val visibilityFormModel: FormModel[Visibility] = formModelOptics.formModelVisibilityOptics.formModel - val visibilityPageModel: PageModel[Visibility] = visibilityFormModel(Classic(pageToValidate)) + val visibilityPageModel: PageModel[Visibility] = visibilityFormModel( + Classic.NormalPage(TemplateSectionIndex(pageToValidate)) + ) val emailAndCodeMap: Map[EmailFieldId, EmailAndCode] = if (includeEmailAndCodeMap)