Skip to content
This repository was archived by the owner on Feb 6, 2023. It is now read-only.

Commit 39be488

Browse files
niveditcfacebook-github-bot
authored andcommitted
Fix DraftTreeAdapter to respect the tree invariants
Summary: `DraftTreeAdapter` is called to convert list-data style Content Blocks into a valid Tree Data structure. * The Tree Data structure has an invariant that only leaf nodes (i.e. nodes with no children) can have text in them. To respect this invariant, we need to create a new parent block at each level of a nested list. By implementing this, we fix a bug with the rendering of nested lists. * Also adds the tree invariant check in DEV mode in `convertFromRawToDraftState` (which calls the tree adapter) to flag if this structure breaks again due to any updates. * Re-record snapshot for `DraftTreeAdapter-test.js` and `convertFromRawToDraftState-test.js` Reviewed By: mitermayer Differential Revision: D9524985 fbshipit-source-id: f5828dfb5f12cb4e34c8511456a79457c1a09808
1 parent c0fb6a8 commit 39be488

File tree

6 files changed

+120
-26
lines changed

6 files changed

+120
-26
lines changed

src/component/utils/exploration/DraftTreeAdapter.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import type {RawDraftContentBlock} from 'RawDraftContentBlock';
1717
import type {RawDraftContentState} from 'RawDraftContentState';
1818

19+
const generateRandomKey = require('generateRandomKey');
20+
const idx = require('idx');
1921
const invariant = require('invariant');
2022

