Skip to content

Commit

Permalink
Merge branch 'master' into feature/exui-2223-pt2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
StoneBenHMCTS authored Jan 3, 2025
2 parents 92f00eb + ada884f commit 75ced36
Show file tree
Hide file tree
Showing 82 changed files with 6,337 additions and 2,472 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/npmpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
name: code-coverage-report
path: coverage
- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@v3.0.0
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
18 changes: 17 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
## RELEASE NOTES
## RELEASE NOTES

### Version 7.0.75-exui-2575
**EXUI-2536** issue with DynamicMultiSelectList
**EXUI-2431** Special characters stopping events
**EXUI-2537** upgrade pdf-dist in media viewer
**EXUI-2389** PED and Media Viewer

### Version 7.0.75-exui-2315
**EXUI-2315** etrieve current user language selection


### Version 7.0.75-exui-2515
**EXUI-2515** case-link-issue

### Version 7.0.75-exui-2462-rc1
**EXUI-2462** DynamicRadioList incorrectly selects the wrong radio button

### Version 7.0.75
**EXUI-2415** Redirect to an new event by a hyperlink
Expand Down
3 changes: 2 additions & 1 deletion browserslist
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.
not op_mini all
not kaios 2.5
9 changes: 8 additions & 1 deletion demo/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export class AppConfig extends AbstractAppConfig {
'access_management_mode': true,
'refunds_url': '/api/refunds',
'payment_return_url': 'https://paymentoutcome-web.demo.platform.hmcts.net/',
'case_flags_refdata_api_url': '/refdata/commondata/caseflags/service-id=:sid'
'case_flags_refdata_api_url': '/refdata/commondata/caseflags/service-id=:sid',
'events_to_hide': [
'queryManagementRespondQuery'
]
};

