diff --git a/Makefile b/Makefile index 6c42666e3d..f197a03d72 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,13 @@ else yarn run cypress:run endif +run-cypress-headed: ##@testing Runs cypress e2e tests in a browser. To run a specific spec file pass in spec e.g. make run-cypress spec=start +ifdef spec + yarn run cypress:run --spec "cypress/e2e/$(spec).cy.js" --headed --no-exit +else + yarn run cypress:run --headed --no-exit +endif + run-cypress-parallel: ##@testing Runs cypress e2e tests in parallel across 4 processor threads yarn run cypress:parallel diff --git a/app/internal/page/app.go b/app/internal/page/app.go index 9ba39169b3..af60e7b49e 100644 --- a/app/internal/page/app.go +++ b/app/internal/page/app.go @@ -136,6 +136,8 @@ func App( ChooseAttorneySummary(logger, tmpls.Get("choose_attorneys_summary.gohtml"), lpaStore)) handle(removeAttorneyPath, RequireSession|CanGoBack, RemoveAttorney(logger, tmpls.Get("remove_attorney.gohtml"), lpaStore)) + handle(howShouldAttorneysMakeDecisionsPath, RequireSession|CanGoBack, + HowShouldAttorneysMakeDecisions(tmpls.Get("how_should_attorneys_make_decisions.gohtml"), lpaStore)) handle(wantReplacementAttorneysPath, RequireSession|CanGoBack, WantReplacementAttorneys(tmpls.Get("want_replacement_attorneys.gohtml"), lpaStore)) handle(whenCanTheLpaBeUsedPath, RequireSession|CanGoBack, diff --git a/app/internal/page/check_your_lpa.go b/app/internal/page/check_your_lpa.go index 904e9a049c..f105431bf6 100644 --- a/app/internal/page/check_your_lpa.go +++ b/app/internal/page/check_your_lpa.go @@ -7,11 +7,16 @@ import ( ) type checkYourLpaData struct { - App AppData - Errors map[string]string - Lpa *Lpa - Form *checkYourLpaForm - Completed bool + App AppData + Errors map[string]string + Lpa *Lpa + Form *checkYourLpaForm + Completed bool + HowAttorneysMakeDecisionsPath string + ChooseAttorneysPath string + WhenCanLpaBeUsedPath string + RestrictionsPath string + CertificatesProviderPath string } func CheckYourLpa(tmpl template.Template, lpaStore LpaStore) Handler { @@ -28,7 +33,12 @@ func CheckYourLpa(tmpl template.Template, lpaStore LpaStore) Handler { Checked: lpa.Checked, Happy: lpa.HappyToShare, }, - Completed: lpa.Tasks.CheckYourLpa == TaskCompleted, + Completed: lpa.Tasks.CheckYourLpa == TaskCompleted, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, } if r.Method == http.MethodPost { diff --git a/app/internal/page/check_your_lpa_test.go b/app/internal/page/check_your_lpa_test.go index 40eee46a6f..0bc5e01983 100644 --- a/app/internal/page/check_your_lpa_test.go +++ b/app/internal/page/check_your_lpa_test.go @@ -22,9 +22,14 @@ func TestGetCheckYourLpa(t *testing.T) { template := &mockTemplate{} template. On("Func", w, &checkYourLpaData{ - App: appData, - Form: &checkYourLpaForm{}, - Lpa: &Lpa{}, + App: appData, + Form: &checkYourLpaForm{}, + Lpa: &Lpa{}, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, }). Return(nil) @@ -77,6 +82,11 @@ func TestGetCheckYourLpaFromStore(t *testing.T) { Checked: true, Happy: true, }, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, }). Return(nil) diff --git a/app/internal/page/choose_attorney_summary.go b/app/internal/page/choose_attorney_summary.go index ab7888fe14..de06498648 100644 --- a/app/internal/page/choose_attorney_summary.go +++ b/app/internal/page/choose_attorney_summary.go @@ -48,11 +48,16 @@ func ChooseAttorneySummary(logger Logger, tmpl template.Template, lpaStore LpaSt if len(data.Errors) == 0 { redirectUrl := wantReplacementAttorneysPath + if len(lpa.Attorneys) > 1 { + redirectUrl = howShouldAttorneysMakeDecisionsPath + } + if data.Form.AddAttorney == "yes" { redirectUrl = fmt.Sprintf("%s?addAnother=1", data.AttorneyDetailsPath) } appData.Lang.Redirect(w, r, redirectUrl, http.StatusFound) + return nil } } diff --git a/app/internal/page/choose_attorneys_summary_test.go b/app/internal/page/choose_attorneys_summary_test.go index 2ec9c5be06..7b661e978b 100644 --- a/app/internal/page/choose_attorneys_summary_test.go +++ b/app/internal/page/choose_attorneys_summary_test.go @@ -67,14 +67,22 @@ func TestPostChooseAttorneysSummaryAddAttorney(t *testing.T) { testcases := map[string]struct { addMoreFormValue string expectedUrl string + Attorneys []Attorney }{ - "add-attorney": { - "yes", - "/choose-attorneys?addAnother=1", + "add attorney": { + addMoreFormValue: "yes", + expectedUrl: "/choose-attorneys?addAnother=1", + Attorneys: []Attorney{}, }, - "do-not-add-attorney": { - "no", - "/want-replacement-attorneys", + "do not add attorney - with single attorney": { + addMoreFormValue: "no", + expectedUrl: "/want-replacement-attorneys", + Attorneys: []Attorney{{ID: "123"}}, + }, + "do not add attorney - with multiple attorneys": { + addMoreFormValue: "no", + expectedUrl: "/how-should-attorneys-make-decisions", + Attorneys: []Attorney{{ID: "123"}, {ID: "456"}}, }, } @@ -85,20 +93,7 @@ func TestPostChooseAttorneysSummaryAddAttorney(t *testing.T) { lpaStore := &mockLpaStore{} lpaStore. On("Get", mock.Anything, "session-id"). - Return(&Lpa{}, nil) - - template := &mockTemplate{} - template. - On("Func", w, &chooseAttorneysSummaryData{ - App: appData, - Lpa: &Lpa{}, - AttorneyAddressPath: chooseAttorneysAddressPath, - AttorneyDetailsPath: chooseAttorneysPath, - RemoveAttorneyPath: removeAttorneyPath, - Form: chooseAttorneysSummaryForm{AddAttorney: tc.addMoreFormValue}, - Errors: map[string]string{}, - }). - Return(nil) + Return(&Lpa{Attorneys: tc.Attorneys}, nil) form := url.Values{ "add-attorney": {tc.addMoreFormValue}, @@ -107,13 +102,13 @@ func TestPostChooseAttorneysSummaryAddAttorney(t *testing.T) { r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) r.Header.Add("Content-Type", formUrlEncoded) - err := ChooseAttorneySummary(nil, template.Func, lpaStore)(appData, w, r) + err := ChooseAttorneySummary(nil, nil, lpaStore)(appData, w, r) resp := w.Result() assert.Nil(t, err) assert.Equal(t, http.StatusFound, resp.StatusCode) assert.Equal(t, tc.expectedUrl, resp.Header.Get("Location")) - mock.AssertExpectationsForObjects(t, lpaStore, template) + mock.AssertExpectationsForObjects(t, lpaStore) }) } } diff --git a/app/internal/page/data.go b/app/internal/page/data.go index f162033694..976ae83fd4 100644 --- a/app/internal/page/data.go +++ b/app/internal/page/data.go @@ -51,6 +51,8 @@ type Lpa struct { SignatureEmailID string IdentityOptions IdentityOptions YotiUserData identity.UserData + DecisionsType string + DecisionsDetails string } type PaymentDetails struct { diff --git a/app/internal/page/how_should_attorneys_make_decisions.go b/app/internal/page/how_should_attorneys_make_decisions.go new file mode 100644 index 0000000000..100d05e6af --- /dev/null +++ b/app/internal/page/how_should_attorneys_make_decisions.go @@ -0,0 +1,79 @@ +package page + +import ( + "net/http" + + "github.com/ministryofjustice/opg-go-common/template" +) + +type howShouldAttorneysMakeDecisionsData struct { + App AppData + Errors map[string]string + Form *howShouldAttorneysMakeDecisionsForm +} + +type howShouldAttorneysMakeDecisionsForm struct { + DecisionsType string + DecisionsDetails string +} + +func HowShouldAttorneysMakeDecisions(tmpl template.Template, lpaStore LpaStore) Handler { + return func(appData AppData, w http.ResponseWriter, r *http.Request) error { + lpa, err := lpaStore.Get(r.Context(), appData.SessionID) + if err != nil { + return err + } + + data := &howShouldAttorneysMakeDecisionsData{ + App: appData, + Form: &howShouldAttorneysMakeDecisionsForm{ + DecisionsType: lpa.DecisionsType, + DecisionsDetails: lpa.DecisionsDetails, + }, + } + + if r.Method == http.MethodPost { + data.Form = readHowShouldAttorneysMakeDecisionsForm(r) + data.Errors = data.Form.Validate() + + if len(data.Errors) == 0 { + lpa.DecisionsType = data.Form.DecisionsType + + if data.Form.DecisionsType != "mixed" { + lpa.DecisionsDetails = "" + } else { + lpa.DecisionsDetails = data.Form.DecisionsDetails + } + + if err := lpaStore.Put(r.Context(), appData.SessionID, lpa); err != nil { + return err + } + appData.Lang.Redirect(w, r, wantReplacementAttorneysPath, http.StatusFound) + return nil + } + } + + return tmpl(w, data) + } +} + +func readHowShouldAttorneysMakeDecisionsForm(r *http.Request) *howShouldAttorneysMakeDecisionsForm { + return &howShouldAttorneysMakeDecisionsForm{ + DecisionsType: postFormString(r, "decision-type"), + DecisionsDetails: postFormString(r, "mixed-details"), + } +} + +func (f *howShouldAttorneysMakeDecisionsForm) Validate() map[string]string { + errors := map[string]string{} + + if f.DecisionsType != "jointly-and-severally" && f.DecisionsType != "jointly" && f.DecisionsType != "mixed" { + errors["decision-type"] = "chooseADecisionType" + } + + if f.DecisionsType == "mixed" && f.DecisionsDetails == "" { + errors["mixed-details"] = "provideDecisionDetails" + } + + return errors +} diff --git a/app/internal/page/how_should_attorneys_make_decisions_test.go b/app/internal/page/how_should_attorneys_make_decisions_test.go new file mode 100644 index 0000000000..34c0042d38 --- /dev/null +++ b/app/internal/page/how_should_attorneys_make_decisions_test.go @@ -0,0 +1,310 @@ +package page + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetHowShouldAttorneysMakeDecisions(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{}, nil) + + template := &mockTemplate{} + template. + On("Func", w, &howShouldAttorneysMakeDecisionsData{ + App: appData, + Form: &howShouldAttorneysMakeDecisionsForm{}, + }). + Return(nil) + + r, _ := http.NewRequest(http.MethodGet, "/", nil) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, template, lpaStore) +} + +func TestGetHowShouldAttorneysMakeDecisionsFromStore(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{DecisionsDetails: "some decisions", DecisionsType: "jointly"}, nil) + + template := &mockTemplate{} + template. + On("Func", w, &howShouldAttorneysMakeDecisionsData{ + App: appData, + Form: &howShouldAttorneysMakeDecisionsForm{ + DecisionsType: "jointly", + DecisionsDetails: "some decisions", + }, + }). + Return(nil) + + r, _ := http.NewRequest(http.MethodGet, "/", nil) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, template, lpaStore) +} + +func TestGetHowShouldAttorneysMakeDecisionsWhenStoreErrors(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{}, expectedError) + + r, _ := http.NewRequest(http.MethodGet, "/", nil) + + err := HowShouldAttorneysMakeDecisions(nil, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Equal(t, expectedError, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, lpaStore) +} + +func TestGetHowShouldAttorneysMakeDecisionsWhenTemplateErrors(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{}, nil) + + template := &mockTemplate{} + template. + On("Func", w, &howShouldAttorneysMakeDecisionsData{ + App: appData, + Form: &howShouldAttorneysMakeDecisionsForm{ + DecisionsType: "", + DecisionsDetails: "", + }, + }). + Return(expectedError) + + r, _ := http.NewRequest(http.MethodGet, "/", nil) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Equal(t, expectedError, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, template, lpaStore) +} + +func TestPostHowShouldAttorneysMakeDecisions(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{DecisionsDetails: "", DecisionsType: ""}, nil) + lpaStore. + On("Put", mock.Anything, "session-id", &Lpa{DecisionsDetails: "", DecisionsType: "jointly"}). + Return(nil) + + template := &mockTemplate{} + + form := url.Values{ + "decision-type": {"jointly"}, + "mixed-details": {""}, + } + + r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", formUrlEncoded) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Nil(t, err) + assert.Equal(t, http.StatusFound, resp.StatusCode) + assert.Equal(t, wantReplacementAttorneysPath, resp.Header.Get("Location")) + mock.AssertExpectationsForObjects(t, lpaStore) +} + +func TestPostHowShouldAttorneysMakeDecisionsFromStore(t *testing.T) { + testCases := map[string]struct { + existingType string + existingDetails string + updatedType string + updatedDetails string + formType string + formDetails string + }{ + "existing details not set": { + existingType: "jointly-and-severally", + existingDetails: "", + updatedType: "mixed", + updatedDetails: "some details", + formType: "mixed", + formDetails: "some details", + }, + "existing details set": { + existingType: "mixed", + existingDetails: "some details", + updatedType: "jointly", + updatedDetails: "", + formType: "jointly", + formDetails: "some details", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{DecisionsDetails: tc.existingDetails, DecisionsType: tc.existingType}, nil) + lpaStore. + On("Put", mock.Anything, "session-id", &Lpa{DecisionsDetails: tc.updatedDetails, DecisionsType: tc.updatedType}). + Return(nil) + + template := &mockTemplate{} + + form := url.Values{ + "decision-type": {tc.formType}, + "mixed-details": {tc.formDetails}, + } + + r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", formUrlEncoded) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Nil(t, err) + assert.Equal(t, http.StatusFound, resp.StatusCode) + assert.Equal(t, wantReplacementAttorneysPath, resp.Header.Get("Location")) + mock.AssertExpectationsForObjects(t, lpaStore) + }) + } +} + +func TestPostHowShouldAttorneysMakeDecisionsWhenStoreErrors(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{}, expectedError) + + form := url.Values{ + "decision-type": {"jointly"}, + "mixed-details": {"some decisions"}, + } + + r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", formUrlEncoded) + + err := HowShouldAttorneysMakeDecisions(nil, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Equal(t, expectedError, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, lpaStore) +} + +func TestPostHowShouldAttorneysMakeDecisionsWhenValidationErrors(t *testing.T) { + w := httptest.NewRecorder() + + lpaStore := &mockLpaStore{} + lpaStore. + On("Get", mock.Anything, "session-id"). + Return(&Lpa{DecisionsDetails: "", DecisionsType: ""}, nil) + + template := &mockTemplate{} + template. + On("Func", w, &howShouldAttorneysMakeDecisionsData{ + App: appData, + Errors: map[string]string{ + "decision-type": "chooseADecisionType", + }, + Form: &howShouldAttorneysMakeDecisionsForm{ + DecisionsType: "", + DecisionsDetails: "", + }, + }). + Return(nil) + + form := url.Values{ + "decision-type": {""}, + } + + r, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", formUrlEncoded) + + err := HowShouldAttorneysMakeDecisions(template.Func, lpaStore)(appData, w, r) + resp := w.Result() + + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + mock.AssertExpectationsForObjects(t, lpaStore, template) +} + +func TestValidateForm(t *testing.T) { + testCases := map[string]struct { + DecisionType string + DecisionDetail string + ExpectedErrors map[string]string + }{ + "valid": { + DecisionType: "jointly-and-severally", + DecisionDetail: "", + ExpectedErrors: map[string]string{}, + }, + "valid with detail": { + DecisionType: "mixed", + DecisionDetail: "some details", + ExpectedErrors: map[string]string{}, + }, + "unsupported decision type": { + DecisionType: "not-supported", + DecisionDetail: "", + ExpectedErrors: map[string]string{"decision-type": "chooseADecisionType"}, + }, + "missing decision type": { + DecisionType: "", + DecisionDetail: "", + ExpectedErrors: map[string]string{"decision-type": "chooseADecisionType"}, + }, + "missing decision detail when mixed": { + DecisionType: "mixed", + DecisionDetail: "", + ExpectedErrors: map[string]string{"mixed-details": "provideDecisionDetails"}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + form := howShouldAttorneysMakeDecisionsForm{ + DecisionsType: tc.DecisionType, + DecisionsDetails: tc.DecisionDetail, + } + + assert.Equal(t, tc.ExpectedErrors, form.Validate()) + }) + } +} diff --git a/app/internal/page/paths.go b/app/internal/page/paths.go index 4eee5af752..90fd72f6a8 100644 --- a/app/internal/page/paths.go +++ b/app/internal/page/paths.go @@ -13,6 +13,7 @@ const ( dashboardPath = "/dashboard" howDoYouKnowYourCertificateProviderPath = "/how-do-you-know-your-certificate-provider" howLongHaveYouKnownCertificateProviderPath = "/how-long-have-you-known-certificate-provider" + howShouldAttorneysMakeDecisionsPath = "/how-should-attorneys-make-decisions" howToSignPath = "/how-to-sign" howWouldYouLikeToBeContactedPath = "/how-would-you-like-to-be-contacted" identityConfirmedPath = "/identity-confirmed" diff --git a/app/internal/page/read_your_lpa.go b/app/internal/page/read_your_lpa.go index b38f17bd6f..685dfbfd18 100644 --- a/app/internal/page/read_your_lpa.go +++ b/app/internal/page/read_your_lpa.go @@ -8,11 +8,16 @@ import ( ) type readYourLpaData struct { - App AppData - Errors map[string]string - Lpa *Lpa - EnteredSignature bool - Form *readYourLpaForm + App AppData + Errors map[string]string + Lpa *Lpa + EnteredSignature bool + Form *readYourLpaForm + HowAttorneysMakeDecisionsPath string + ChooseAttorneysPath string + WhenCanLpaBeUsedPath string + RestrictionsPath string + CertificatesProviderPath string } func ReadYourLpa(tmpl template.Template, lpaStore LpaStore) Handler { @@ -30,6 +35,11 @@ func ReadYourLpa(tmpl template.Template, lpaStore LpaStore) Handler { Confirm: lpa.ConfirmFreeWill, Signature: lpa.EnteredSignatureCode, }, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, } if r.Method == http.MethodPost { diff --git a/app/internal/page/read_your_lpa_test.go b/app/internal/page/read_your_lpa_test.go index 53e8e0cb1e..67feab3563 100644 --- a/app/internal/page/read_your_lpa_test.go +++ b/app/internal/page/read_your_lpa_test.go @@ -22,9 +22,14 @@ func TestGetReadYourLpa(t *testing.T) { template := &mockTemplate{} template. On("Func", w, &readYourLpaData{ - App: appData, - Form: &readYourLpaForm{}, - Lpa: &Lpa{}, + App: appData, + Form: &readYourLpaForm{}, + Lpa: &Lpa{}, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, }). Return(nil) @@ -80,6 +85,11 @@ func TestGetReadYourLpaFromStore(t *testing.T) { Confirm: true, Signature: "4567", }, + HowAttorneysMakeDecisionsPath: howShouldAttorneysMakeDecisionsPath, + ChooseAttorneysPath: chooseAttorneysPath, + WhenCanLpaBeUsedPath: whenCanTheLpaBeUsedPath, + RestrictionsPath: restrictionsPath, + CertificatesProviderPath: certificateProviderDetailsPath, }). Return(nil) diff --git a/app/lang/cy.json b/app/lang/cy.json index 68c4aa506d..751ce97d9d 100644 --- a/app/lang/cy.json +++ b/app/lang/cy.json @@ -294,5 +294,20 @@ "myLastingPowersOfAttorney": "Welsh", "signed": "Welsh", "applicationNumber": "Welsh", - "viewTheLpaProgress": "Welsh" + "viewTheLpaProgress": "Welsh", + + "howShouldAttorneysMakeDecisions": "Welsh?", + "howShouldAttorneysMakeDecisionsDetail": "Welsh", + "getHelpMakingDecision": "Welsh", + "jointlyAndSeverally": "Welsh", + "jointlyAndSeverallyHint": "Welsh", + "jointly": "Welsh", + "jointlyHint": "Welsh", + "jointlyAndSeverallyMixed": "Welsh", + "jointlyAndSeverallyMixedHint": "Welsh", + "decisionDetailsHint": "Welsh", + "details": "Welsh", + "chooseADecisionType": "Welsh", + "provideDecisionDetails": "Welsh", + "removeDetails": "Welsh" } diff --git a/app/lang/en.json b/app/lang/en.json index 2037c7758a..75838e0164 100644 --- a/app/lang/en.json +++ b/app/lang/en.json @@ -288,5 +288,20 @@ "myLastingPowersOfAttorney": "My lasting powers of attorney", "signed": "Signed", "applicationNumber": "Application number", - "viewTheLpaProgress": "View the LPA progress" + "viewTheLpaProgress": "View the LPA progress", + + "howShouldAttorneysMakeDecisions": "How should the attorneys make decisions?", + "howShouldAttorneysMakeDecisionsDetail": "The donor's choice here is very important as it affects how their LPA can be used. Whichever option the donor chooses, the attorneys must always act in the donor's best interests.", + "getHelpMakingDecision": "Get help with making this decision", + "jointlyAndSeverally": "Jointly and severally", + "jointlyAndSeverallyHint": "Attorneys can make decisions on their own or together. Most people choose this option because it's the most practical.", + "jointly": "Jointly", + "jointlyHint": "Attorneys must agree unanimously on every decision, however big or small. Be careful - if one of the attorneys can no longer act, none of the other attorneys will be able to act either, unless the donor states otherwise in their instructions.", + "jointlyAndSeverallyMixed": "Jointly for some decisions, and jointly and severally for other decisions", + "jointlyAndSeverallyMixedHint": "Attorneys must agree unanimously on some decisions, but can make others on their own. The donor must state which decisions need to be agreed unanimously.", + "decisionDetailsHint": " Please tell us which decisions the donor wants to be made jointly. These decisions will be printed on a extra sheet that the donor will need to sign and date. Take a look at the guidance for examples of how the donor can make their wishes clear.", + "details": "Details", + "chooseADecisionType": "Select from the listed decision options", + "provideDecisionDetails": "Provide details on how your attorneys should act", + "removeDetails": "Only provide details on how attorneys should act if selecting Jointly for some decisions, and jointly and severally for other decisions" } diff --git a/app/web/template/check_your_lpa.gohtml b/app/web/template/check_your_lpa.gohtml index 8c9301578b..61159f9b72 100644 --- a/app/web/template/check_your_lpa.gohtml +++ b/app/web/template/check_your_lpa.gohtml @@ -33,7 +33,7 @@ {{ .Lpa.WhenCanTheLpaBeUsed }}
- + {{ tr .App "change" }} {{ tr .App "whenTheLpaCanBeUsed" }}
@@ -47,7 +47,7 @@ {{ .Lpa.AttorneysFullNames }}
- + {{ tr .App "change" }} {{ tr .App "yourAttorneys" }}
@@ -58,10 +58,10 @@ {{ tr .App "howTheAttorneysMustWorkTogether" }}
- We haven't done this bit yet + {{ .Lpa.DecisionsType }}
- + {{ tr .App "change" }} we haven't done this bit yet
@@ -89,7 +89,7 @@

