Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6ac3cf2
feat: add OPAL TDIA extraction skill and script for markdown conversion
max-holland Mar 17, 2026
1bd6241
feat: enhance TDIA extraction script to support E2E interactions imag…
max-holland Mar 17, 2026
4fe4182
feat: add OPAL Ticket Context skill and agent configuration for TDIA …
max-holland Mar 17, 2026
846bbed
feat: add convert-to-company action visibility assertions and related…
max-holland Mar 17, 2026
c94f221
feat: implement convert-to-company functionality with confirmation an…
max-holland Mar 17, 2026
9bbd96d
feat: implement convert mode for fines account conversion and update …
max-holland Mar 17, 2026
d30cc64
feat: add assertions for company details convert route in account con…
max-holland Mar 17, 2026
00db3ea
feat: Implement convert to individual account functionality
max-holland Mar 17, 2026
b34bc93
feat: add setSuccessMessage mock to FinesAccPartyAddAmendConvert tests
max-holland Mar 17, 2026
89bf872
feat: update defendant account party conversion logic and add test fo…
max-holland Mar 17, 2026
339bf09
feat: update image repositories to production for nodejs, opal-fines-…
max-holland Mar 17, 2026
e29cc39
feat: update Dockerfile to use production base image for node
max-holland Mar 17, 2026
c9d5c21
Bumping chart version/ fixing aliases
hmcts-jenkins-cnp[bot] Mar 17, 2026
060fe71
feat: refactor navigation logic and improve code readability in defen…
max-holland Mar 17, 2026
c3bb276
Merge branch 'master' into convert_def_acc
Arnabsubedi233 Mar 17, 2026
8001aef
feat: enhance action link styling for account conversion in defendant…
max-holland Mar 17, 2026
f12b3a9
feat: update tests and payload transformation logic for defendant acc…
max-holland Mar 17, 2026
4722d5d
feat: implement defendant account conversion logic and update related…
max-holland Mar 18, 2026
bae8a1d
feat: simplify account conversion logic by removing unused service an…
max-holland Mar 18, 2026
9fc6fb3
feat: add account conversion success messages
max-holland Mar 18, 2026
f1eae3f
test: remove unnecessary blank line in FinesAccConvertComponent tests
max-holland Mar 18, 2026
8370182
Revert "feat: add OPAL Ticket Context skill and agent configuration f…
max-holland Mar 18, 2026
20821b4
Revert "feat: enhance TDIA extraction script to support E2E interacti…
max-holland Mar 18, 2026
c900919
Revert "feat: add OPAL TDIA extraction skill and script for markdown …
max-holland Mar 18, 2026
ab13cdd
docs: add convert component method comments
max-holland Mar 18, 2026
f41b671
chore: drop registry config changes from convert PR
max-holland Mar 18, 2026
3c082ef
test: use stable hooks for convert flow selectors
max-holland Mar 18, 2026
b8c28a4
feat: add assertions and conversion methods for account details in th…
max-holland Mar 18, 2026
8fcf547
feat: enhance completeConvertToIndividual method with detailed parame…
max-holland Mar 19, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { AccountConvertLocators as L } from '../../../../../shared/selectors/account-details/account.convert.locators';
import { createScopedLogger } from '../../../../../support/utils/log.helper';
import { CommonActions } from '../common/common.actions';

const log = createScopedLogger('AccountConvertActions');

