Skip to content
Open

PO-2948 #2263

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
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,21 @@ describe('FinesMacFixedPenaltyFormComponent', () => {
vi.useRealTimers();
});

it('should set offenceCodeValidationPending immediately when lookup-length offence code changes', () => {
vi.useFakeTimers();
component['setupFixedPenaltyDetailsForm']();
component['setupOffenceCodeListener']();
component.form.get('fm_fp_offence_details_offence_id')?.setValue(314441);

component.form.get('fm_fp_offence_details_offence_cjs_code')?.setValue('AK12345');

expect(component.form.get('fm_fp_offence_details_offence_id')?.value).toBeNull();
expect(component.form.get('fm_fp_offence_details_offence_cjs_code')?.errors).toEqual({
offenceCodeValidationPending: true,
});
vi.useRealTimers();
});

it('should set initial value if dob value already exists', () => {
component['setupFixedPenaltyDetailsForm']();
component.form.get('fm_fp_personal_details_dob')?.setValue('01-01-1979');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,17 @@ export const FINES_MAC_OFFENCE_DETAILS_OFFENCES_FIELD_ERRORS: IAbstractFormBaseF
message: 'Offence code must be 7 or 8 characters',
priority: 4,
},
offenceCodeLookupFailed: {
message: 'We could not validate the offence code. Try again',
priority: 5,
},
offenceCodeValidationPending: {
message: 'Wait for offence code validation to complete',
priority: 6,
},
invalidOffenceCode: {
message: 'Offence not found',
priority: 5,
priority: 7,
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('FinesMacOffenceDetailsAddAnOffenceFormComponent', () => {
'upperCaseFirstLetter',
'scrollToTop',
]);
mockUtilsService.upperCaseAllLetters.mockImplementation((value: string) => value?.toUpperCase?.() ?? value);

await TestBed.configureTestingModule({
imports: [FinesMacOffenceDetailsAddAnOffenceFormComponent],
Expand Down Expand Up @@ -729,6 +730,115 @@ describe('FinesMacOffenceDetailsAddAnOffenceFormComponent', () => {
expect(superHandleFormSubmitSpy).toHaveBeenCalledWith(event);
});

it('should set offenceCodeValidationPending on submit when offence code length is valid and offence id is unresolved', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors(null);
offenceIdControl.setValue(null);

component.handleFormSubmit(new SubmitEvent('submit'));

expect(offenceCodeControl.errors).toEqual(expect.objectContaining({ offenceCodeValidationPending: true }));
});

it('should preserve existing offence code errors when setting offenceCodeValidationPending', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors({ customError: true });
offenceIdControl.setValue(null);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(component as any).enforceOffenceCodeValidationBeforeSubmit();

expect(offenceCodeControl.errors).toEqual({
customError: true,
offenceCodeValidationPending: true,
});
});

it('should handle null offence code values without setting pending validation', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue(null);
offenceCodeControl.setErrors({ offenceCodeValidationPending: true });
offenceIdControl.setValue(null);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(component as any).enforceOffenceCodeValidationBeforeSubmit();

expect(offenceCodeControl.errors).toBeNull();
});

it('should not set offenceCodeValidationPending on submit when offence code is already invalid', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors({ invalidOffenceCode: true });
offenceIdControl.setValue(null);

component.handleFormSubmit(new SubmitEvent('submit'));

expect(offenceCodeControl.errors).toEqual({ invalidOffenceCode: true });
expect(offenceCodeControl.errors?.['offenceCodeValidationPending']).toBeUndefined();
});

it('should not set offenceCodeValidationPending on submit when offence lookup failed', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors({ offenceCodeLookupFailed: true });
offenceIdControl.setValue(null);

component.handleFormSubmit(new SubmitEvent('submit'));

expect(offenceCodeControl.errors).toEqual({ offenceCodeLookupFailed: true });
expect(offenceCodeControl.errors?.['offenceCodeValidationPending']).toBeUndefined();
});

it('should remove offenceCodeValidationPending and keep other existing errors', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors({
offenceCodeValidationPending: true,
invalidOffenceCode: true,
});
offenceIdControl.setValue(null);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(component as any).enforceOffenceCodeValidationBeforeSubmit();

expect(offenceCodeControl.errors).toEqual({ invalidOffenceCode: true });
});

it('should clear offenceCodeValidationPending on submit when offence id is set', () => {
const offenceCodeControl = component.form.controls['fm_offence_details_offence_cjs_code'];
const offenceIdControl = component.form.controls['fm_offence_details_offence_id'];

offenceCodeControl.setValue('AK12345');
offenceCodeControl.setErrors({ offenceCodeValidationPending: true });
offenceIdControl.setValue(314441);

component.handleFormSubmit(new SubmitEvent('submit'));

expect(offenceCodeControl.errors?.['offenceCodeValidationPending']).toBeUndefined();
});

it('should return early when offence code or offence id controls are missing', () => {
component.form.removeControl('fm_offence_details_offence_id');

// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect(() => (component as any).enforceOffenceCodeValidationBeforeSubmit()).not.toThrow();
});

it('should add a new draft offence when index is -1', () => {
component.form.controls['fm_offence_details_id'].setValue('test-id');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,42 @@ export class FinesMacOffenceDetailsAddAnOffenceFormComponent
}
}

/**
* Ensures the offence code lookup has completed before allowing submission.
* If a 7 or 8 character code has no resolved offence id yet, a pending-validation
* error is applied so the form remains invalid and the user sees a clear message.
*/
private enforceOffenceCodeValidationBeforeSubmit(): void {
const offenceCodeControl = this.form.get('fm_offence_details_offence_cjs_code') as FormControl | null;
const offenceIdControl = this.form.get('fm_offence_details_offence_id') as FormControl | null;

if (!offenceCodeControl || !offenceIdControl) {
return;
}

const offenceCode = offenceCodeControl.value ?? '';
const isLookupLength = offenceCode.length >= 7 && offenceCode.length <= 8;
const hasOffenceId = offenceIdControl.value !== null && offenceIdControl.value !== undefined;
const hasInvalidOffenceCodeError = Boolean(offenceCodeControl.errors?.['invalidOffenceCode']);
const hasOffenceCodeLookupFailedError = Boolean(offenceCodeControl.errors?.['offenceCodeLookupFailed']);

if (isLookupLength && !hasOffenceId && !hasInvalidOffenceCodeError && !hasOffenceCodeLookupFailedError) {
const currentErrors = offenceCodeControl.errors;
const updatedErrors = currentErrors
? { ...currentErrors, offenceCodeValidationPending: true }
: { offenceCodeValidationPending: true };
offenceCodeControl.setErrors(updatedErrors, { emitEvent: false });
return;
}

const currentErrors = offenceCodeControl.errors;
if (currentErrors?.['offenceCodeValidationPending']) {
const remainingErrors = { ...currentErrors };
delete remainingErrors['offenceCodeValidationPending'];
offenceCodeControl.setErrors(Object.keys(remainingErrors).length ? remainingErrors : null, { emitEvent: false });
}
}

/**
* Navigates to the minor creditor page for the specified row index.
*
Expand Down Expand Up @@ -610,6 +646,7 @@ export class FinesMacOffenceDetailsAddAnOffenceFormComponent
*/
public override handleFormSubmit(event: SubmitEvent): void {
this.checkImpositionMinorCreditors();
this.enforceOffenceCodeValidationBeforeSubmit();
super.handleFormSubmit(event);
}

Expand Down
Loading
Loading