From a0f32c69756e2e91ed40d1671fb61b3616691500 Mon Sep 17 00:00:00 2001 From: Jiuqing Song Date: Thu, 6 Jun 2024 23:39:39 -0700 Subject: [PATCH] Skip same format from LI (#2683) * Skip same format from LI * Add test --- .../lib/modelToDom/handlers/handleListItem.ts | 5 +- .../test/endToEndTest.ts | 53 +++++++- .../modelToDom/handlers/handleListItemTest.ts | 122 ++++++++++++++---- 3 files changed, 155 insertions(+), 25 deletions(-) diff --git a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts index b672d1b7b9f..942ffd44486 100644 --- a/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts +++ b/packages/roosterjs-content-model-dom/lib/modelToDom/handlers/handleListItem.ts @@ -1,6 +1,7 @@ import { applyFormat } from '../utils/applyFormat'; import { applyMetadata } from '../utils/applyMetadata'; import { setParagraphNotImplicit } from '../../modelApi/block/setParagraphNotImplicit'; +import { stackFormat } from '../utils/stackFormat'; import { unwrap } from '../../domUtils/unwrap'; import type { ContentModelBlockHandler, @@ -40,7 +41,9 @@ export const handleListItem: ContentModelBlockHandler = ( // Need to apply listItemElement formats after applying metadata since the list numbers value relies on the result of metadata handling applyFormat(li, context.formatAppliers.listItemElement, listItem.format, context); - context.modelHandlers.blockGroupChildren(doc, li, listItem, context); + stackFormat(context, listItem.formatHolder.format, () => { + context.modelHandlers.blockGroupChildren(doc, li, listItem, context); + }); } else { // There is no level for this list item, that means it should be moved out of the list // For each paragraph, make it not implicit so it will have a DIV around it, to avoid more paragraphs connected together diff --git a/packages/roosterjs-content-model-dom/test/endToEndTest.ts b/packages/roosterjs-content-model-dom/test/endToEndTest.ts index 0e48b670b52..be9b8fc582d 100644 --- a/packages/roosterjs-content-model-dom/test/endToEndTest.ts +++ b/packages/roosterjs-content-model-dom/test/endToEndTest.ts @@ -135,7 +135,58 @@ describe('End to end test for DOM => Model => DOM/TEXT', () => { ], }, '1\r\n2', - '' + '' + ); + }); + + it('list with partially same format in list item and segment', () => { + runTest( + '
  • test
', + { + blockGroupType: 'Document', + blocks: [ + { + blockType: 'BlockGroup', + blockGroupType: 'ListItem', + blocks: [ + { + blockType: 'Paragraph', + segments: [ + { + segmentType: 'Text', + text: 'test', + format: { + fontFamily: 'Arial', + fontSize: '12pt', + textColor: 'red', + fontWeight: 'bold', + }, + }, + ], + format: {}, + segmentFormat: { + fontFamily: 'Arial', + fontSize: '12pt', + textColor: 'red', + }, + }, + ], + levels: [{ listType: 'UL', format: {}, dataset: {} }], + formatHolder: { + segmentType: 'SelectionMarker', + isSelected: false, + format: { + fontFamily: 'Arial', + fontSize: '10pt', + textColor: 'red', + }, + }, + format: {}, + }, + ], + }, + 'test', + '' ); }); diff --git a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts index 09ea4c633d7..0c2b0ac6e3c 100644 --- a/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelToDom/handlers/handleListItemTest.ts @@ -1,9 +1,11 @@ import * as applyFormat from '../../../lib/modelToDom/utils/applyFormat'; +import * as handleBlockGroupChildren from '../../../lib/modelToDom/handlers/handleBlockGroupChildren'; +import * as handleList from '../../../lib/modelToDom/handlers/handleList'; import { createListItem } from '../../../lib/modelApi/creators/createListItem'; import { createListLevel } from '../../../lib/modelApi/creators/createListLevel'; import { createModelToDomContext } from '../../../lib/modelToDom/context/createModelToDomContext'; import { createParagraph } from '../../../lib/modelApi/creators/createParagraph'; -import { handleList as originalHandleList } from '../../../lib/modelToDom/handlers/handleList'; +import { createText } from '../../../lib'; import { handleListItem } from '../../../lib/modelToDom/handlers/handleListItem'; import { ContentModelBlockGroup, @@ -18,22 +20,22 @@ import { describe('handleListItem without format handler', () => { let context: ModelToDomContext; - let handleBlockGroupChildren: jasmine.Spy>; - let handleList: jasmine.Spy>; + let handleBlockGroupChildrenSpy: jasmine.Spy>; + let handleListSpy: jasmine.Spy>; let listItemMetadataApplier: jasmine.Spy>; beforeEach(() => { - handleBlockGroupChildren = jasmine.createSpy('handleBlockGroupChildren'); - handleList = jasmine.createSpy('handleList').and.callFake(originalHandleList); + handleBlockGroupChildrenSpy = spyOn(handleBlockGroupChildren, 'handleBlockGroupChildren'); + handleListSpy = spyOn(handleList, 'handleList').and.callThrough(); listItemMetadataApplier = jasmine.createSpy('listItemMetadataApplier'); context = createModelToDomContext(undefined, { modelHandlerOverride: { - blockGroupChildren: handleBlockGroupChildren, - list: handleList, + blockGroupChildren: handleBlockGroupChildrenSpy, + list: handleListSpy, }, formatApplierOverride: { listItemThread: null, @@ -64,11 +66,11 @@ describe('handleListItem without format handler', () => { }, ], }); - expect(handleList).toHaveBeenCalledTimes(1); - expect(handleList).toHaveBeenCalledWith(document, parent, listItem, context, null); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, null); expect(applyFormat.applyFormat).not.toHaveBeenCalledTimes(1); - expect(handleBlockGroupChildren).toHaveBeenCalledTimes(1); - expect(handleBlockGroupChildren).toHaveBeenCalledWith( + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( document, document.createElement('li'), listItem, @@ -109,8 +111,8 @@ describe('handleListItem without format handler', () => { }, ], }); - expect(handleList).toHaveBeenCalledTimes(1); - expect(handleList).toHaveBeenCalledWith(document, parent, listItem, context, null); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, null); expect(applyFormat.applyFormat).toHaveBeenCalledTimes(3); expect(applyFormat.applyFormat).toHaveBeenCalledWith( parent.firstChild as HTMLElement, @@ -131,8 +133,8 @@ describe('handleListItem without format handler', () => { context ); expect(listItemMetadataApplier).toHaveBeenCalledWith(null, listItem.format, context); - expect(handleBlockGroupChildren).toHaveBeenCalledTimes(1); - expect(handleBlockGroupChildren).toHaveBeenCalledWith( + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( document, parent.firstChild as HTMLElement, listItem, @@ -172,8 +174,8 @@ describe('handleListItem without format handler', () => { }, ], }); - expect(handleList).toHaveBeenCalledTimes(1); - expect(handleList).toHaveBeenCalledWith(document, parent, listItem, context, null); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, null); expect(applyFormat.applyFormat).toHaveBeenCalledTimes(3); expect(applyFormat.applyFormat).toHaveBeenCalledWith( parent.firstChild as HTMLElement, @@ -194,8 +196,8 @@ describe('handleListItem without format handler', () => { context ); expect(listItemMetadataApplier).toHaveBeenCalledWith(null, listItem.format, context); - expect(handleBlockGroupChildren).toHaveBeenCalledTimes(1); - expect(handleBlockGroupChildren).toHaveBeenCalledWith( + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( document, parent.firstChild as HTMLElement, listItem, @@ -213,11 +215,11 @@ describe('handleListItem without format handler', () => { const result = handleListItem(document, parent, listItem, context, br); expect(parent.outerHTML).toBe('

'); - expect(handleList).toHaveBeenCalledTimes(1); - expect(handleList).toHaveBeenCalledWith(document, parent, listItem, context, br); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, br); expect(applyFormat.applyFormat).toHaveBeenCalled(); - expect(handleBlockGroupChildren).toHaveBeenCalledTimes(1); - expect(handleBlockGroupChildren).toHaveBeenCalledWith( + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( document, parent.firstChild!.firstChild as HTMLElement, listItem, @@ -226,6 +228,80 @@ describe('handleListItem without format handler', () => { expect(result).toBe(br); }); + it('UL with same format on list and segment', () => { + const listItem = createListItem([createListLevel('UL')], { + fontFamily: 'Arial', + fontSize: '10pt', + textColor: 'red', + }); + const para = createParagraph(); + const text = createText('test', { + fontFamily: 'Arial', + fontSize: '10pt', + textColor: 'red', + }); + + para.segments.push(text); + listItem.blocks.push(para); + + handleBlockGroupChildrenSpy.and.callThrough(); + + const parent = document.createElement('div'); + const result = handleListItem(document, parent, listItem, context, null); + + expect(parent.outerHTML).toBe( + '
  • test
' + ); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, null); + expect(applyFormat.applyFormat).toHaveBeenCalled(); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( + document, + parent.firstChild!.firstChild as HTMLElement, + listItem, + context + ); + expect(result).toBe(null); + }); + + it('UL with different format on list and segment', () => { + const listItem = createListItem([createListLevel('UL')], { + fontFamily: 'Arial', + fontSize: '10pt', + textColor: 'red', + }); + const para = createParagraph(); + const text = createText('test', { + fontFamily: 'Arial', + fontSize: '12pt', + textColor: 'green', + }); + + para.segments.push(text); + listItem.blocks.push(para); + + handleBlockGroupChildrenSpy.and.callThrough(); + + const parent = document.createElement('div'); + const result = handleListItem(document, parent, listItem, context, null); + + expect(parent.outerHTML).toBe( + '
  • test
' + ); + expect(handleListSpy).toHaveBeenCalledTimes(1); + expect(handleListSpy).toHaveBeenCalledWith(document, parent, listItem, context, null); + expect(applyFormat.applyFormat).toHaveBeenCalled(); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledTimes(1); + expect(handleBlockGroupChildrenSpy).toHaveBeenCalledWith( + document, + parent.firstChild!.firstChild as HTMLElement, + listItem, + context + ); + expect(result).toBe(null); + }); + it('With onNodeCreated', () => { const listLevel0 = createListLevel('OL'); const listItem: ContentModelListItem = {