/** Actions and assertions for the convert-account confirmation page. */
export class AccountConvertActions {
private static readonly DEFAULT_TIMEOUT = 10_000;
private readonly common = new CommonActions();

/**
* Normalizes visible text for resilient assertions.
*
* @param value - Raw text content.
* @returns Lower-cased single-spaced text.
*/
private normalize(value: string): string {
return value.replace(/\s+/g, ' ').trim().toLowerCase();
}

/**
* Asserts the convert-to-company confirmation page content.
*
* @param expectedCaptionName - Expected defendant name in the page caption.
*/
public assertOnConvertToCompanyConfirmation(expectedCaptionName: string): void {
log('assert', 'Convert to company confirmation page is visible', { expectedCaptionName });
cy.location('pathname', { timeout: this.common.getPathTimeout() }).should('include', '/convert/company');

cy.get(L.page.caption, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.invoke('text')
.then((text) => {
const actual = this.normalize(text);
expect(actual).to.include(this.normalize(expectedCaptionName));
expect(actual).to.include('-');
});

cy.get(L.page.header, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.should(($el) => {
const text = this.normalize($el.text());
expect(text).to.include('are you sure you want to convert this account to a company account?');
});

cy.get(L.page.warningText, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.should(($el) => {
const text = this.normalize($el.text());
expect(text).to.include(
this.normalize('Certain data related to individual accounts, such as employment details, will be removed.'),
);
});

cy.get(L.page.confirmButton, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('exist');
cy.get(L.page.cancelLink, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('exist');
}

/**
* Asserts the convert-to-individual confirmation page content.
*
* @param expectedCaptionName - Expected company name in the page caption.
*/
public assertOnConvertToIndividualConfirmation(expectedCaptionName: string): void {
log('assert', 'Convert to individual confirmation page is visible', { expectedCaptionName });
cy.location('pathname', { timeout: this.common.getPathTimeout() }).should('include', '/convert/individual');

cy.get(L.page.caption, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.invoke('text')
.then((text) => {
const actual = this.normalize(text);
expect(actual).to.include(this.normalize(expectedCaptionName));
expect(actual).to.include('-');
});

cy.get(L.page.header, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.should(($el) => {
const text = this.normalize($el.text());
expect(text).to.include('are you sure you want to convert this account to an individual account?');
});

cy.get(L.page.warningText, { timeout: AccountConvertActions.DEFAULT_TIMEOUT })
.should('be.visible')
.should(($el) => {
const text = this.normalize($el.text());
expect(text).to.include(
this.normalize('Some information specific to company accounts, such as company name, will be removed.'),
);
});

cy.get(L.page.confirmButton, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('exist');
cy.get(L.page.cancelLink, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('exist');
}

/**
* Clicks the confirmation button to continue to Company details.
*/
public confirmConvertToCompany(): void {
log('action', 'Confirming account conversion to company');
cy.get(L.page.confirmButton, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('be.visible').click();
}

/**
* Clicks the cancel link to return to Defendant details.
*/
public cancelConvertToCompany(): void {
log('action', 'Cancelling account conversion to company');
cy.get(L.page.cancelLink, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('be.visible').click();
}

/**
* Clicks the confirmation button to continue to Defendant details.
*/
public confirmConvertToIndividual(): void {
log('action', 'Confirming account conversion to individual');
cy.get(L.page.confirmButton, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('be.visible').click();
}

/**
* Clicks the cancel link to return to Defendant details.
*/
public cancelConvertToIndividual(): void {
log('action', 'Cancelling account conversion to individual');
cy.get(L.page.cancelLink, { timeout: AccountConvertActions.DEFAULT_TIMEOUT }).should('be.visible').click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,75 @@ export class AccountDetailsDefendantActions {
assertDefendantNameContains(expected: string): void {
cy.get(L.defendant.fields.name, this.common.getTimeoutOptions()).should('contain.text', expected);
}

/**
* Asserts the defendant summary card is rendered in the Defendant tab.
*/
assertDefendantSummaryVisible(): void {
cy.get(L.defendant.card, this.common.getTimeoutOptions()).should('be.visible');
}

/**
* Asserts the defendant summary card is not rendered in the Defendant tab.
*/
assertDefendantSummaryNotPresent(): void {
cy.get(L.defendant.card, this.common.getTimeoutOptions()).should('not.exist');
}

/**
* Asserts the primary email address shown in the contact summary contains the expected value.
*
* @param expected - Expected text within the primary email field.
*/
assertPrimaryEmailContains(expected: string): void {
cy.get(L.contact.fields.primaryEmail, this.common.getTimeoutOptions()).should('contain.text', expected);
}

/**
* Asserts that the convert-to-company action is visible in the Defendant tab.
*/
assertConvertToCompanyActionVisible(): void {
cy.get(L.actions.convertAction, this.common.getTimeoutOptions())
.should('be.visible')
.and('contain.text', 'Convert to a company account');
}

/**
* Asserts that the convert-to-individual action is visible in the Defendant tab.
*/
assertConvertToIndividualActionVisible(): void {
cy.get(L.actions.convertAction, this.common.getTimeoutOptions())
.should('be.visible')
.and('contain.text', 'Convert to an individual account');
}

/**
* Asserts that the visible convert action does not contain the company label.
*/
assertConvertToCompanyActionTextNotPresent(): void {
cy.get(L.actions.convertAction, this.common.getTimeoutOptions())
.should('be.visible')
.and('not.contain.text', 'Convert to a company account');
}

/**
* Clicks the convert-to-company action from the Defendant tab.
*/
startConvertToCompanyAccount(): void {
cy.get(L.actions.convertActionLink, this.common.getTimeoutOptions()).should('be.visible').click();
}

/**
* Clicks the convert-to-individual action from the Defendant tab.
*/
startConvertToIndividualAccount(): void {
cy.get(L.actions.convertActionLink, this.common.getTimeoutOptions()).should('be.visible').click();
}

/**
* Asserts that the convert-to-company action is not rendered in the Defendant tab.
*/
assertConvertToCompanyActionNotPresent(): void {
cy.get(L.actions.convertAction, this.common.getTimeoutOptions()).should('not.exist');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,18 @@ export class AccountDetailsNavActions {
.and('have.attr', 'aria-current', 'page')
.and('contain.text', 'Payment terms');
}

/**
* Asserts the account details success banner shows the expected message.
*
* @param expected - Expected success banner text.
*/
assertSuccessBannerText(expected: string): void {
log('assert', 'Asserting account details success banner text', { expected });

cy.get(N.banners.success, { timeout: 10_000 })
.should('be.visible')
.find(N.banners.successText)
.should('contain.text', expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@ const log = createScopedLogger('EditCompanyDetailsActions');

/** Actions for editing company details within Account Details. */
export class EditCompanyDetailsActions {
private static readonly DEFAULT_TIMEOUT = 10_000;
private readonly common = new CommonActions();
private readonly companyFieldLocators = {
'Address line 1': L.fields.addressLine1,
'Address line 2': L.fields.addressLine2,
'Address line 3': L.fields.addressLine3,
Postcode: L.fields.postcode,
'Primary email address': L.fields.primaryEmail,
'Secondary email address': L.fields.secondaryEmail,
'Mobile telephone number': L.fields.mobileTelephone,
'Home telephone number': L.fields.homeTelephone,
'Work telephone number': L.fields.workTelephone,
'Make and model': L.fields.vehicleMakeModel,
'Registration number': L.fields.vehicleRegistration,
} as const;

/**
* Ensure we are still on the edit page (form visible, not navigated away).
Expand All @@ -23,6 +37,18 @@ export class EditCompanyDetailsActions {
log('done', 'Company edit form is visible');
}

/**
* Asserts the convert handoff lands on the Company details convert route.
*/
public assertOnConvertRoute(): void {
log('assert', 'Asserting Company details convert route');
cy.location('pathname', { timeout: this.common.getPathTimeout() }).should(
'match',
/\/fines\/account\/defendant\/\d+\/party\/company\/convert$/,
);
cy.get(L.form, { timeout: EditCompanyDetailsActions.DEFAULT_TIMEOUT }).should('be.visible');
}

/**
* Update the company name field on the edit form.
*
Expand Down Expand Up @@ -102,4 +128,37 @@ export class EditCompanyDetailsActions {
cy.get(SummaryL.fields.name, this.common.getTimeoutOptions()).should('be.visible').and('contain.text', expected);
log('done', `Verified company name contains "${expected}"`);
}

/**
* Asserts the company summary card is rendered in the Defendant tab.
*/
public assertCompanySummaryVisible(): void {
log('assert', 'Asserting company summary card is visible');
cy.get(SummaryL.card, this.common.getTimeoutOptions()).should('be.visible');
}

/**
* Asserts the company summary card is not rendered in the Defendant tab.
*/
public assertCompanySummaryNotPresent(): void {
log('assert', 'Asserting company summary card is absent');
cy.get(SummaryL.card, this.common.getTimeoutOptions()).should('not.exist');
}

/**
* Asserts Company details form fields are pre-populated with the expected values.
*
* @param expectedFieldValues - Key/value map of ticket field labels to expected values.
*/
public assertPrefilledFieldValues(expectedFieldValues: Record<string, string>): void {
Object.entries(expectedFieldValues).forEach(([fieldName, expectedValue]) => {
const fieldSelector = this.companyFieldLocators[fieldName as keyof typeof this.companyFieldLocators];
if (!fieldSelector) {
throw new Error(`Unsupported company prefill field: ${fieldName}`);
}

log('assert', 'Asserting company field prefill', { fieldName, expectedValue });
cy.get(fieldSelector, this.common.getTimeoutOptions()).should('have.value', expectedValue);
});
}
}
Loading
Loading