2123
const traverseInDepthOrder = (
@@ -62,7 +64,7 @@ const addDepthToChildren = (block: RawDraftContentBlock) => {
6264
*/
6365
const DraftTreeAdapter = {
6466
/**
65-
* Converts from a tree raw state back to draft raw state
67+
* Converts from a tree raw state back to draft raw state
6668
*/
6769
fromRawTreeStateToRawState(
6870
draftTreeState: RawDraftContentState,
@@ -123,12 +125,30 @@ const DraftTreeAdapter = {
123125
return;
124126
}
125127

126-
// update our depth cache reference path
127-
lastListDepthCacheRef[depth] = treeBlock;
128-
129-
// if we are greater than zero we must have seen a parent already
128+
// nesting
130129
if (depth > 0) {
131-
const parent = lastListDepthCacheRef[depth - 1];
130+
let parent = idx(lastListDepthCacheRef, _ => _[depth - 1]);
131+
if (parent == null) {
132+
parent = {
133+
key: generateRandomKey(),
134+
text: '',
135+
depth: depth - 1,
136+
type: block.type,
137+
children: [],
138+
entityRanges: [],
139+
inlineStyleRanges: [],
140+
};
141+
142+
lastListDepthCacheRef[depth - 1] = parent;
143+
if (depth === 1) {
144+
// add as a root-level block
145+
transformedBlocks.push(parent);
146+
} else {
147+
// depth > 1 => also add as previous parent's child
148+
const grandparent = lastListDepthCacheRef[depth - 2];
149+
grandparent.children.push(parent);
150+
}
151+
}
132152

133153
invariant(parent, 'Invalid depth for RawDraftContentBlock');
134154

src/component/utils/exploration/__tests__/DraftTreeAdapter-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
jest.disableAutomock();
1616

17+
jest.mock('generateRandomKey');
18+
1719
const DraftTreeAdapter = require('DraftTreeAdapter');
1820

1921
const assertFromRawTreeStateToRawState = rawState => {

src/component/utils/exploration/__tests__/__snapshots__/DraftTreeAdapter-test.js.snap

+44-8
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,22 @@ Object {
2626
exports[`must be able to convert from raw state to raw tree state with nested trees based on lists depth 1`] = `
2727
Object {
2828
"blocks": Array [
29+
Object {
30+
"children": Array [],
31+
"depth": 0,
32+
"key": "A",
33+
"text": "Alpha",
34+
"type": "ordered-list-item",
35+
},
2936
Object {
3037
"children": Array [
38+
Object {
39+
"children": Array [],
40+
"depth": 1,
41+
"key": "B",
42+
"text": "Beta",
43+
"type": "ordered-list-item",
44+
},
3145
Object {
3246
"children": Array [
3347
Object {
@@ -39,8 +53,10 @@ Object {
3953
},
4054
],
4155
"depth": 1,
42-
"key": "B",
43-
"text": "Beta",
56+
"entityRanges": Array [],
57+
"inlineStyleRanges": Array [],
58+
"key": "key1",
59+
"text": "",
4460
"type": "ordered-list-item",
4561
},
4662
Object {
@@ -52,8 +68,10 @@ Object {
5268
},
5369
],
5470
"depth": 0,
55-
"key": "A",
56-
"text": "Alpha",
71+
"entityRanges": Array [],
72+
"inlineStyleRanges": Array [],
73+
"key": "key0",
74+
"text": "",
5775
"type": "ordered-list-item",
5876
},
5977
],
@@ -64,6 +82,13 @@ Object {
6482
exports[`must be able to convert from raw state to raw tree state with nested trees based on lists depth and attach nested blocks to closest depth parent 1`] = `
6583
Object {
6684
"blocks": Array [
85+
Object {
86+
"children": Array [],
87+
"depth": 0,
88+
"key": "A",
89+
"text": "Alpha",
90+
"type": "ordered-list-item",
91+
},
6792
Object {
6893
"children": Array [
6994
Object {
@@ -73,6 +98,13 @@ Object {
7398
"text": "Beta",
7499
"type": "ordered-list-item",
75100
},
101+
Object {
102+
"children": Array [],
103+
"depth": 1,
104+
"key": "C",
105+
"text": "Charlie",
106+
"type": "ordered-list-item",
107+
},
76108
Object {
77109
"children": Array [
78110
Object {
@@ -84,14 +116,18 @@ Object {
84116
},
85117
],
86118
"depth": 1,
87-
"key": "C",
88-
"text": "Charlie",
119+
"entityRanges": Array [],
120+
"inlineStyleRanges": Array [],
121+
"key": "key3",
122+
"text": "",
89123
"type": "ordered-list-item",
90124
},
91125
],
92126
"depth": 0,
93-
"key": "A",
94-
"text": "Alpha",
127+
"entityRanges": Array [],
128+
"inlineStyleRanges": Array [],
129+
"key": "key2",
130+
"text": "",
95131
"type": "ordered-list-item",
96132
},
97133
],

src/model/encoding/__tests__/__snapshots__/convertFromRawToDraftState-test.js.snap

+35-10
Original file line numberDiff line numberDiff line change
@@ -282,28 +282,24 @@ exports[`must be able to convert content blocks that have list with depth from r
282282
Object {
283283
"A": Object {
284284
"characterList": Array [],
285-
"children": Array [
286-
"B",
287-
],
285+
"children": Array [],
288286
"data": Object {},
289287
"depth": 0,
290288
"key": "A",
291-
"nextSibling": null,
289+
"nextSibling": "key0",
292290
"parent": null,
293291
"prevSibling": null,
294292
"text": "",
295293
"type": "ordered-list-item",
296294
},
297295
"B": Object {
298296
"characterList": Array [],
299-
"children": Array [
300-
"C",
301-
],
297+
"children": Array [],
302298
"data": Object {},
303299
"depth": 1,
304300
"key": "B",
305-
"nextSibling": null,
306-
"parent": "A",
301+
"nextSibling": "key1",
302+
"parent": "key0",
307303
"prevSibling": null,
308304
"text": "",
309305
"type": "ordered-list-item",
@@ -388,11 +384,40 @@ Object {
388384
"depth": 2,
389385
"key": "C",
390386
"nextSibling": null,
391-
"parent": "B",
387+
"parent": "key1",
392388
"prevSibling": null,
393389
"text": "deeply nested list",
394390
"type": "ordered-list-item",
395391
},
392+
"key0": Object {
393+
"characterList": Array [],
394+
"children": Array [
395+
"B",
396+
"key1",
397+
],
398+
"data": Object {},
399+
"depth": 0,
400+
"key": "key0",
401+
"nextSibling": null,
402+
"parent": null,
403+
"prevSibling": "A",
404+
"text": "",
405+
"type": "ordered-list-item",
406+
},
407+
"key1": Object {
408+
"characterList": Array [],
409+
"children": Array [
410+
"C",
411+
],
412+
"data": Object {},
413+
"depth": 1,
414+
"key": "key1",
415+
"nextSibling": null,
416+
"parent": "key0",
417+
"prevSibling": "B",
418+
"text": "",
419+
"type": "ordered-list-item",
420+
},
396421
}
397422
`;
398423

src/model/encoding/__tests__/convertFromRawToDraftState-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
jest.disableAutomock();
1616

17+
jest.mock('generateRandomKey');
18+
1719
const convertFromRawToDraftState = require('convertFromRawToDraftState');
1820

1921
const toggleExperimentalTreeDataSupport = enabled => {

src/model/encoding/convertFromRawToDraftState.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ const ContentBlockNode = require('ContentBlockNode');
2323
const ContentState = require('ContentState');
2424
const DraftEntity = require('DraftEntity');
2525
const DraftTreeAdapter = require('DraftTreeAdapter');
26-
const Immutable = require('immutable');
26+
const DraftTreeInvariants = require('DraftTreeInvariants');
2727
const SelectionState = require('SelectionState');
2828

2929
const createCharacterList = require('createCharacterList');
3030
const decodeEntityRanges = require('decodeEntityRanges');
3131
const decodeInlineStyleRanges = require('decodeInlineStyleRanges');
3232
const generateRandomKey = require('generateRandomKey');
3333
const gkx = require('gkx');
34+
const Immutable = require('immutable');
3435
const invariant = require('invariant');
3536

3637
const experimentalTreeDataSupport = gkx('draft_tree_data_support');
@@ -228,7 +229,15 @@ const decodeRawBlocks = (
228229
);
229230
}
230231

231-
return decodeContentBlockNodes(rawBlocks, entityMap);
232+
const blockMap = decodeContentBlockNodes(rawBlocks, entityMap);
233+
// in dev mode, check that the tree invariants are met
234+
if (__DEV__) {
235+
invariant(
236+
DraftTreeInvariants.isValidTree(blockMap),
237+
'Should be a valid tree',
238+
);
239+
}
240+
return blockMap;
232241
};
233242

234243
const decodeRawEntityMap = (rawState: RawDraftContentState): * => {

0 commit comments

Comments
 (0)