diff --git a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts index 3a8a2811f3b..1966f591fcf 100644 --- a/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts +++ b/packages/roosterjs-content-model-api/lib/publicApi/utils/formatSegmentWithContentModel.ts @@ -1,5 +1,5 @@ import { adjustWordSelection } from '../../modelApi/selection/adjustWordSelection'; -import { getSelectedSegmentsAndParagraphs } from 'roosterjs-content-model-dom'; +import { getSelectedSegmentsAndParagraphs, mergeTextSegments } from 'roosterjs-content-model-dom'; import type { ContentModelSegmentFormat, IEditor, @@ -72,12 +72,18 @@ export function formatSegmentWithContentModel( ) : false; - formatsAndSegments.forEach(([format, segment, paragraph]) => - toggleStyleCallback(format, !isTurningOff, segment, paragraph) - ); + formatsAndSegments.forEach(([format, segment, paragraph]) => { + toggleStyleCallback(format, !isTurningOff, segment, paragraph); + }); afterFormatCallback?.(model); + formatsAndSegments.forEach(([_, __, paragraph]) => { + if (paragraph) { + mergeTextSegments(paragraph); + } + }); + if (isCollapsedSelection) { context.newPendingFormat = segmentAndParagraphs[0][0].format; editor.focus(); diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts index 673e07f7340..bd958c71d76 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/applySegmentFormatTest.ts @@ -275,23 +275,7 @@ describe('applySegmentFormat', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - backgroundColor: undefined, - fontFamily: undefined, - fontSize: '10px', - fontWeight: undefined, - italic: true, - strikethrough: undefined, - superOrSubScriptSequence: undefined, - textColor: 'red', - underline: false, - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { backgroundColor: undefined, fontFamily: undefined, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts index bfc9e208a91..093943724c5 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setBackgroundColorTest.ts @@ -248,14 +248,7 @@ describe('setBackgroundColor', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - backgroundColor: 'red', - }, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { backgroundColor: 'red', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts index be71d490150..b30609d21a9 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontNameTest.ts @@ -237,15 +237,7 @@ describe('setFontName', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - fontFamily: 'Arial', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { fontFamily: 'Arial', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts index 81725bd874a..d34d2b8f21c 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setFontSizeTest.ts @@ -235,15 +235,7 @@ describe('setFontSize', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - fontSize: '10pt', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { fontSize: '10pt', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts index 0dae57bd630..73d84831021 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/setTextColorTest.ts @@ -236,15 +236,7 @@ describe('setTextColor', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - textColor: 'red', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { textColor: 'red', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts index 7ff3e947bab..09637527422 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleBoldTest.ts @@ -229,15 +229,7 @@ describe('toggleBold', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - fontWeight: 'bold', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { fontWeight: 'bold', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts index bd032376893..f35827e5277 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleCodeTest.ts @@ -245,18 +245,7 @@ describe('toggleCode', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: {}, - isSelected: true, - code: { - format: { - fontFamily: 'monospace', - }, - }, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: {}, isSelected: true, code: { diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts index 402f873de61..cac644f507e 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleItalicTest.ts @@ -229,15 +229,7 @@ describe('toggleItalic', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - italic: true, - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { italic: true, }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts index b758010f93d..2da229db01d 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleStrikethroughTest.ts @@ -229,15 +229,7 @@ describe('toggleStrikethrough', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - strikethrough: true, - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { strikethrough: true, }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts index 04a710b652d..7337932648c 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSubscriptTest.ts @@ -229,15 +229,7 @@ describe('toggleSubscript', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - superOrSubScriptSequence: 'sub', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { superOrSubScriptSequence: 'sub', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts index d554ad27168..9f94609b08b 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleSuperscriptTest.ts @@ -229,15 +229,7 @@ describe('toggleSuperscript', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - superOrSubScriptSequence: 'super', - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { superOrSubScriptSequence: 'super', }, diff --git a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts index 3356aee5a0b..41e740e437c 100644 --- a/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts +++ b/packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts @@ -229,15 +229,7 @@ describe('toggleUnderline', () => { segments: [ { segmentType: 'Text', - text: 'test', - format: { - underline: true, - }, - isSelected: true, - }, - { - segmentType: 'Text', - text: 'test', + text: 'testtest', format: { underline: true, }, diff --git a/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts b/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts index dd96fd509ae..e51c7f888c9 100644 --- a/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts +++ b/packages/roosterjs-content-model-core/lib/corePlugin/cache/domIndexerImpl.ts @@ -109,7 +109,8 @@ function isIndexedSegment(node: Node): node is IndexedSegmentNode { paragraph && paragraph.blockType == 'Paragraph' && Array.isArray(paragraph.segments) && - Array.isArray(segments) + Array.isArray(segments) && + segments.every(segment => paragraph.segments.includes(segment)) ); } diff --git a/packages/roosterjs-content-model-core/test/command/paste/mergePasteContentTest.ts b/packages/roosterjs-content-model-core/test/command/paste/mergePasteContentTest.ts index 2810be3d786..71efb5798c0 100644 --- a/packages/roosterjs-content-model-core/test/command/paste/mergePasteContentTest.ts +++ b/packages/roosterjs-content-model-core/test/command/paste/mergePasteContentTest.ts @@ -971,7 +971,7 @@ describe('mergePasteContent', () => { segments: [ { segmentType: 'Text', - text: 'Unformatted line\n', + text: 'Unformatted line', format: { fontSize: '14px', textColor: 'white', @@ -1149,7 +1149,15 @@ describe('mergePasteContent', () => { segments: [ { segmentType: 'Text', - text: 'Unformatted line\n', + text: 'Unformatted line', + format: { + fontSize: '14px', + textColor: 'white', + }, + }, + { + segmentType: 'Text', + text: '\n', format: { fontSize: '14px', textColor: 'white', @@ -1482,7 +1490,15 @@ describe('mergePasteContent', () => { segments: [ { segmentType: 'Text', - text: 'Inline text\n', + text: 'Inline text', + format: { + fontSize: '14px', + textColor: 'rgb(0,0,0)', + }, + }, + { + segmentType: 'Text', + text: '\n', format: { fontSize: '14px', textColor: 'rgb(0,0,0)', @@ -1537,7 +1553,15 @@ describe('mergePasteContent', () => { }, { segmentType: 'Text', - text: 'Inline text\n', + text: 'Inline text', + format: { + fontSize: '14px', + textColor: 'rgb(0,0,0)', + }, + }, + { + segmentType: 'Text', + text: '\n', format: { fontSize: '14px', textColor: 'rgb(0,0,0)', @@ -1605,7 +1629,16 @@ describe('mergePasteContent', () => { segments: [ { segmentType: 'Text', - text: 'Inline text\n', + text: 'Inline text', + format: { + fontFamily: 'Aptos', + fontSize: '14px', + textColor: 'white', + }, + }, + { + segmentType: 'Text', + text: '\n', format: { fontFamily: 'Aptos', fontSize: '14px', @@ -1653,7 +1686,16 @@ describe('mergePasteContent', () => { { segmentType: 'Text', text: 'Text in source', format: {} }, { segmentType: 'Text', - text: 'Inline text\n', + text: 'Inline text', + format: { + fontFamily: 'Aptos', + fontSize: '14px', + textColor: 'white', + }, + }, + { + segmentType: 'Text', + text: '\n', format: { fontFamily: 'Aptos', fontSize: '14px', diff --git a/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts b/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts index a1a3b565a75..478b8db5fde 100644 --- a/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts +++ b/packages/roosterjs-content-model-core/test/corePlugin/cache/domIndexerImplTest.ts @@ -89,6 +89,7 @@ describe('domIndexerImpl.onParagraph', () => { const segment1 = 'Segment1' as any; const segment2 = 'Segment2' as any; const segment3 = 'Segment3' as any; + paragraph.segments.push(segment1, segment2, segment3); text1.__roosterjsContentModel = { paragraph, @@ -136,6 +137,8 @@ describe('domIndexerImpl.onParagraph', () => { const segment3 = 'Segment3' as any; const segment4 = 'Segment4' as any; + paragraph.segments.push(segment1, segment2, segment3, segment4); + text1.__roosterjsContentModel = { paragraph, segments: [segment1], @@ -295,24 +298,27 @@ describe('domIndexImpl.onMergeText', () => { const text1Model = createText('test1'); const text2Model = createText('test2'); + const paragraph = createParagraph(); + + paragraph.segments.push(text1Model, text2Model); ((text1 as Node) as IndexedSegmentNode).__roosterjsContentModel = { - paragraph: createParagraph(), + paragraph: paragraph, segments: [text1Model], }; ((text2 as Node) as IndexedSegmentNode).__roosterjsContentModel = { - paragraph: createParagraph(), + paragraph: paragraph, segments: [text2Model], }; new DomIndexerImpl().onMergeText(text1, text2); expect(((text1 as Node) as IndexedSegmentNode).__roosterjsContentModel).toEqual({ - paragraph: createParagraph(), + paragraph: paragraph, segments: [text1Model], }); expect(((text2 as Node) as IndexedSegmentNode).__roosterjsContentModel).toEqual({ - paragraph: createParagraph(), + paragraph: paragraph, segments: [text2Model], }); }); @@ -327,20 +333,23 @@ describe('domIndexImpl.onMergeText', () => { const text1Model = createText('test1'); const text2Model = createText('test2'); + const paragraph = createParagraph(); + + paragraph.segments.push(text1Model, text2Model); ((text1 as Node) as IndexedSegmentNode).__roosterjsContentModel = { - paragraph: createParagraph(), + paragraph: paragraph, segments: [text1Model], }; ((text2 as Node) as IndexedSegmentNode).__roosterjsContentModel = { - paragraph: createParagraph(), + paragraph: paragraph, segments: [text2Model], }; new DomIndexerImpl().onMergeText(text1, text2); expect(((text1 as Node) as IndexedSegmentNode).__roosterjsContentModel).toEqual({ - paragraph: createParagraph(), + paragraph: paragraph, segments: [text1Model, text2Model], }); expect(((text2 as Node) as IndexedSegmentNode).__roosterjsContentModel).toBeUndefined(); @@ -1089,6 +1098,31 @@ describe('domIndexerImpl.reconcileSelection', () => { expect(setSelectionSpy).not.toHaveBeenCalled(); expect(model.hasRevertedRangeSelection).toBeFalsy(); }); + + it('Index segment is not in paragraph, bad index', () => { + const node = document.createTextNode('test'); + const paragraph = createParagraph(); + const segment = createText('test'); + + domIndexerImpl.onSegment(node, paragraph, [segment]); + + const newRangeEx: DOMSelection = { + type: 'range', + range: createRange(node, 2), + isReverted: false, + }; + + expect(((node as Node) as IndexedSegmentNode).__roosterjsContentModel).toEqual({ + paragraph, + segments: [segment], + }); + + const result = domIndexerImpl.reconcileSelection(model, newRangeEx); + + expect(result).toBeFalse(); + expect(setSelectionSpy).not.toHaveBeenCalled(); + expect(model.hasRevertedRangeSelection).toBeFalsy(); + }); }); describe('domIndexerImpl.reconcileChildList', () => { diff --git a/packages/roosterjs-content-model-dom/lib/index.ts b/packages/roosterjs-content-model-dom/lib/index.ts index 12077de7df0..2723beb3035 100644 --- a/packages/roosterjs-content-model-dom/lib/index.ts +++ b/packages/roosterjs-content-model-dom/lib/index.ts @@ -71,6 +71,7 @@ export { unwrapBlock } from './modelApi/common/unwrapBlock'; export { addSegment } from './modelApi/common/addSegment'; export { isEmpty } from './modelApi/common/isEmpty'; export { normalizeSingleSegment } from './modelApi/common/normalizeSegment'; +export { mergeTextSegments } from './modelApi/common/mergeTextSegments'; export { setParagraphNotImplicit } from './modelApi/block/setParagraphNotImplicit'; export { getOrderedListNumberStr } from './modelApi/list/getOrderedListNumberStr'; diff --git a/packages/roosterjs-content-model-dom/lib/modelApi/common/mergeTextSegments.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/mergeTextSegments.ts new file mode 100644 index 00000000000..211928f62af --- /dev/null +++ b/packages/roosterjs-content-model-dom/lib/modelApi/common/mergeTextSegments.ts @@ -0,0 +1,62 @@ +import { areSameFormats } from '../../domToModel/utils/areSameFormats'; +import type { + ContentModelText, + ReadonlyContentModelCode, + ReadonlyContentModelLink, + ReadonlyContentModelSegment, + ShallowMutableContentModelParagraph, +} from 'roosterjs-content-model-types'; + +/** + * Find continuous text segments that have the same format and decorators, merge them, So we can reduce total count of segments + * @param block The parent paragraph to check. + */ +export function mergeTextSegments(block: ShallowMutableContentModelParagraph) { + let lastText: ContentModelText | null = null; + + for (let i = 0; i < block.segments.length; i++) { + const segment = block.segments[i]; + + if (segment.segmentType != 'Text') { + lastText = null; + } else if (!lastText || !segmentsWithSameFormat(lastText, segment)) { + lastText = segment; + } else { + lastText.text += segment.text; + block.segments.splice(i, 1); + i--; + } + } +} + +function segmentsWithSameFormat( + seg1: ReadonlyContentModelSegment, + seg2: ReadonlyContentModelSegment +) { + return ( + !!seg1.isSelected == !!seg2.isSelected && + areSameFormats(seg1.format, seg2.format) && + areSameLinks(seg1.link, seg2.link) && + areSameCodes(seg1.code, seg2.code) + ); +} + +function areSameLinks( + link1: ReadonlyContentModelLink | undefined, + link2: ReadonlyContentModelLink | undefined +) { + return ( + (!link1 && !link2) || + (link1 && + link2 && + areSameFormats(link1.format, link2.format) && + areSameFormats(link1.dataset, link2.dataset)) + ); +} + +function areSameCodes( + code1: ReadonlyContentModelCode | undefined, + code2: ReadonlyContentModelCode | undefined +) { + return (!code1 && !code2) || (code1 && code2 && areSameFormats(code1.format, code2.format)); +} diff --git a/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts index 6a760889ac2..aaee4b92d99 100644 --- a/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts +++ b/packages/roosterjs-content-model-dom/lib/modelApi/common/normalizeParagraph.ts @@ -2,16 +2,12 @@ import { areSameFormats } from '../../domToModel/utils/areSameFormats'; import { createBr } from '../creators/createBr'; import { isSegmentEmpty } from './isEmpty'; import { isWhiteSpacePreserved } from '../../domUtils/isWhiteSpacePreserved'; -import { mutateBlock, mutateSegment, mutateSegments } from './mutate'; +import { mutateBlock, mutateSegment } from './mutate'; import { normalizeAllSegments } from './normalizeSegment'; import type { ContentModelSegmentFormat, - ContentModelText, - ReadonlyContentModelCode, - ReadonlyContentModelLink, ReadonlyContentModelParagraph, ReadonlyContentModelSegment, - ReadonlyContentModelText, } from 'roosterjs-content-model-types'; /** @@ -52,7 +48,6 @@ export function normalizeParagraph(paragraph: ReadonlyContentModelParagraph) { removeEmptyLinks(paragraph); removeEmptySegments(paragraph); - mergeTextSegments(paragraph); moveUpSegmentFormat(paragraph); } @@ -76,58 +71,6 @@ function removeEmptySegments(block: ReadonlyContentModelParagraph) { } } -function mergeTextSegments(block: ReadonlyContentModelParagraph) { - let lastText: ReadonlyContentModelText | null = null; - - for (let i = 0; i < block.segments.length; i++) { - const segment = block.segments[i]; - - if (segment.segmentType != 'Text') { - lastText = null; - } else if (!lastText || !segmentsWithSameFormat(lastText, segment)) { - lastText = segment; - } else { - const [mutableBlock, [mutableLastText]] = mutateSegments(block, [lastText, segment]); - - (mutableLastText as ContentModelText).text += segment.text; - mutableBlock.segments.splice(i, 1); - i--; - } - } -} - -function segmentsWithSameFormat( - seg1: ReadonlyContentModelSegment, - seg2: ReadonlyContentModelSegment -) { - return ( - !!seg1.isSelected == !!seg2.isSelected && - areSameFormats(seg1.format, seg2.format) && - areSameLinks(seg1.link, seg2.link) && - areSameCodes(seg1.code, seg2.code) - ); -} - -function areSameLinks( - link1: ReadonlyContentModelLink | undefined, - link2: ReadonlyContentModelLink | undefined -) { - return ( - (!link1 && !link2) || - (link1 && - link2 && - areSameFormats(link1.format, link2.format) && - areSameFormats(link1.dataset, link2.dataset)) - ); -} - -function areSameCodes( - code1: ReadonlyContentModelCode | undefined, - code2: ReadonlyContentModelCode | undefined -) { - return (!code1 && !code2) || (code1 && code2 && areSameFormats(code1.format, code2.format)); -} - function removeEmptyLinks(paragraph: ReadonlyContentModelParagraph) { const marker = paragraph.segments.find(x => x.segmentType == 'SelectionMarker'); if (marker) { diff --git a/packages/roosterjs-content-model-dom/test/modelApi/common/mergeTextSegmentsTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/mergeTextSegmentsTest.ts new file mode 100644 index 00000000000..952222cc398 --- /dev/null +++ b/packages/roosterjs-content-model-dom/test/modelApi/common/mergeTextSegmentsTest.ts @@ -0,0 +1,607 @@ +import { createParagraph } from '../../../lib/modelApi/creators/createParagraph'; +import { mergeTextSegments } from '../../../lib/modelApi/common/mergeTextSegments'; +import type { ContentModelSegment, ContentModelParagraph } from 'roosterjs-content-model-types'; + +describe('mergeTextSegments', () => { + function runTest(input: ContentModelSegment[], expectedResult: ContentModelSegment[]) { + const paragraph = createParagraph(); + + paragraph.segments = input; + + mergeTextSegments(paragraph); + + const expectedParagraph: ContentModelParagraph = { + blockType: 'Paragraph', + format: {}, + segments: expectedResult, + }; + + expect(paragraph).toEqual(expectedParagraph); + } + + it('Empty paragraph', () => { + runTest([], []); + }); + + it('Single text segment', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: {}, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: {}, + }, + ] + ); + }); + + it('Two text segments, same format', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments, same format, with space - 1', () => { + runTest( + [ + { + segmentType: 'Text', + text: ' abc ', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: ' def ', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: ' abc def ', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments, same format, with space - 2', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: ' def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments, different format - 1', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt', italic: true }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt', italic: true }, + }, + ] + ); + }); + + it('Two text segments, different format - 2', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos' }, + }, + ] + ); + }); + + it('Two text segments, different format - 3', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '14pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '14pt' }, + }, + ] + ); + }); + + it('Two text segments, one has link', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments, both have same link', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: {}, + }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: {}, + }, + }, + ] + ); + }); + + it('Two text segments, both have different link', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: { href: 'url1' }, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: { href: 'url2' }, + }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: { href: 'url1' }, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + link: { + dataset: {}, + format: { href: 'url2' }, + }, + }, + ] + ); + }); + + it('Two text segments, one has code', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + code: { + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + code: { + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments, both have same code', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + code: { + format: {}, + }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + code: { + format: {}, + }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + code: { + format: {}, + }, + }, + ] + ); + }); + + it('Two text segments around selection marker', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments after selection marker', () => { + runTest( + [ + { + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'SelectionMarker', + format: {}, + }, + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two text segments before selection marker', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'SelectionMarker', + format: {}, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'SelectionMarker', + format: {}, + }, + ] + ); + }); + + it('Three text segments with same format', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'ghi', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdefghi', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + ] + ); + }); + + it('Two pairs - 1', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'ghi', + format: { fontFamily: 'Aptos', fontSize: '14pt' }, + }, + { + segmentType: 'Text', + text: 'jkl', + format: { fontFamily: 'Aptos', fontSize: '14pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abcdef', + format: { fontFamily: 'Aptos', fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'ghijkl', + format: { fontFamily: 'Aptos', fontSize: '14pt' }, + }, + ] + ); + }); + + it('Two pairs - 2', () => { + runTest( + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontSize: '14pt' }, + }, + { + segmentType: 'Text', + text: 'ghi', + format: { fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'jkl', + format: { fontSize: '14pt' }, + }, + ], + [ + { + segmentType: 'Text', + text: 'abc', + format: { fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'def', + format: { fontSize: '14pt' }, + }, + { + segmentType: 'Text', + text: 'ghi', + format: { fontSize: '12pt' }, + }, + { + segmentType: 'Text', + text: 'jkl', + format: { fontSize: '14pt' }, + }, + ] + ); + }); +}); diff --git a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts index 3466a7656b3..bc3aa260c52 100644 --- a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeContentModelTest.ts @@ -59,7 +59,12 @@ describe('normalizeContentModel', () => { { segmentType: 'Text', format: {}, - text: 'test1test2', + text: 'test1', + }, + { + segmentType: 'Text', + format: {}, + text: 'test2', }, ], }, diff --git a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts index de798f6a570..1de5d7c3e3b 100644 --- a/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelApi/common/normalizeParagraphTest.ts @@ -6,12 +6,7 @@ import { createSelectionMarker } from '../../../lib/modelApi/creators/createSele import { createText } from '../../../lib/modelApi/creators/createText'; import { normalizeContentModel } from '../../../lib/modelApi/common/normalizeContentModel'; import { normalizeParagraph } from '../../../lib/modelApi/common/normalizeParagraph'; -import { - ContentModelParagraph, - ContentModelSegment, - ContentModelSegmentFormat, - ReadonlyContentModelParagraph, -} from 'roosterjs-content-model-types'; +import { ReadonlyContentModelParagraph } from 'roosterjs-content-model-types'; describe('Normalize text that contains space', () => { function runTest(texts: string[], expected: string[], whiteSpace?: string) { @@ -74,9 +69,9 @@ describe('Normalize text that contains space', () => { }); it('Text ends with  ', () => { - runTest(['a\u00A0', 'b'], ['a b']); - runTest(['a\u00A0\u00A0', 'b'], ['a\u00A0 b']); - runTest(['a \u00A0', 'b'], ['a \u00A0b']); + runTest(['a\u00A0', 'b'], ['a ', 'b']); + runTest(['a\u00A0\u00A0', 'b'], ['a\u00A0 ', 'b']); + runTest(['a \u00A0', 'b'], ['a \u00A0', 'b']); }); it('with other type of segment', () => { @@ -171,7 +166,12 @@ describe('Normalize text that contains space', () => { segments: [ { segmentType: 'Text', - text: 'a \u00A0b', + text: 'a ', + format: {}, + }, + { + segmentType: 'Text', + text: '\u00A0b', format: {}, }, ], @@ -528,11 +528,17 @@ describe('Move up format', () => { segments: [ { segmentType: 'Text', - text: 'test1test2', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'test2', format: {}, }, ], format: {}, + cachedElement: mockedCache, }); }); @@ -834,657 +840,3 @@ describe('Move up format', () => { }); }); }); - -describe('Merge text segments', () => { - function runTest( - input: ContentModelSegment[], - expectedResult: ContentModelSegment[], - stillHasCache: boolean, - expectedParagraphFormat?: ContentModelSegmentFormat - ) { - const paragraph = createParagraph(); - const cache = 'CACHE' as any; - - paragraph.cachedElement = cache; - - paragraph.segments = input; - - normalizeParagraph(paragraph); - - const expectedParagraph: ContentModelParagraph = { - blockType: 'Paragraph', - format: {}, - segments: expectedResult, - }; - - if (expectedParagraphFormat) { - expectedParagraph.segmentFormat = expectedParagraphFormat; - } - - if (stillHasCache) { - expectedParagraph.cachedElement = cache; - } - - expect(paragraph).toEqual(expectedParagraph); - } - - it('Empty paragraph', () => { - runTest([], [], true); - }); - - it('Single text segment', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: {}, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: {}, - }, - ], - true - ); - }); - - it('Two text segments, same format', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, same format, with space - 1', () => { - runTest( - [ - { - segmentType: 'Text', - text: ' abc ', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: ' def ', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, same format, with space - 2', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: ' def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc\u00A0def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, different format - 1', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt', italic: true }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt', italic: true }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, different format - 2', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos' }, - }, - ], - false, - { fontFamily: 'Aptos' } - ); - }); - - it('Two text segments, different format - 3', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '14pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '14pt' }, - }, - ], - false, - { fontFamily: 'Aptos' } - ); - }); - - it('Two text segments, one has link', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, both have same link', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: {}, - }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: {}, - }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, both have different link', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: { href: 'url1' }, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: { href: 'url2' }, - }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: { href: 'url1' }, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - link: { - dataset: {}, - format: { href: 'url2' }, - }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, one has code', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - code: { - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - code: { - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments, both have same code', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - code: { - format: {}, - }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - code: { - format: {}, - }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - code: { - format: {}, - }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments around selection marker', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'SelectionMarker', - format: {}, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'SelectionMarker', - format: {}, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments after selection marker', () => { - runTest( - [ - { - segmentType: 'SelectionMarker', - format: {}, - }, - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'SelectionMarker', - format: {}, - }, - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two text segments before selection marker', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'SelectionMarker', - format: {}, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'SelectionMarker', - format: {}, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Three text segments with same format', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'ghi', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdefghi', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - ], - false, - { fontFamily: 'Aptos', fontSize: '12pt' } - ); - }); - - it('Two pairs - 1', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'ghi', - format: { fontFamily: 'Aptos', fontSize: '14pt' }, - }, - { - segmentType: 'Text', - text: 'jkl', - format: { fontFamily: 'Aptos', fontSize: '14pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abcdef', - format: { fontFamily: 'Aptos', fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'ghijkl', - format: { fontFamily: 'Aptos', fontSize: '14pt' }, - }, - ], - false, - { fontFamily: 'Aptos' } - ); - }); - - it('Two pairs - 2', () => { - runTest( - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontSize: '14pt' }, - }, - { - segmentType: 'Text', - text: 'ghi', - format: { fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'jkl', - format: { fontSize: '14pt' }, - }, - ], - [ - { - segmentType: 'Text', - text: 'abc', - format: { fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'def', - format: { fontSize: '14pt' }, - }, - { - segmentType: 'Text', - text: 'ghi', - format: { fontSize: '12pt' }, - }, - { - segmentType: 'Text', - text: 'jkl', - format: { fontSize: '14pt' }, - }, - ], - true - ); - }); -}); diff --git a/packages/roosterjs-content-model-dom/test/modelApi/editing/mergeModelTest.ts b/packages/roosterjs-content-model-dom/test/modelApi/editing/mergeModelTest.ts index 78ec1d24aa9..f806f20e84a 100644 --- a/packages/roosterjs-content-model-dom/test/modelApi/editing/mergeModelTest.ts +++ b/packages/roosterjs-content-model-dom/test/modelApi/editing/mergeModelTest.ts @@ -96,7 +96,12 @@ describe('mergeModel', () => { segments: [ { segmentType: 'Text', - text: 'test1test2', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'test2', format: {}, }, { @@ -395,7 +400,12 @@ describe('mergeModel', () => { segments: [ { segmentType: 'Text', - text: 'test11newText1', + text: 'test11', + format: {}, + }, + { + segmentType: 'Text', + text: 'newText1', format: {}, }, ], @@ -1690,7 +1700,12 @@ describe('mergeModel', () => { }, { segmentType: 'Text', - text: 'test1new text', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'new text', format: {}, }, marker2, @@ -2937,7 +2952,9 @@ describe('mergeModel', () => { const paragraph: ContentModelParagraph = { blockType: 'Paragraph', segments: [ - { segmentType: 'Text', text: 'test1sourceTest1sourceTest2', format: {} }, + { segmentType: 'Text', text: 'test1', format: {} }, + { segmentType: 'Text', text: 'sourceTest1', format: {} }, + { segmentType: 'Text', text: 'sourceTest2', format: {} }, { segmentType: 'SelectionMarker', isSelected: true, @@ -4080,7 +4097,12 @@ describe('mergeModel', () => { }, { segmentType: 'Text', - text: 'test1new text', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'new text', format: {}, }, marker2, @@ -4952,7 +4974,12 @@ describe('mergeModel', () => { }, { segmentType: 'Text', - text: 'test1new text', + text: 'test1', + format: {}, + }, + { + segmentType: 'Text', + text: 'new text', format: {}, }, marker2, diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts index fab4753280f..50ff7a50e80 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWacTest.ts @@ -3103,7 +3103,10 @@ describe('wordOnlineHandler', () => { blocks: [ { blockType: 'Paragraph', - segments: [{ segmentType: 'Text', text: 'it went:  ', format: {} }], + segments: [ + { segmentType: 'Text', text: 'it went: ', format: {} }, + { segmentType: 'Text', text: ' ', format: {} }, + ], format: { marginTop: '1em', marginBottom: '1em' }, decorator: { tagName: 'p', format: {} }, }, @@ -3132,7 +3135,10 @@ describe('wordOnlineHandler', () => { blocks: [ { blockType: 'Paragraph', - segments: [{ segmentType: 'Text', text: 'Test. ', format: {} }], + segments: [ + { segmentType: 'Text', text: 'Test.', format: {} }, + { segmentType: 'Text', text: ' ', format: {} }, + ], format: { marginTop: '1em', marginBottom: '1em' }, decorator: { tagName: 'p', format: {} }, }, @@ -4411,7 +4417,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4465,7 +4483,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4519,7 +4549,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4583,7 +4625,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4647,7 +4701,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4721,7 +4787,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4795,7 +4873,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4859,7 +4949,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4923,7 +5025,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -4977,7 +5091,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5132,7 +5258,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + textColor: 'rgb(0, 0, 0)', + fontWeight: 'normal', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5231,7 +5369,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5291,7 +5441,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + textColor: 'rgb(0, 0, 0)', + fontWeight: 'normal', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5340,7 +5502,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5445,7 +5619,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + textColor: 'rgb(0, 0, 0)', + fontWeight: 'normal', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5544,7 +5730,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5603,7 +5801,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5708,7 +5918,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + textColor: 'rgb(0, 0, 0)', + fontWeight: 'normal', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5757,7 +5979,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', @@ -5816,7 +6050,19 @@ describe('wordOnlineHandler', () => { segments: [ { segmentType: 'Text', - text: '_ ', + text: '_', + format: { + fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', + fontSize: '12pt', + italic: false, + fontWeight: 'normal', + textColor: 'rgb(0, 0, 0)', + lineHeight: '22.0875px', + }, + }, + { + segmentType: 'Text', + text: ' ', format: { fontFamily: 'Aptos, Aptos_MSFontService, sans-serif', fontSize: '12pt', diff --git a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts index bfa5272bc6e..07f17b4039d 100644 --- a/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts +++ b/packages/roosterjs-content-model-plugins/test/paste/processPastedContentFromWordDesktopTest.ts @@ -80,7 +80,12 @@ describe('processPastedContentFromWordDesktopTest', () => { segments: [ { segmentType: 'Text', - text: 'TestTest', + text: 'Test', + format: {}, + }, + { + segmentType: 'Text', + text: 'Test', format: {}, }, ], @@ -101,7 +106,12 @@ describe('processPastedContentFromWordDesktopTest', () => { segments: [ { segmentType: 'Text', - text: 'TestTest', + text: 'Test', + format: {}, + }, + { + segmentType: 'Text', + text: 'Test', format: {}, }, ], @@ -4254,7 +4264,12 @@ describe('processPastedContentFromWordDesktopTest', () => { isImplicit: true, segments: [ { - text: 'text.', + text: 'text', + segmentType: 'Text', + format: {}, + }, + { + text: '.', segmentType: 'Text', format: {}, }, @@ -4833,7 +4848,12 @@ describe('processPastedContentFromWordDesktopTest', () => { isImplicit: true, segments: [ { - text: 'text ', + text: 'text', + segmentType: 'Text', + format: {}, + }, + { + text: ' ', segmentType: 'Text', format: {}, },