{{ .Lpa.Restrictions }}

- + {{ tr .App "change" }} {{ tr .App "yourRestrictions" }}
@@ -158,7 +158,7 @@ {{ .Lpa.CertificateProvider.FirstNames }} {{ .Lpa.CertificateProvider.LastName }}
- + {{ tr .App "change" }} {{ lowerFirst (tr .App "certificateProvider") }}
@@ -172,7 +172,7 @@ {{ .Lpa.CertificateProvider.Email }}
- + {{ tr .App "change" }} {{ tr .App "certificateProviderEmail" }}
diff --git a/app/web/template/how_should_attorneys_make_decisions.gohtml b/app/web/template/how_should_attorneys_make_decisions.gohtml new file mode 100644 index 0000000000..21d43d3b6f --- /dev/null +++ b/app/web/template/how_should_attorneys_make_decisions.gohtml @@ -0,0 +1,80 @@ +{{ template "page" . }} + +{{ define "main" }} +
+
+

{{ tr .App "howShouldAttorneysMakeDecisions" }}

+ +

{{ tr .App "howShouldAttorneysMakeDecisionsDetail" }}

+ +

+ {{ tr .App "getHelpMakingDecision" }} +

+ +
+
+ + {{ tr .App "howShouldAttorneysMakeDecisions" }} + + +
+ {{ template "error-message" (errorMessage . "decision-type") }} + +
+
+ + + +
+ {{ tr .App "jointlyAndSeverallyHint" }} +
+
+
+ + + +
+ {{ tr .App "jointlyHint" }} +
+
+
+ + + +
+ {{ tr .App "jointlyAndSeverallyMixedHint" }} +
+ +
+
+

