Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions e2e/cypress/e2e/runner/repeatedMultiField/addMultiplSections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Then, When } from "@badeball/cypress-cucumber-preprocessor";

Then("I see a summary card titled {string}", (cardTitle) => {
cy.findByRole("heading", { name: cardTitle }).should("exist");
});

Then(
"the summary card {string} contains a row {string} with value {string}",
(cardTitle, rowLabel, rowValue) => {
cy.findByRole("heading", { name: cardTitle })
.parents(".govuk-summary-card")
.within(() => {
cy.contains("dt", rowLabel).next("dd").should("contain.text", rowValue);
});
}
);

Then(
"the summary card {string} has a {string} link to {string}",
(cardTitle, linkText, hrefFragment) => {
cy.findByRole("heading", { name: cardTitle })
.parents(".govuk-summary-card")
.within(() => {
cy.findAllByRole("link", { name: new RegExp(linkText, "i") })
.first()
.should("have.attr", "href")
.and("include", hrefFragment);
});
}
);

// Click the delete button on a specific card
When("I delete the summary card {string}", (cardTitle) => {
cy.findByRole("heading", { name: cardTitle })
.parents(".govuk-summary-card")
.within(() => {
cy.findByRole("link", { name: /Delete/i }).click();
});
});

// Click the edit button on a specific card
When("I edit the summary card {string}", (cardTitle) => {
cy.findByRole("heading", { name: cardTitle })
.parents(".govuk-summary-card")
.within(() => {
cy.findByRole("link", { name: /Change/i }).click();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Feature: Repeating multi field - add multiple sections

Background:
Given the form "repeat-multi-field" exists

Scenario: User can repeat sections with multiple fields
When I enter "Alice" for "Your name"
And I enter "French{enter}" for "Language"
And I select the button "Continue"
Then I see "You have selected these Interpreters"
And I select the button "Add another"
Then I don't see "French"
Then I don't see "Alice"
And I enter "Bob{enter}" for "Your name"
And I enter "Italian" for "Language"
And I select the button "Continue"
Then I see "You have selected these Interpreters"
Then I see a summary card titled "Item 1"
And the summary card "Item 1" contains a row "Your name" with value "Alice"
And the summary card "Item 1" contains a row "Language" with value "French"
And the summary card "Item 1" has a "Change" link to "?view=0"
And the summary card "Item 1" has a "Delete" link to "?remove=0"
Then I see a summary card titled "Item 2"
And the summary card "Item 2" contains a row "Your name" with value "Bob"
And the summary card "Item 2" contains a row "Language" with value "Italian"
And the summary card "Item 2" has a "Change" link to "?view=1"
And the summary card "Item 2" has a "Delete" link to "?remove=1"
And I edit the summary card "Item 2"
And I enter "Robert{enter}" for "Your name"
Then the summary card "Item 2" contains a row "Your name" with value "Robert"
And I don't see "Bob"
And the summary card "Item 2" has a "Delete" link to "?remove=1"
And I delete the summary card "Item 2"
And the summary card "Item 1" contains a row "Your name" with value "Alice"
And the summary card "Item 1" contains a row "Language" with value "French"
And I don't see "Robert"
And I don't see "Delete"
And I select the button "Continue"
Then I see a summary card titled "Item 1"
And I see "Check your answers"
And the summary card "Item 1" contains a row "Your name" with value "Alice"
And the summary card "Item 1" contains a row "Language" with value "French"
And the summary card "Item 1" has a "Change" link to "?view=0"
And the summary card "Item 1" has a "Delete" link to "?remove=0"
87 changes: 87 additions & 0 deletions e2e/cypress/fixtures/repeat-multi-field.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"startPage": "/which-languages-do-you-translate-or-interpret",
"pages": [
{
"title": "Check your answers",
"path": "/summary",
"controller": "./pages/summary.js",
"components": [],
"next": []
},
{
"path": "/which-languages-do-you-translate-or-interpret",
"title": "Select Interpreters",
"controller": "RepeatedMultiFieldPageController",
"options": {
"sectionKey": "people_you_live_with",
"summaryDisplayMode": {
"samePage": false
},
"customText": {
"separatePageTitle": "You have selected these Interpreters"
}
},
"components": [
{
"name": "Name",
"options": {
"customValidationMessages": {
"string.pattern.base": "Enter a valid first (given) name"
}
},
"type": "TextField",
"title": "Your name",
"schema": {
"regex": "(?=.*[A-Za-zÀ-ÖØ-öø-ÿ]{1}.*[A-Za-zÀ-ÖØ-öø-ÿ]{1}.*)^[\\sA-Za-zÀ-ÖØ-öø-ÿ'\\-,.]*$"
}
},
{
"name": "languagesProvided",
"list": "languages",
"type": "AutocompleteField",
"title": "Language",
"hint": "Start typing and select a language. Add each language separately. Include all the languages you provide a service in, apart from English.",
"schema": {}
}
],
"next": [
{
"path": "/summary"
}
]
}
],
"lists": [
{
"title": "Languages",
"name": "languages",
"type": "string",
"items": [
{
"value": "fr",
"text": "French"
},
{
"value": "es",
"text": "Spanish; Castilian"
},
{
"value": "it",
"text": "Italian"
}
]
}
],
"sections": [],
"phaseBanner": {
"phase": "beta"
},
"metadata": {
"type": "translators-interpreters"
},
"fees": [],
"outputs": [],
"version": 2,
"conditions": [],
"skipSummary": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { nanoid } from "nanoid";
import { When } from "@badeball/cypress-cucumber-preprocessor";

When("I enter {string} for {string}", (answer, label) => {
cy.findByLabelText(label).type(answer);
cy.findByLabelText(label).clear().type(answer);
});
13 changes: 13 additions & 0 deletions model/src/data-model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ export interface RepeatingFieldPage extends Page {
};
};
}
export interface RepeatingMultiFieldPage extends Page {
controller: "RepeatedMultiFieldPageController";
options: {
sectionKey: string;
summaryDisplayMode?: {
samePage?: boolean;
};
customText?: {
separatePageTitle?: string;
customCardTitle?: string;
};
};
}
export interface CheckpointSummaryPage extends Page {
controller: "CheckpointSummaryPageController";
options: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ export class ContactDetailsCollection extends FormComponent {
model
);

// State schema (Pass 2) — shape only
this.stateSchema = Joi.object({
mobile_number: Joi.string().empty(["", null]),
email_address: Joi.string().empty(["", null]).email(),
landline_number: Joi.string().empty(["", null]),
});

// Cross-field rule enforcing "at least one of mobile/email" when the component is required.
Expand Down Expand Up @@ -146,20 +146,22 @@ export class ContactDetailsCollection extends FormComponent {
return {
mobile_number: value.mobile_number ?? "",
email_address: value.email_address ?? "",
landline_number: value.landline_number ?? "",
};
}

getStateValueFromValidForm(payload: FormPayload) {
return {
mobile_number: payload["mobile_number"] || null,
email_address: payload["email_address"] || null,
landline_number: payload["landline_number"] || null,
};
}

getDisplayStringFromState(state: FormSubmissionState) {
const value = state[this.name];
if (!value) return "";
return [value.mobile_number, value.email_address]
return [value.mobile_number, value.email_address, value.landline_number]
.filter(Boolean)
.join(", ");
}
Expand Down
20 changes: 20 additions & 0 deletions runner/src/server/plugins/engine/models/SummaryViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class SummaryViewModel {
callback?: InitialiseSessionOptions;
showPaymentSkippedWarningPage: boolean = false;
returnUrl: string;

constructor(
pageTitle: string,
model: FormModel,
Expand Down Expand Up @@ -178,6 +179,7 @@ export class SummaryViewModel {

[undefined, ...model.sections].forEach((section) => {
const items: any[] = [];
const repeatingCards: any[] = [];
let sectionState = section ? state[section.name] || {} : state;

sectionState.originalFilenames = state.originalFilenames ?? {};
Expand Down Expand Up @@ -206,6 +208,23 @@ export class SummaryViewModel {
}

sectionPages.forEach((page) => {
if (page.isRepeatingFieldPageController) {
const cards = page.toSummaryDetails(state);

cards.forEach((card) => {
const url = redirectUrl(request, `/${model.basePath}${page.path}`, {
returnUrl: redirectUrl(request, `/${model.basePath}/summary`),
view: card.index,
});
card.card = url;
card.items.forEach((item) => {
item.url = url;
});
});

repeatingCards.push(...cards);
return;
}
for (const component of page.components.formItems) {
const item = Item(
request,
Expand Down Expand Up @@ -259,6 +278,7 @@ export class SummaryViewModel {
});
}
}
details.push(...repeatingCards);
});

return details;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ export function WebhookModel(model: FormModel, state: FormSubmissionState) {
englishName = model.name.en ?? model.name;
}

let questions;

const { relevantPages } = model.getRelevantPages(state);

questions = relevantPages.map((page) => pagesToQuestions(page, state));
// Sipliefied logi more extendable shown here https://github.com/XGovFormBuilder/digital-form-builder/issues/1401
const questions = relevantPages.flatMap((page) => {
if ((page as any).isRepeatingFieldPageController === true) {
return (page as any).toWebhookQuestions(state);
}
return [pagesToQuestions(page, state)];
});

const fees = FeesModel(model, state);

return {
Expand Down
Loading