Skip to content

Commit d236f2b

Browse files
authored
add unknown embed op (#416)
- fix corruption bug when editing note if an unknown node is before the note
1 parent 8648ec5 commit d236f2b

File tree

9 files changed

+659
-152
lines changed

9 files changed

+659
-152
lines changed

libs/shared-react/src/plugins/usj/collab/delta-apply-update.utils.test.tsx

Lines changed: 232 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
$isNoteNode,
5353
$isParaNode,
5454
$isSomeChapterNode,
55+
$isUnknownNode,
5556
charIdState,
5657
EMPTY_CHAR_PLACEHOLDER_TEXT,
5758
GENERATOR_NOTE_CALLER,
@@ -2739,6 +2740,84 @@ describe("Delta Utils $applyUpdate", () => {
27392740
});
27402741
});
27412742

2743+
it("(dc) should insert adjacent chars with different markers", async () => {
2744+
const { editor } = await testEnvironment();
2745+
const ops: DeltaOp[] = [
2746+
{ insert: "added text", attributes: { char: { style: "add", cid: "1" } } },
2747+
{ insert: "words of Jesus", attributes: { char: { style: "wj", cid: "2" } } },
2748+
];
2749+
2750+
await sutApplyUpdate(editor, ops);
2751+
2752+
editor.getEditorState().read(() => {
2753+
const root = $getRoot();
2754+
const para = root.getFirstChild();
2755+
if (!$isImpliedParaNode(para)) throw new Error("Expected an ImpliedParaNode");
2756+
expect(para.getChildrenSize()).toBe(2);
2757+
2758+
const addChar = para.getFirstChild();
2759+
if (!$isCharNode(addChar)) throw new Error("Expected a CharNode");
2760+
expect(addChar.getMarker()).toBe("add");
2761+
expect(addChar.getTextContent()).toBe("added text");
2762+
expect($getState(addChar, charIdState)).toBe("1");
2763+
2764+
const wjChar = para.getChildAtIndex(1);
2765+
if (!$isCharNode(wjChar)) throw new Error("Expected a CharNode");
2766+
expect(wjChar.getMarker()).toBe("wj");
2767+
expect(wjChar.getTextContent()).toBe("words of Jesus");
2768+
expect($getState(wjChar, charIdState)).toBe("2");
2769+
});
2770+
});
2771+
2772+
it("(dc) should insert adjacent chars where second has nested char", async () => {
2773+
const { editor } = await testEnvironment();
2774+
const ops: DeltaOp[] = [
2775+
{ insert: "added text", attributes: { char: { style: "add", cid: "1" } } },
2776+
{ insert: "words of ", attributes: { char: { style: "wj", cid: "2" } } },
2777+
{
2778+
insert: "Jesus",
2779+
attributes: {
2780+
char: [
2781+
{ style: "wj", cid: "2" },
2782+
{ style: "bd", cid: "3" },
2783+
],
2784+
},
2785+
},
2786+
];
2787+
2788+
await sutApplyUpdate(editor, ops);
2789+
2790+
editor.getEditorState().read(() => {
2791+
const root = $getRoot();
2792+
const para = root.getFirstChild();
2793+
if (!$isImpliedParaNode(para)) throw new Error("Expected an ImpliedParaNode");
2794+
expect(para.getChildrenSize()).toBe(2);
2795+
2796+
const addChar = para.getFirstChild();
2797+
if (!$isCharNode(addChar)) throw new Error("Expected a CharNode");
2798+
expect(addChar.getMarker()).toBe("add");
2799+
expect(addChar.getTextContent()).toBe("added text");
2800+
expect($getState(addChar, charIdState)).toBe("1");
2801+
2802+
const wjChar = para.getChildAtIndex(1);
2803+
if (!$isCharNode(wjChar)) throw new Error("Expected a CharNode");
2804+
expect(wjChar.getMarker()).toBe("wj");
2805+
expect(wjChar.getTextContent()).toBe("words of Jesus");
2806+
expect($getState(wjChar, charIdState)).toBe("2");
2807+
expect(wjChar.getChildrenSize()).toBe(2);
2808+
2809+
const textNode = wjChar.getFirstChild();
2810+
if (!$isTextNode(textNode)) throw new Error("Expected a TextNode");
2811+
expect(textNode.getTextContent()).toBe("words of ");
2812+
2813+
const bdChar = wjChar.getChildAtIndex(1);
2814+
if (!$isCharNode(bdChar)) throw new Error("Expected a CharNode");
2815+
expect(bdChar.getMarker()).toBe("bd");
2816+
expect(bdChar.getTextContent()).toBe("Jesus");
2817+
expect($getState(bdChar, charIdState)).toBe("3");
2818+
});
2819+
});
2820+
27422821
it("(dc) should insert milestone embeds in empty para", async () => {
27432822
const { editor } = await testEnvironment(() => {
27442823
$getRoot().append($createParaNode());
@@ -2801,27 +2880,6 @@ describe("Delta Utils $applyUpdate", () => {
28012880
});
28022881
});
28032882

2804-
it("(dc) should insert unmatched embeds in empty para", async () => {
2805-
const { editor } = await testEnvironment(() => {
2806-
$getRoot().append($createParaNode());
2807-
});
2808-
const unmatchedEmbed = { unmatched: { marker: "f*" } };
2809-
const ops: DeltaOp[] = [{ insert: unmatchedEmbed }];
2810-
2811-
await sutApplyUpdate(editor, ops);
2812-
2813-
editor.getEditorState().read(() => {
2814-
const p = $getRoot().getFirstChild();
2815-
if (!$isParaNode(p)) throw new Error("p is not a ParaNode");
2816-
expect(p.getChildrenSize()).toBe(1);
2817-
2818-
const unmatched = p.getFirstChild();
2819-
if (!$isImmutableUnmatchedNode(unmatched))
2820-
throw new Error("unmatched is not an ImmutableUnmatchedNode");
2821-
expect(unmatched.getMarker()).toBe("f*");
2822-
});
2823-
});
2824-
28252883
it("(dc) should insert a note", async () => {
28262884
const { editor } = await testEnvironment();
28272885
const ops: DeltaOp[] = [
@@ -3314,81 +3372,183 @@ describe("Delta Utils $applyUpdate", () => {
33143372
});
33153373
});
33163374

3317-
it("(dc) should insert adjacent chars with different markers", async () => {
3318-
const { editor } = await testEnvironment();
3375+
it("(dc) should insert unknown embed in empty para", async () => {
3376+
const { editor } = await testEnvironment(() => {
3377+
$getRoot().append($createParaNode());
3378+
});
3379+
const unknownEmbed = {
3380+
unknown: { tag: "wat", marker: "z", "attr-unknown": "watAttr" },
3381+
};
3382+
const ops: DeltaOp[] = [{ insert: unknownEmbed }];
3383+
3384+
await sutApplyUpdate(editor, ops);
3385+
3386+
editor.getEditorState().read(() => {
3387+
const p = $getRoot().getFirstChild();
3388+
if (!$isParaNode(p)) throw new Error("Expected a ParaNode");
3389+
expect(p.getChildrenSize()).toBe(1);
3390+
3391+
const unknown = p.getFirstChild();
3392+
if (!$isUnknownNode(unknown)) throw new Error("Expected an UnknownNode");
3393+
expect(unknown.getTag()).toBe("wat");
3394+
expect(unknown.getMarker()).toBe("z");
3395+
expect(unknown.getUnknownAttributes()).toEqual({ "attr-unknown": "watAttr" });
3396+
});
3397+
});
3398+
3399+
it("(dc) should insert unknown embed with text contents", async () => {
3400+
const { editor } = await testEnvironment(() => {
3401+
$getRoot().append($createParaNode());
3402+
});
33193403
const ops: DeltaOp[] = [
3320-
{ insert: "added text", attributes: { char: { style: "add", cid: "1" } } },
3321-
{ insert: "words of Jesus", attributes: { char: { style: "wj", cid: "2" } } },
3404+
{
3405+
insert: {
3406+
unknown: {
3407+
tag: "wat",
3408+
marker: "z",
3409+
"attr-unknown": "watAttr",
3410+
contents: { ops: [{ insert: "child text" }] },
3411+
},
3412+
},
3413+
},
33223414
];
33233415

33243416
await sutApplyUpdate(editor, ops);
33253417

33263418
editor.getEditorState().read(() => {
3327-
const root = $getRoot();
3328-
const para = root.getFirstChild();
3329-
if (!$isImpliedParaNode(para)) throw new Error("Expected an ImpliedParaNode");
3330-
expect(para.getChildrenSize()).toBe(2);
3419+
const para = $getRoot().getFirstChild();
3420+
if (!$isParaNode(para)) throw new Error("Expected a ParaNode");
33313421

3332-
const addChar = para.getFirstChild();
3333-
if (!$isCharNode(addChar)) throw new Error("Expected a CharNode");
3334-
expect(addChar.getMarker()).toBe("add");
3335-
expect(addChar.getTextContent()).toBe("added text");
3336-
expect($getState(addChar, charIdState)).toBe("1");
3422+
const unknown = para?.getFirstChild();
3423+
if (!$isUnknownNode(unknown)) throw new Error("Expected an UnknownNode");
3424+
expect(unknown.getTag()).toBe("wat");
3425+
expect(unknown.getMarker()).toBe("z");
3426+
expect(unknown.getUnknownAttributes()).toEqual({ "attr-unknown": "watAttr" });
3427+
expect(unknown.getChildrenSize()).toBe(1);
33373428

3338-
const wjChar = para.getChildAtIndex(1);
3339-
if (!$isCharNode(wjChar)) throw new Error("Expected a CharNode");
3340-
expect(wjChar.getMarker()).toBe("wj");
3341-
expect(wjChar.getTextContent()).toBe("words of Jesus");
3342-
expect($getState(wjChar, charIdState)).toBe("2");
3429+
const child = unknown.getFirstChild();
3430+
if (!$isTextNode(child)) throw new Error("Expected a TextNode child");
3431+
expect(child.getTextContent()).toBe("child text");
33433432
});
33443433
});
33453434

3346-
it("(dc) should insert adjacent chars where second has nested char", async () => {
3347-
const { editor } = await testEnvironment();
3435+
it("(dc) should insert unknown embed with nested unknown contents", async () => {
3436+
const { editor } = await testEnvironment(() => {
3437+
$getRoot().append($createParaNode());
3438+
});
33483439
const ops: DeltaOp[] = [
3349-
{ insert: "added text", attributes: { char: { style: "add", cid: "1" } } },
3350-
{ insert: "words of ", attributes: { char: { style: "wj", cid: "2" } } },
33513440
{
3352-
insert: "Jesus",
3353-
attributes: {
3354-
char: [
3355-
{ style: "wj", cid: "2" },
3356-
{ style: "bd", cid: "3" },
3357-
],
3441+
insert: {
3442+
unknown: {
3443+
tag: "outer",
3444+
marker: "om",
3445+
"attr-outer": "outerAttr",
3446+
contents: {
3447+
ops: [
3448+
{
3449+
insert: {
3450+
unknown: {
3451+
tag: "inner",
3452+
marker: "im",
3453+
"attr-inner": "innerAttr",
3454+
},
3455+
},
3456+
},
3457+
{ insert: "tail" },
3458+
],
3459+
},
3460+
},
33583461
},
33593462
},
33603463
];
33613464

33623465
await sutApplyUpdate(editor, ops);
33633466

33643467
editor.getEditorState().read(() => {
3365-
const root = $getRoot();
3366-
const para = root.getFirstChild();
3367-
if (!$isImpliedParaNode(para)) throw new Error("Expected an ImpliedParaNode");
3368-
expect(para.getChildrenSize()).toBe(2);
3468+
const para = $getRoot().getFirstChild();
3469+
if (!$isParaNode(para)) throw new Error("Expected a ParaNode");
33693470

3370-
const addChar = para.getFirstChild();
3371-
if (!$isCharNode(addChar)) throw new Error("Expected a CharNode");
3372-
expect(addChar.getMarker()).toBe("add");
3373-
expect(addChar.getTextContent()).toBe("added text");
3374-
expect($getState(addChar, charIdState)).toBe("1");
3471+
const unknown = para?.getFirstChild();
3472+
if (!$isUnknownNode(unknown)) throw new Error("Expected an UnknownNode");
3473+
expect(unknown.getTag()).toBe("outer");
3474+
expect(unknown.getMarker()).toBe("om");
3475+
expect(unknown.getUnknownAttributes()).toEqual({ "attr-outer": "outerAttr" });
3476+
expect(unknown.getChildrenSize()).toBe(2);
33753477

3376-
const wjChar = para.getChildAtIndex(1);
3377-
if (!$isCharNode(wjChar)) throw new Error("Expected a CharNode");
3378-
expect(wjChar.getMarker()).toBe("wj");
3379-
expect(wjChar.getTextContent()).toBe("words of Jesus");
3380-
expect($getState(wjChar, charIdState)).toBe("2");
3381-
expect(wjChar.getChildrenSize()).toBe(2);
3478+
const innerUnknown = unknown.getFirstChild();
3479+
if (!$isUnknownNode(innerUnknown)) throw new Error("Expected nested UnknownNode");
3480+
expect(innerUnknown.getTag()).toBe("inner");
3481+
expect(innerUnknown.getMarker()).toBe("im");
3482+
expect(innerUnknown.getUnknownAttributes()).toEqual({ "attr-inner": "innerAttr" });
33823483

3383-
const textNode = wjChar.getFirstChild();
3384-
if (!$isTextNode(textNode)) throw new Error("Expected a TextNode");
3385-
expect(textNode.getTextContent()).toBe("words of ");
3484+
const tail = unknown.getChildAtIndex(1);
3485+
if (!$isTextNode(tail)) throw new Error("Expected trailing text in unknown contents");
3486+
expect(tail.getTextContent()).toBe("tail");
3487+
});
3488+
});
33863489

3387-
const bdChar = wjChar.getChildAtIndex(1);
3388-
if (!$isCharNode(bdChar)) throw new Error("Expected a CharNode");
3389-
expect(bdChar.getMarker()).toBe("bd");
3390-
expect(bdChar.getTextContent()).toBe("Jesus");
3391-
expect($getState(bdChar, charIdState)).toBe("3");
3490+
it("(dc) should insert unknown embed with char contents", async () => {
3491+
const { editor } = await testEnvironment(() => {
3492+
$getRoot().append($createParaNode());
3493+
});
3494+
const ops: DeltaOp[] = [
3495+
{
3496+
insert: {
3497+
unknown: {
3498+
tag: "wat",
3499+
contents: {
3500+
ops: [
3501+
{
3502+
insert: "bold",
3503+
attributes: { char: { style: "bd", cid: "char-id-1" } },
3504+
},
3505+
],
3506+
},
3507+
},
3508+
},
3509+
},
3510+
];
3511+
3512+
await sutApplyUpdate(editor, ops);
3513+
3514+
editor.getEditorState().read(() => {
3515+
const para = $getRoot().getFirstChild();
3516+
if (!$isParaNode(para)) throw new Error("Expected a ParaNode");
3517+
3518+
const unknown = para?.getFirstChild();
3519+
if (!$isUnknownNode(unknown)) throw new Error("Expected an UnknownNode");
3520+
expect(unknown.getTag()).toBe("wat");
3521+
expect(unknown.getChildrenSize()).toBe(1);
3522+
3523+
const child = unknown.getFirstChild();
3524+
if (!$isCharNode(child)) throw new Error("Expected CharNode child");
3525+
expect(child.getMarker()).toBe("bd");
3526+
expect($getState(child, charIdState)).toBe("char-id-1");
3527+
3528+
const text = child.getFirstChild();
3529+
if (!$isTextNode(text)) throw new Error("Expected text within CharNode");
3530+
expect(text.getTextContent()).toBe("bold");
3531+
});
3532+
});
3533+
3534+
it("(dc) should insert unmatched embed in empty para", async () => {
3535+
const { editor } = await testEnvironment(() => {
3536+
$getRoot().append($createParaNode());
3537+
});
3538+
const unmatchedEmbed = { unmatched: { marker: "f*" } };
3539+
const ops: DeltaOp[] = [{ insert: unmatchedEmbed }];
3540+
3541+
await sutApplyUpdate(editor, ops);
3542+
3543+
editor.getEditorState().read(() => {
3544+
const p = $getRoot().getFirstChild();
3545+
if (!$isParaNode(p)) throw new Error("p is not a ParaNode");
3546+
expect(p.getChildrenSize()).toBe(1);
3547+
3548+
const unmatched = p.getFirstChild();
3549+
if (!$isImmutableUnmatchedNode(unmatched))
3550+
throw new Error("unmatched is not an ImmutableUnmatchedNode");
3551+
expect(unmatched.getMarker()).toBe("f*");
33923552
});
33933553
});
33943554

0 commit comments

Comments
 (0)