diff --git a/src/model/encoding/__tests__/__snapshots__/convertFromDraftStateToRaw-test.js.snap b/src/model/encoding/__tests__/__snapshots__/convertFromDraftStateToRaw-test.js.snap index c38d006f1e..6dd517e194 100644 --- a/src/model/encoding/__tests__/__snapshots__/convertFromDraftStateToRaw-test.js.snap +++ b/src/model/encoding/__tests__/__snapshots__/convertFromDraftStateToRaw-test.js.snap @@ -149,3 +149,90 @@ Object { "entityMap": Object {}, } `; + +exports[`must be able to convert from draft state with noncontiguous entities to raw 1`] = ` +Object { + "blocks": Array [ + Object { + "data": Object {}, + "depth": 0, + "entityRanges": Array [ + Object { + "key": 0, + "length": 5, + "offset": 0, + }, + Object { + "key": 0, + "length": 5, + "offset": 6, + }, + Object { + "key": 1, + "length": 5, + "offset": 12, + }, + ], + "inlineStyleRanges": Array [], + "key": "a", + "text": "link2 link2 link3", + "type": "unstyled", + }, + Object { + "data": Object {}, + "depth": 0, + "entityRanges": Array [ + Object { + "key": 2, + "length": 5, + "offset": 0, + }, + Object { + "key": 0, + "length": 5, + "offset": 6, + }, + Object { + "key": 3, + "length": 5, + "offset": 12, + }, + ], + "inlineStyleRanges": Array [], + "key": "b", + "text": "link4 link2 link5", + "type": "unstyled", + }, + ], + "entityMap": Object { + "0": Object { + "data": Object { + "url": "www.2.com", + }, + "mutability": "IMMUTABLE", + "type": "LINK", + }, + "1": Object { + "data": Object { + "url": "www.3.com", + }, + "mutability": "IMMUTABLE", + "type": "LINK", + }, + "2": Object { + "data": Object { + "url": "www.4.com", + }, + "mutability": "IMMUTABLE", + "type": "LINK", + }, + "3": Object { + "data": Object { + "url": "www.5.com", + }, + "mutability": "IMMUTABLE", + "type": "LINK", + }, + }, +} +`; diff --git a/src/model/encoding/__tests__/convertFromDraftStateToRaw-test.js b/src/model/encoding/__tests__/convertFromDraftStateToRaw-test.js index 5bf95170bd..ce356e6ef5 100644 --- a/src/model/encoding/__tests__/convertFromDraftStateToRaw-test.js +++ b/src/model/encoding/__tests__/convertFromDraftStateToRaw-test.js @@ -16,7 +16,11 @@ jest.disableAutomock(); const BlockMapBuilder = require('BlockMapBuilder'); +const CharacterMetadata = require('CharacterMetadata'); +const ContentBlock = require('ContentBlock'); const ContentBlockNode = require('ContentBlockNode'); +const ContentState = require('ContentState'); +const DraftEntityInstance = require('DraftEntityInstance'); const Immutable = require('immutable'); const convertFromDraftStateToRaw = require('convertFromDraftStateToRaw'); @@ -59,6 +63,49 @@ const treeContentState = contentState.set( ]), ); +const getMetadata = entityKey => + Immutable.Repeat(CharacterMetadata.create({entity: entityKey}), 5); +const getLink = entityKey => + new DraftEntityInstance({ + type: 'LINK', + mutabiltity: 'MUTABLE', + data: { + url: `www.${entityKey}.com`, + }, + }); +// We start numbering our entities with '2' because getSampleStateForTesting +// already created an entity with key '1'. +const contentStateWithNonContiguousEntities = ContentState.createFromBlockArray( + [ + new ContentBlock({ + key: 'a', + type: 'unstyled', + text: 'link2 link2 link3', + characterList: getMetadata('2') + .toList() + .push(CharacterMetadata.EMPTY) + .concat(getMetadata('2')) + .push(CharacterMetadata.EMPTY) + .concat(getMetadata('3')), + }), + new ContentBlock({ + key: 'b', + type: 'unstyled', + text: 'link4 link2 link5', + characterList: getMetadata('4') + .toList() + .push(CharacterMetadata.EMPTY) + .concat(getMetadata('2')) + .push(CharacterMetadata.EMPTY) + .concat(getMetadata('5')), + }), + ], +) + .addEntity(getLink('2')) + .addEntity(getLink('3')) + .addEntity(getLink('4')) + .addEntity(getLink('5')); + const assertConvertFromDraftStateToRaw = content => { expect(convertFromDraftStateToRaw(content)).toMatchSnapshot(); }; @@ -70,3 +117,7 @@ test('must be able to convert from draft state with ContentBlock to raw', () => test('must be able to convert from draft state with ContentBlockNode to raw', () => { assertConvertFromDraftStateToRaw(treeContentState); }); + +test('must be able to convert from draft state with noncontiguous entities to raw', () => { + assertConvertFromDraftStateToRaw(contentStateWithNonContiguousEntities); +}); diff --git a/src/model/encoding/convertFromDraftStateToRaw.js b/src/model/encoding/convertFromDraftStateToRaw.js index 2ff38089cd..9b3ebfd398 100644 --- a/src/model/encoding/convertFromDraftStateToRaw.js +++ b/src/model/encoding/convertFromDraftStateToRaw.js @@ -65,28 +65,6 @@ const insertRawBlock = ( rawBlocks.push(rawBlock); }; -const insertRawEntity = ( - entityStorageKey: number, - entityKey: mixed, - entityMap: *, - entityCacheRef: *, -) => { - // Stringify to maintain order of otherwise numeric keys. - const stringifiedEntityKey = DraftStringKey.stringify(entityKey); - - if (entityCacheRef[stringifiedEntityKey]) { - return; - } - - entityCacheRef[stringifiedEntityKey] = entityKey; - - // we need the `any` casting here since this is a temporary state - // where we will later on flip the entity map and populate it with - // real entity, at this stage we just need to map back the entity - // key used by the BlockNode - entityMap[stringifiedEntityKey] = (`${entityStorageKey}`: any); -}; - const encodeRawBlocks = ( contentState: ContentState, rawState: RawDraftContentState, @@ -102,13 +80,23 @@ const encodeRawBlocks = ( contentState.getBlockMap().forEach(block => { block.findEntityRanges( character => character.getEntity() !== null, - start => - insertRawEntity( - entityStorageKey++, - block.getEntityAt(start), - entityMap, - entityCacheRef, - ), + start => { + const entityKey = block.getEntityAt(start); + // Stringify to maintain order of otherwise numeric keys. + const stringifiedEntityKey = DraftStringKey.stringify(entityKey); + // This makes this function resilient to two entities + // erroneously having the same key + if (entityCacheRef[stringifiedEntityKey]) { + return; + } + entityCacheRef[stringifiedEntityKey] = entityKey; + // we need the `any` casting here since this is a temporary state + // where we will later on flip the entity map and populate it with + // real entity, at this stage we just need to map back the entity + // key used by the BlockNode + entityMap[stringifiedEntityKey] = (`${entityStorageKey}`: any); + entityStorageKey++; + }, ); insertRawBlock(block, entityMap, rawBlocks, blockCacheRef);