constructor(private http: HttpClient) {
Expand Down Expand Up @@ -187,4 +190,8 @@ export class AppConfig extends AbstractAppConfig {
public getCaseFlagsRefdataApiUrl(): string {
return this.config.case_flags_refdata_api_url;
}

public getEventsToHide(): string[] {
return this.config.events_to_hide;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@edium/fsm": "^2.1.2",
"@hmcts/ccpay-web-component": "6.2.1",
"@hmcts/frontend": "0.0.50-alpha",
"@hmcts/media-viewer": "4.0.8",
"@hmcts/media-viewer": "4.0.9",
"@ngrx/effects": "17.2.0",
"@ngrx/store": "^17.2.0",
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
Expand Down
10 changes: 8 additions & 2 deletions projects/ccd-case-ui-toolkit/src/lib/app-config.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,26 @@ export class AppMockConfig implements AbstractAppConfig {
return '';
}

public getEventsToHide(): string[] {
return [];
}

public getEnableRestrictedCaseAccessConfig(): boolean {
return true;
}

public getEnableCaseFileViewVersion1_1(): boolean {
return true;
}

public getIcpEnable(): boolean {
return false;
}

public getIcpJurisdictions(): string[] {
return ['', ''];
}
public logMessage(): void {

public logMessage(msg: string): void {
console.log(msg);
}
}
2 changes: 2 additions & 0 deletions projects/ccd-case-ui-toolkit/src/lib/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export abstract class AbstractAppConfig {
public abstract getCaseFlagsRefdataApiUrl(): string;
public abstract getRDCommonDataApiUrl(): string;
public abstract getCaseDataStoreApiUrl(): string;
public abstract getEventsToHide(): string[];
public abstract getEnableRestrictedCaseAccessConfig(): boolean;
public abstract getEnableCaseFileViewVersion1_1(): boolean;
}
Expand Down Expand Up @@ -194,4 +195,5 @@ export class CaseEditorConfig {
public enable_case_file_view_version_1_1: boolean;
public icp_enabled: boolean;
public icp_jurisdictions: string[];
public events_to_hide: string[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { CaseEditUtils, convertNonASCIICharacter } from "./case-edit.utils";

describe('CaseEditUtils', () => {
const caseUtils: CaseEditUtils = new CaseEditUtils();
const LONG_ASCII_STRING = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@£$%^&*()-=[];\,./`<>?:"|{}_+';
const LONG_PRE_STRING: string = 'Examples of non-ASCII characters: éこ¥🌍';
const LONG_POST_STRING: string = 'Examples of non-ASCII characters: &#233;&#12371;&#165;&#55356;&#57101;';

describe('editNonASCIICharacters', () => {

it('should not edit undefined', () => {
// Note: Should never happen
const response = caseUtils.convertNonASCIICharacters(undefined);
expect(response).toEqual('');
});

it('should not edit an empty string', () => {
const mockString = '';
const response = caseUtils.convertNonASCIICharacters(mockString);
expect(response).toEqual(mockString);
});

it('should note edit ASCII characters', () => {
// note: string includes £ (non-ASCII) which should not be edited
const response = caseUtils.convertNonASCIICharacters(LONG_ASCII_STRING);
expect(response).toEqual(LONG_ASCII_STRING);
});

it('should not edit £ (non ASCII)', () => {
const mockString = 'Cost: £2.50';
const response = caseUtils.convertNonASCIICharacters(mockString);
expect(response).toEqual(mockString);
});

it('should edit ASCII characters', () => {
// Summarises with copied mock string
const response = caseUtils.convertNonASCIICharacters(LONG_PRE_STRING);
expect(response).toEqual(LONG_POST_STRING);

// Goes deeper into what should be happening just in case
const chineseCharacter = '漢';
const secondMockString = 'Examples of non-ASCII characters: ' + chineseCharacter;
const editedSecondMockString =
`Examples of non-ASCII characters: ${CaseEditUtils.PREFIX + chineseCharacter.charCodeAt(0) + CaseEditUtils.SUFFIX}`;
const secondResponse = caseUtils.convertNonASCIICharacters(secondMockString);
expect(secondResponse).toEqual(editedSecondMockString);
});
});

describe('revertEditNonASCIICharacters', () => {

it('should not revert strings without the prefix and/or suffix', () => {
const mockString = 'Hello World!';
const response = caseUtils.convertHTMLEntities(mockString);
expect(response).toEqual(mockString);
});

it('should revert relevant strings', () => {
const response = caseUtils.convertHTMLEntities(LONG_POST_STRING);
expect(response).toEqual(LONG_PRE_STRING);
});
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

export function convertNonASCIICharacter(character: string): string {
if (character === '£') {
// pound sign will be frequently used and works for btoa despite being non-ASCII
// note: this could be done for other characters provided they work for btoa()
return character;
}
// Note: Will convert to HTML entity
return CaseEditUtils.PREFIX + character.charCodeAt(0) + CaseEditUtils.SUFFIX;
}

export class CaseEditUtils {

public static readonly PREFIX = '&#';
public static readonly SUFFIX = ';';

public convertNonASCIICharacters(rawString: string): string {
return rawString ? rawString.replace(/[^\x20-\x7E]/g, function (c) {
return convertNonASCIICharacter(c);
}) : '';
}

public convertHTMLEntities(editedString: string): string {
const revertedCharacterList = editedString.split(CaseEditUtils.PREFIX);
let rawString = revertedCharacterList[0];
for (let index = 1; index < revertedCharacterList.length; index++) {
const currentSection = revertedCharacterList[index];
if (!currentSection.includes(CaseEditUtils.SUFFIX)) {
return rawString.concat(currentSection);
} else {
const suffixSplitList = currentSection.split(CaseEditUtils.SUFFIX);
const characterCode = Number(suffixSplitList[0]);
rawString = rawString.concat(String.fromCharCode(characterCode), suffixSplitList[1]);
}
}
return rawString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,46 @@ describe('CaseEditComponent', () => {
expect(mockWorkAllocationService.completeTask).not.toHaveBeenCalled();
});

it('should submit the case for a Case Flags submission', () => {
const mockClass = {
submit: () => of({})
};
spyOn(mockClass, 'submit').and.returnValue(of({
id: 'id',
/* tslint:disable:object-literal-key-quotes */
'callback_response_status': 'CALLBACK_HASNOT_COMPLETED',
/* tslint:disable:object-literal-key-quotes */
'after_submit_callback_response': {
/* tslint:disable:object-literal-key-quotes */
'confirmation_header': 'confirmation_header',
/* tslint:disable:object-literal-key-quotes */
'confirmation_body': 'confirmation_body'
}
}));

spyOn(component, 'confirm');

component.isCaseFlagSubmission = true;
component.confirmation = {} as unknown as Confirmation;

formValueService.sanitise.and.returnValue({name: 'sweet'});
component.onEventCanBeCompleted({
eventTrigger: component.eventTrigger,
eventCanBeCompleted: true,
caseDetails: component.caseDetails,
form: component.form,
submit: mockClass.submit,
});

expect(component.confirm).toHaveBeenCalled();
expect(formValueService.populateLinkedCasesDetailsFromCaseFields).toHaveBeenCalled();
expect(formValueService.removeCaseFieldsOfType)
.toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Array), ['FlagLauncher', 'ComponentLauncher']);
expect(formValueService.repopulateFormDataFromCaseFieldValues).toHaveBeenCalled();
expect(validPageListCaseFieldsService.deleteNonValidatedFields).toHaveBeenCalled();
expect(formValueService.removeUnnecessaryFields).not.toHaveBeenCalled();
});

it('should NOT submit the case due to error', () => {
const mockClass = {
submit: () => of({})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,43 +404,51 @@ export class CaseEditComponent implements OnInit, OnDestroy {
if (caseField && caseField.retain_hidden_value &&
(caseField.hidden || (caseField.hidden !== false && parentField && parentField.hidden))) {
if (caseField.field_type.type === 'Complex') {
// Note: Deliberate use of equality (==) and non-equality (!=) operators for null checks throughout, to
// handle both null and undefined values
if (caseField.value != null) {
// Call this function recursively to replace the Complex field's sub-fields as necessary, passing the
// CaseField itself (the sub-fields do not contain any values, so these need to be obtained from the
// parent)
// Update rawFormValueData for this field
// creating form group and adding control into it in case caseField is of complext type and and part of formGroup
const form: FormGroup = new FormGroup({});
if (formGroup.controls[key].value) {
Object.keys(formGroup.controls[key].value).forEach((item) => {
form.addControl(item, new FormControl(formGroup.controls[key].value[item]));
});
}
rawFormValueData[key] = this.replaceHiddenFormValuesWithOriginalCaseData(
form, caseField.field_type.complex_fields, caseField);
}
this.handleComplexField(caseField, formGroup, key, rawFormValueData);
} else {
// Default case also handles collections of *all* types; the entire collection in rawFormValueData will be
// replaced with the original from formatted_value
// Use the CaseField's existing *formatted_value* from the parent, if available. (This is necessary for
// Complex fields, whose sub-fields do not hold any values in the model.) Otherwise, use formatted_value
// from the CaseField itself.
if (parentField && parentField.formatted_value) {
rawFormValueData[key] = parentField.formatted_value[caseField.id];
} else {
if (!(caseField.hidden && caseField.retain_hidden_value)) {
rawFormValueData[key] = caseField.formatted_value;
}
}
this.handleNonComplexField(parentField, rawFormValueData, key, caseField);
}
}
});

return rawFormValueData;
}

private handleNonComplexField(parentField: CaseField, rawFormValueData, key: string, caseField: CaseField) {
// Default case also handles collections of *all* types; the entire collection in rawFormValueData will be
// replaced with the original from formatted_value
// Use the CaseField's existing *formatted_value* from the parent, if available. (This is necessary for
// Complex fields, whose sub-fields do not hold any values in the model.) Otherwise, use formatted_value
// from the CaseField itself.
if (parentField && parentField.formatted_value) {
rawFormValueData[key] = parentField.formatted_value[caseField.id];
} else {
if (!(caseField.hidden && caseField.retain_hidden_value)) {
rawFormValueData[key] = caseField.formatted_value;
}
}
}

private handleComplexField(caseField: CaseField, formGroup: FormGroup<any>, key: string, rawFormValueData) {
// Note: Deliberate use of equality (==) and non-equality (!=) operators for null checks throughout, to
// handle both null and undefined values
if (caseField.value != null) {
// Call this function recursively to replace the Complex field's sub-fields as necessary, passing the
// CaseField itself (the sub-fields do not contain any values, so these need to be obtained from the
// parent)
// Update rawFormValueData for this field
// creating form group and adding control into it in case caseField is of complext type and and part of formGroup
const form: FormGroup = new FormGroup({});
if (formGroup.controls[key].value) {
Object.keys(formGroup.controls[key].value).forEach((item) => {
form.addControl(item, new FormControl(formGroup.controls[key].value[item]));
});
}
rawFormValueData[key] = this.replaceHiddenFormValuesWithOriginalCaseData(
form, caseField.field_type.complex_fields, caseField);
}
}

private caseSubmit({ form, caseEventData, submit }: CaseEditCaseSubmit): void {
const loadingSpinnerToken = this.loadingService.register();
// keep the initial event response to finalise process after task completion
Expand Down
Loading

0 comments on commit 75ced36

Please sign in to comment.