diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts index b66e698bd4..64785f5a30 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts @@ -18,12 +18,14 @@ import { DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { DisplayRichTextWidgetComponent } from './display-rich-text.widget'; +import { DisplayRichTextWidgetComponent, RICH_TEXT_PARSER_TOKEN } from './display-rich-text.widget'; +import { RichTextParserService } from '../../../services/rich-text-parser.service'; describe('DisplayRichTextWidgetComponent', () => { let widget: DisplayRichTextWidgetComponent; let fixture: ComponentFixture; let debugEl: DebugElement; + let mockRichTextParserService: jasmine.SpyObj; const cssSelector = { parsedHTML: '.adf-display-rich-text-widget-parsed-html' @@ -79,8 +81,14 @@ describe('DisplayRichTextWidgetComponent', () => { }; beforeEach(() => { + mockRichTextParserService = jasmine.createSpyObj('RichTextParserService', ['parse']); + mockRichTextParserService.parse.and.returnValue( + '

Editor.js

Display some formatted text

' + ); + TestBed.configureTestingModule({ - imports: [DisplayRichTextWidgetComponent] + imports: [DisplayRichTextWidgetComponent], + providers: [{ provide: RICH_TEXT_PARSER_TOKEN, useValue: mockRichTextParserService }] }); fixture = TestBed.createComponent(DisplayRichTextWidgetComponent); widget = fixture.componentInstance; @@ -88,6 +96,13 @@ describe('DisplayRichTextWidgetComponent', () => { widget.field = fakeFormField; }); + it('should call RichTextParserService.parse() method', () => { + fixture.detectChanges(); + + expect(mockRichTextParserService.parse).toHaveBeenCalledWith(fakeFormField.value); + expect(mockRichTextParserService.parse).toHaveBeenCalledTimes(1); + }); + it('should parse editorjs data to html', async () => { const expectedHtml = '

Editor.js

Display some formatted text

'; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts index 9dab4332ea..d462c01aae 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts @@ -17,10 +17,14 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit, SecurityContext, ViewEncapsulation } from '@angular/core'; +import { Component, inject, InjectionToken, OnInit, SecurityContext, ViewEncapsulation } from '@angular/core'; import { WidgetComponent, FormService } from '@alfresco/adf-core'; -import edjsHTML from 'editorjs-html'; import { DomSanitizer } from '@angular/platform-browser'; +import { RichTextParserService } from '../../../services/rich-text-parser.service'; + +export const RICH_TEXT_PARSER_TOKEN = new InjectionToken('RichTextParserService', { + factory: () => new RichTextParserService() +}); @Component({ selector: 'display-rich-text', @@ -40,39 +44,22 @@ import { DomSanitizer } from '@angular/platform-browser'; encapsulation: ViewEncapsulation.None }) export class DisplayRichTextWidgetComponent extends WidgetComponent implements OnInit { - parsedHTML: any; + parsedHTML: string | Error; - private static readonly CUSTOM_PARSER = { - header: (block: any): string => { - const paragraphAlign = block.data.alignment || block.data.align || block.tunes?.anyTuneName?.alignment; - if (typeof paragraphAlign !== 'undefined' && ['left', 'right', 'center'].includes(paragraphAlign)) { - return `${block.data.text}`; - } else { - return `${block.data.text}`; - } - }, - paragraph: (block: any): string => { - const paragraphAlign = block.data.alignment || block.data.align || block.tunes?.anyTuneName?.alignment; + private readonly richTextParserService = inject(RICH_TEXT_PARSER_TOKEN); + private readonly sanitizer = inject(DomSanitizer); - if (typeof paragraphAlign !== 'undefined' && ['left', 'right', 'center', 'justify'].includes(paragraphAlign)) { - return `

${block.data.text}

`; - } else { - return `

${block.data.text}

`; - } - } - }; - - constructor(public formService: FormService, private readonly sanitizer: DomSanitizer) { + constructor(formService: FormService) { super(formService); } ngOnInit(): void { - this.parsedHTML = edjsHTML(DisplayRichTextWidgetComponent.CUSTOM_PARSER, { strict: true }).parse(this.field.value); + this.parsedHTML = this.richTextParserService.parse(this.field.value); - if (!(this.parsedHTML instanceof Error)) { - this.sanitizeHtmlContent(); - } else { + if (this.parsedHTML instanceof Error) { throw this.parsedHTML; + } else { + this.sanitizeHtmlContent(); } } diff --git a/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.spec.ts b/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.spec.ts new file mode 100644 index 0000000000..5372eea50a --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.spec.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RichTextParserService } from './rich-text-parser.service'; + +describe('RichTextParserService', () => { + it('should test CUSTOM_PARSER.header method works correctly', () => { + const block = { data: { text: 'Test Header', level: 2, alignment: 'center' } }; + const result = (RichTextParserService as any).CUSTOM_PARSER.header(block); + expect(result).toBe('

Test Header

'); + }); + + it('should test CUSTOM_PARSER.paragraph method works correctly', () => { + const block = { data: { text: 'Test paragraph text', alignment: 'justify' } }; + const result = (RichTextParserService as any).CUSTOM_PARSER.paragraph(block); + expect(result).toBe('

Test paragraph text

'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.ts b/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.ts new file mode 100644 index 0000000000..82d87ca916 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/rich-text-parser.service.ts @@ -0,0 +1,56 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import edjsHTML from 'editorjs-html'; + +export interface OutputData { + version?: string; + time?: number; + blocks: any[]; +} + +export class RichTextParserService { + private static readonly CUSTOM_PARSER = { + header: (block: any): string => { + if (!block.data || !block.data.text || !block.data.level) { + return ''; + } + const paragraphAlign = block.data.alignment || block.data.align || block.tunes?.anyTuneName?.alignment; + if (typeof paragraphAlign !== 'undefined' && ['left', 'right', 'center'].includes(paragraphAlign)) { + return `${block.data.text}`; + } else { + return `${block.data.text}`; + } + }, + paragraph: (block: any): string => { + if (!block.data || !block.data.text) { + return ''; + } + const paragraphAlign = block.data.alignment || block.data.align || block.tunes?.anyTuneName?.alignment; + + if (typeof paragraphAlign !== 'undefined' && ['left', 'right', 'center', 'justify'].includes(paragraphAlign)) { + return `

${block.data.text}

`; + } else { + return `

${block.data.text}

`; + } + } + }; + + parse(richText: OutputData): string | Error { + return edjsHTML(RichTextParserService.CUSTOM_PARSER, { strict: true }).parse(richText); + } +}