{{ tr .App "decisionDetailsHint" }}

+ + {{ if index .Errors "mixed-details" }} +

+ {{ tr .App (index .Errors "mixed-details") }} +

+ {{ end }} + + +
+
+
+
+
+ +
+ {{ template "continue-button" . }} +
+
+
+
+
+{{ end }} diff --git a/app/web/template/layout/radios.gohtml b/app/web/template/layout/radios.gohtml index 903ed1de52..c23efc5f8e 100644 --- a/app/web/template/layout/radios.gohtml +++ b/app/web/template/layout/radios.gohtml @@ -2,7 +2,14 @@
{{ range $i, $e := .items }}
- + diff --git a/app/web/template/read_your_lpa.gohtml b/app/web/template/read_your_lpa.gohtml index af811ca834..03443ba451 100644 --- a/app/web/template/read_your_lpa.gohtml +++ b/app/web/template/read_your_lpa.gohtml @@ -35,7 +35,7 @@ {{ .Lpa.WhenCanTheLpaBeUsed }}
- + {{ tr .App "change" }} {{ tr .App "whenTheLpaCanBeUsed" }}
@@ -49,7 +49,7 @@ {{ .Lpa.AttorneysFullNames }}
- + {{ tr .App "change" }} {{ tr .App "yourAttorneys" }}
@@ -60,10 +60,10 @@ {{ tr .App "howTheAttorneysMustWorkTogether" }}
- We haven't done this bit yet + {{ .Lpa.WhenCanTheLpaBeUsed }}
- + {{ tr .App "change" }} we haven't done this bit yet
@@ -91,7 +91,7 @@

{{ .Lpa.Restrictions }}

- + {{ tr .App "change" }} {{ tr .App "yourRestrictions" }}
diff --git a/cypress.config.js b/cypress.config.js index 95a6c0b63c..122a6a249a 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,7 +5,8 @@ module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:5050', experimentalSessionAndOrigin: true, - pageLoadTimeout: 5000, + defaultCommandTimeout: 2000, + pageLoadTimeout: 3000, setupNodeEvents(on, config) { on('task', { log(message) { @@ -13,6 +14,11 @@ module.exports = defineConfig({ return null }, + table(message) { + console.table(message) + + return null + } }) }, video: false diff --git a/cypress/e2e/how-should-attorneys-make-decisions.cy.js b/cypress/e2e/how-should-attorneys-make-decisions.cy.js new file mode 100644 index 0000000000..034abcfb9e --- /dev/null +++ b/cypress/e2e/how-should-attorneys-make-decisions.cy.js @@ -0,0 +1,39 @@ +describe('How should attorneys make decisions', () => { + beforeEach(() => { + cy.visit('/testing-start?redirect=/how-should-attorneys-make-decisions?cookiesAccepted=1'); + cy.injectAxe(); + }); + + it('can choose how attorneys act', () => { + cy.contains('h1', 'How should the attorneys make decisions?'); + + // see https://github.com/alphagov/govuk-frontend/issues/979 + cy.checkA11y(null, { rules: { region: { enabled: false }, 'aria-allowed-attr': { enabled: false } } }); + + cy.get('input[name="decision-type"]').check('jointly'); + + cy.contains('button', 'Continue').click(); + + cy.url().should('contain', '/want-replacement-attorneys'); + + cy.injectAxe(); + cy.checkA11y(null, { rules: { region: { enabled: false }, 'aria-allowed-attr': { enabled: false } } }); + + }); + + it('can choose how attorneys act - Jointly for some decisions, and jointly and severally for other decisions', () => { + cy.contains('h1', 'How should the attorneys make decisions?'); + + cy.checkA11y(null, { rules: { region: { enabled: false }, 'aria-allowed-attr': { enabled: false } } }); + + cy.get('input[name="decision-type"]').check('mixed'); + cy.get('#mixed-details').type('some details on attorneys'); + + cy.contains('button', 'Continue').click(); + + cy.url().should('contain', '/want-replacement-attorneys'); + + cy.injectAxe(); + cy.checkA11y(null, { rules: { region: { enabled: false }, 'aria-allowed-attr': { enabled: false } } }); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 119ab03f7c..02086b6fdc 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,3 +23,28 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +function terminalLog(violations) { + cy.task( + 'log', + `${violations.length} accessibility violation${ + violations.length === 1 ? '' : 's' + } ${violations.length === 1 ? 'was' : 'were'} detected` + ) + // pluck specific keys to keep the table readable + const violationData = violations.map( + ({ id, impact, description, nodes }) => ({ + id, + impact, + description, + nodes: nodes.length + }) + ) + + cy.task('table', violationData) +} + +// Adds a table to the terminal with violation details +Cypress.Commands.add('checkA11yVvv', () => { + cy.checkA11y(null, { rules: { region: { enabled: false } } }, terminalLog); +}) diff --git a/package.json b/package.json index d635e23404..91bd8b79a9 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,7 @@ "build:fonts": "mkdir -p web/static/assets/fonts && cp node_modules/govuk-frontend/govuk/assets/fonts/* web/static/assets/fonts", "cypress:open": "node_modules/.bin/cypress open", "cypress:run": "node_modules/.bin/cypress run -vvv", - "cypress:run:debug": "node_modules/.bin/cypress run --headed", - "cypress:parallel": "cypress-parallel -s cypress:run -t 4 -d ./cypress/e2e", - "test": "echo \"Error: no test specified\" && exit 1" + "cypress:parallel": "cypress-parallel -s cypress:run -t 4 -d ./cypress/e2e" }, "license": "MIT", "dependencies": {