diff --git a/src/record/observer.ts b/src/record/observer.ts index 1c367be4..d03d2e2e 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -191,17 +191,14 @@ function initMutationObserver( const addQueue: Node[] = []; const pushAdd = (n: Node) => { const parentId = mirror.getId((n.parentNode as Node) as INode); - if (parentId === -1) { + const nextId = + n.nextSibling && mirror.getId((n.nextSibling as unknown) as INode); + if (parentId === -1 || nextId === -1) { return addQueue.push(n); } adds.push({ parentId, - previousId: !n.previousSibling - ? n.previousSibling - : mirror.getId((n.previousSibling as unknown) as INode), - nextId: !n.nextSibling - ? n.nextSibling - : mirror.getId((n.nextSibling as unknown) as INode), + nextId, node: serializeNodeWithId( n, document, diff --git a/src/replay/index.ts b/src/replay/index.ts index d1e58b40..6c6537ea 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -69,7 +69,7 @@ export class Replayer { private nextUserInteractionEvent: eventWithTime | null; private noramlSpeed: number = -1; - private missingNodeRetryMap: missingNodeMap = {}; + private legacy_missingNodeRetryMap: missingNodeMap = {}; private service!: ReturnType; @@ -320,13 +320,13 @@ export class Replayer { private rebuildFullSnapshot( event: fullSnapshotEvent & { timestamp: number }, ) { - if (Object.keys(this.missingNodeRetryMap).length) { + if (Object.keys(this.legacy_missingNodeRetryMap).length) { console.warn( 'Found unresolved missing node map', - this.missingNodeRetryMap, + this.legacy_missingNodeRetryMap, ); } - this.missingNodeRetryMap = {}; + this.legacy_missingNodeRetryMap = {}; mirror.map = rebuild(event.data.node, this.iframe.contentDocument!)[1]; const styleEl = document.createElement('style'); const { documentElement, head } = this.iframe.contentDocument!; @@ -405,7 +405,9 @@ export class Replayer { } }); - const missingNodeMap: missingNodeMap = { ...this.missingNodeRetryMap }; + const legacy_missingNodeMap: missingNodeMap = { + ...this.legacy_missingNodeRetryMap, + }; const queue: addedNodeMutation[] = []; const appendNode = (mutation: addedNodeMutation) => { @@ -413,12 +415,7 @@ export class Replayer { if (!parent) { return queue.push(mutation); } - const target = buildNodeWithSN( - mutation.node, - this.iframe.contentDocument!, - mirror.map, - true, - ) as Node; + let previous: Node | null = null; let next: Node | null = null; if (mutation.previousId) { @@ -427,9 +424,21 @@ export class Replayer { if (mutation.nextId) { next = mirror.getNode(mutation.nextId) as Node; } + // next not present at this moment + if (mutation.nextId !== null && mutation.nextId !== -1 && !next) { + return queue.push(mutation); + } + const target = buildNodeWithSN( + mutation.node, + this.iframe.contentDocument!, + mirror.map, + true, + ) as Node; + + // legacy data, we should not have -1 siblings any more if (mutation.previousId === -1 || mutation.nextId === -1) { - missingNodeMap[mutation.node.id] = { + legacy_missingNodeMap[mutation.node.id] = { node: target, mutation, }; @@ -453,7 +462,12 @@ export class Replayer { } if (mutation.previousId || mutation.nextId) { - this.resolveMissingNode(missingNodeMap, parent, target, mutation); + this.legacy_resolveMissingNode( + legacy_missingNodeMap, + parent, + target, + mutation, + ); } }; @@ -469,8 +483,8 @@ export class Replayer { appendNode(mutation); } - if (Object.keys(missingNodeMap).length) { - Object.assign(this.missingNodeRetryMap, missingNodeMap); + if (Object.keys(legacy_missingNodeMap).length) { + Object.assign(this.legacy_missingNodeRetryMap, legacy_missingNodeMap); } d.texts.forEach((mutation) => { @@ -685,7 +699,7 @@ export class Replayer { } } - private resolveMissingNode( + private legacy_resolveMissingNode( map: missingNodeMap, parent: Node, target: Node, @@ -698,18 +712,18 @@ export class Replayer { const { node, mutation } = previousInMap as missingNode; parent.insertBefore(node, target); delete map[mutation.node.id]; - delete this.missingNodeRetryMap[mutation.node.id]; + delete this.legacy_missingNodeRetryMap[mutation.node.id]; if (mutation.previousId || mutation.nextId) { - this.resolveMissingNode(map, parent, node as Node, mutation); + this.legacy_resolveMissingNode(map, parent, node as Node, mutation); } } if (nextInMap) { const { node, mutation } = nextInMap as missingNode; parent.insertBefore(node, target.nextSibling); delete map[mutation.node.id]; - delete this.missingNodeRetryMap[mutation.node.id]; + delete this.legacy_missingNodeRetryMap[mutation.node.id]; if (mutation.previousId || mutation.nextId) { - this.resolveMissingNode(map, parent, node as Node, mutation); + this.legacy_resolveMissingNode(map, parent, node as Node, mutation); } } } diff --git a/src/types.ts b/src/types.ts index 6431b3df..9a88ef12 100644 --- a/src/types.ts +++ b/src/types.ts @@ -195,7 +195,8 @@ export type removedNodeMutation = { export type addedNodeMutation = { parentId: number; - previousId: number | null; + // Newly recorded mutations will not have previousId any more, just for compatibility + previousId?: number | null; nextId: number | null; node: serializedNodeWithId; }; diff --git a/test/__snapshots__/integration.test.ts.snap b/test/__snapshots__/integration.test.ts.snap index 64784506..534ddf43 100644 --- a/test/__snapshots__/integration.test.ts.snap +++ b/test/__snapshots__/integration.test.ts.snap @@ -479,7 +479,6 @@ exports[`character-data 1`] = ` \\"adds\\": [ { \\"parentId\\": 6, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -635,7 +634,6 @@ exports[`child-list 1`] = ` \\"adds\\": [ { \\"parentId\\": 6, - \\"previousId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2480,7 +2478,6 @@ exports[`move-node-1 1`] = ` \\"adds\\": [ { \\"parentId\\": 6, - \\"previousId\\": 9, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2492,7 +2489,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 11, - \\"previousId\\": null, \\"nextId\\": 13, \\"node\\": { \\"type\\": 3, @@ -2502,7 +2498,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 11, - \\"previousId\\": 12, \\"nextId\\": 18, \\"node\\": { \\"type\\": 2, @@ -2514,7 +2509,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": null, \\"nextId\\": 15, \\"node\\": { \\"type\\": 3, @@ -2524,7 +2518,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": 14, \\"nextId\\": 17, \\"node\\": { \\"type\\": 2, @@ -2536,7 +2529,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 15, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2546,7 +2538,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": 15, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2556,7 +2547,6 @@ exports[`move-node-1 1`] = ` }, { \\"parentId\\": 11, - \\"previousId\\": 13, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2748,7 +2738,6 @@ exports[`move-node-2 1`] = ` \\"adds\\": [ { \\"parentId\\": 11, - \\"previousId\\": null, \\"nextId\\": 13, \\"node\\": { \\"type\\": 3, @@ -2758,7 +2747,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 11, - \\"previousId\\": 12, \\"nextId\\": 18, \\"node\\": { \\"type\\": 2, @@ -2770,7 +2758,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": null, \\"nextId\\": 15, \\"node\\": { \\"type\\": 3, @@ -2780,7 +2767,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": 14, \\"nextId\\": 17, \\"node\\": { \\"type\\": 2, @@ -2792,7 +2778,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 15, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2802,7 +2787,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 13, - \\"previousId\\": 15, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2812,7 +2796,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 11, - \\"previousId\\": 13, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -2822,7 +2805,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 4, - \\"previousId\\": 22, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2834,7 +2816,6 @@ exports[`move-node-2 1`] = ` }, { \\"parentId\\": 23, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -3804,7 +3785,7 @@ exports[`select2 1`] = ` } }, { - \\"id\\": 75, + \\"id\\": 70, \\"attributes\\": { \\"style\\": \\"\\" } @@ -3823,7 +3804,7 @@ exports[`select2 1`] = ` } }, { - \\"id\\": 67, + \\"id\\": 72, \\"attributes\\": { \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\" } @@ -3842,7 +3823,6 @@ exports[`select2 1`] = ` \\"adds\\": [ { \\"parentId\\": 25, - \\"previousId\\": null, \\"nextId\\": 34, \\"node\\": { \\"type\\": 2, @@ -3858,7 +3838,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 26, - \\"previousId\\": null, \\"nextId\\": 28, \\"node\\": { \\"type\\": 3, @@ -3868,7 +3847,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 26, - \\"previousId\\": 27, \\"nextId\\": 30, \\"node\\": { \\"type\\": 2, @@ -3883,7 +3861,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 28, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -3893,7 +3870,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 26, - \\"previousId\\": 28, \\"nextId\\": 31, \\"node\\": { \\"type\\": 2, @@ -3907,7 +3883,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 26, - \\"previousId\\": 30, \\"nextId\\": 32, \\"node\\": { \\"type\\": 3, @@ -3917,7 +3892,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 26, - \\"previousId\\": 31, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -3932,7 +3906,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 32, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -3946,7 +3919,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 18, - \\"previousId\\": -1, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -3961,7 +3933,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 36, - \\"previousId\\": null, \\"nextId\\": 38, \\"node\\": { \\"type\\": 3, @@ -3971,7 +3942,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 36, - \\"previousId\\": 37, \\"nextId\\": 44, \\"node\\": { \\"type\\": 2, @@ -3985,7 +3955,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 38, - \\"previousId\\": null, \\"nextId\\": 40, \\"node\\": { \\"type\\": 3, @@ -3995,7 +3964,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 38, - \\"previousId\\": 39, \\"nextId\\": 41, \\"node\\": { \\"type\\": 2, @@ -4010,7 +3978,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 38, - \\"previousId\\": 40, \\"nextId\\": 42, \\"node\\": { \\"type\\": 3, @@ -4020,7 +3987,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 38, - \\"previousId\\": 41, \\"nextId\\": 43, \\"node\\": { \\"type\\": 2, @@ -4046,7 +4012,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 38, - \\"previousId\\": 42, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -4056,7 +4021,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 36, - \\"previousId\\": 38, \\"nextId\\": 45, \\"node\\": { \\"type\\": 3, @@ -4066,7 +4030,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 36, - \\"previousId\\": 44, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -4082,62 +4045,6 @@ exports[`select2 1`] = ` }, { \\"parentId\\": 45, - \\"previousId\\": null, - \\"nextId\\": -1, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"li\\", - \\"attributes\\": { - \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\", - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [], - \\"id\\": 67 - } - }, - { - \\"parentId\\": 67, - \\"previousId\\": null, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"div\\", - \\"attributes\\": { - \\"class\\": \\"select2-result-label\\", - \\"id\\": \\"select2-result-label-2\\", - \\"role\\": \\"option\\" - }, - \\"childNodes\\": [], - \\"id\\": 68 - } - }, - { - \\"parentId\\": 68, - \\"previousId\\": null, - \\"nextId\\": -1, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-match\\" - }, - \\"childNodes\\": [], - \\"id\\": 69 - } - }, - { - \\"parentId\\": 68, - \\"previousId\\": 69, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 70 - } - }, - { - \\"parentId\\": 45, - \\"previousId\\": 67, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -4147,12 +4054,11 @@ exports[`select2 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 71 + \\"id\\": 67 } }, { - \\"parentId\\": 71, - \\"previousId\\": null, + \\"parentId\\": 67, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -4163,36 +4069,20 @@ exports[`select2 1`] = ` \\"role\\": \\"option\\" }, \\"childNodes\\": [], - \\"id\\": 72 + \\"id\\": 68 } }, { - \\"parentId\\": 72, - \\"previousId\\": null, - \\"nextId\\": -1, - \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"span\\", - \\"attributes\\": { - \\"class\\": \\"select2-match\\" - }, - \\"childNodes\\": [], - \\"id\\": 73 - } - }, - { - \\"parentId\\": 72, - \\"previousId\\": 73, + \\"parentId\\": 68, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"B\\", - \\"id\\": 74 + \\"id\\": 69 } }, { \\"parentId\\": 18, - \\"previousId\\": 66, \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, @@ -4203,16 +4093,79 @@ exports[`select2 1`] = ` \\"style\\": \\"\\" }, \\"childNodes\\": [], - \\"id\\": 75 + \\"id\\": 70 } }, { \\"parentId\\": 62, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", + \\"id\\": 71 + } + }, + { + \\"parentId\\": 45, + \\"nextId\\": 67, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": { + \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\", + \\"role\\": \\"presentation\\" + }, + \\"childNodes\\": [], + \\"id\\": 72 + } + }, + { + \\"parentId\\": 72, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"select2-result-label\\", + \\"id\\": \\"select2-result-label-2\\", + \\"role\\": \\"option\\" + }, + \\"childNodes\\": [], + \\"id\\": 73 + } + }, + { + \\"parentId\\": 73, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 74 + } + }, + { + \\"parentId\\": 68, + \\"nextId\\": 69, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-match\\" + }, + \\"childNodes\\": [], + \\"id\\": 75 + } + }, + { + \\"parentId\\": 73, + \\"nextId\\": 74, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-match\\" + }, + \\"childNodes\\": [], \\"id\\": 76 } } @@ -4224,7 +4177,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 75 + \\"id\\": 70 } }, { @@ -4250,7 +4203,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 1, - \\"id\\": 75 + \\"id\\": 70 } }, { @@ -4276,7 +4229,7 @@ exports[`select2 1`] = ` \\"texts\\": [], \\"attributes\\": [ { - \\"id\\": 75, + \\"id\\": 70, \\"attributes\\": { \\"style\\": \\"display: none;\\" } @@ -4309,21 +4262,20 @@ exports[`select2 1`] = ` \\"removes\\": [ { \\"parentId\\": 18, - \\"id\\": 75 + \\"id\\": 70 + }, + { + \\"parentId\\": 45, + \\"id\\": 72 }, { \\"parentId\\": 45, \\"id\\": 67 - }, - { - \\"parentId\\": 45, - \\"id\\": 71 } ], \\"adds\\": [ { \\"parentId\\": 18, - \\"previousId\\": 66, \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, @@ -4334,7 +4286,7 @@ exports[`select2 1`] = ` \\"style\\": \\"display: none;\\" }, \\"childNodes\\": [], - \\"id\\": 75 + \\"id\\": 70 } } ] @@ -4350,3 +4302,177 @@ exports[`select2 1`] = ` } ]" `; + +exports[`serialize-before-record 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 3 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"mutation observer\\", + \\"id\\": 7 + } + ], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 8 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"ul\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 10 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 11 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + } + ], + \\"id\\": 9 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 13 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 15 + } + ], + \\"id\\": 14 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 16 + } + ], + \\"id\\": 4 + } + ], + \\"id\\": 2 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 9, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 17 + } + }, + { + \\"parentId\\": 9, + \\"nextId\\": 17, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 18 + } + }, + { + \\"parentId\\": 9, + \\"nextId\\": 18, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 19 + } + } + ] + } + } +]" +`; diff --git a/test/__snapshots__/record.test.ts.snap b/test/__snapshots__/record.test.ts.snap index 03eff2af..70c7ec05 100644 --- a/test/__snapshots__/record.test.ts.snap +++ b/test/__snapshots__/record.test.ts.snap @@ -82,7 +82,6 @@ exports[`async-checkout 1`] = ` \\"adds\\": [ { \\"parentId\\": 4, - \\"previousId\\": 7, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -94,7 +93,6 @@ exports[`async-checkout 1`] = ` }, { \\"parentId\\": 8, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -117,7 +115,6 @@ exports[`async-checkout 1`] = ` \\"adds\\": [ { \\"parentId\\": 9, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -220,7 +217,6 @@ exports[`async-checkout 1`] = ` \\"adds\\": [ { \\"parentId\\": 4, - \\"previousId\\": 8, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -232,7 +228,6 @@ exports[`async-checkout 1`] = ` }, { \\"parentId\\": 9, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, @@ -409,7 +404,6 @@ exports[`stylesheet-rules 1`] = ` \\"adds\\": [ { \\"parentId\\": 3, - \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, diff --git a/test/html/shuffle.html b/test/html/shuffle.html new file mode 100644 index 00000000..e8af09d3 --- /dev/null +++ b/test/html/shuffle.html @@ -0,0 +1,12 @@ + + + + + + shuffle + + + + + + diff --git a/test/integration.test.ts b/test/integration.test.ts index 8f72f7ed..afe8dba3 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import * as puppeteer from 'puppeteer'; import { assertSnapshot, launchPuppeteer } from './utils'; import { Suite } from 'mocha'; +import { expect } from 'chai'; import { recordOptions, eventWithTime } from '../src/types'; interface ISuite extends Suite { @@ -63,7 +64,7 @@ describe('record integration tests', function (this: ISuite) { const snapshots = await page.evaluate('window.snapshots'); assertSnapshot(snapshots, __filename, 'form'); - }).timeout(5000); + }); it('can record childList mutations', async () => { const page: puppeteer.Page = await this.browser.newPage(); @@ -81,7 +82,7 @@ describe('record integration tests', function (this: ISuite) { const snapshots = await page.evaluate('window.snapshots'); assertSnapshot(snapshots, __filename, 'child-list'); - }).timeout(5000); + }); it('can record character data muatations', async () => { const page: puppeteer.Page = await this.browser.newPage(); @@ -219,4 +220,45 @@ describe('record integration tests', function (this: ISuite) { const snapshots = await page.evaluate('window.snapshots'); assertSnapshot(snapshots, __filename, 'react-styled-components'); }); + + it('will serialize node before record', async () => { + const page: puppeteer.Page = await this.browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'mutation-observer.html')); + + await page.evaluate(() => { + const ul = document.querySelector('ul') as HTMLUListElement; + let count = 3; + while (count > 0) { + count--; + const li = document.createElement('li'); + ul.appendChild(li); + } + }); + + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots, __filename, 'serialize-before-record'); + }); + + it('will defer missing next node mutation', async () => { + const page: puppeteer.Page = await this.browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'shuffle.html')); + + const text = await page.evaluate(() => { + const els = Array.prototype.slice.call(document.querySelectorAll('li')); + const parent = document.querySelector('ul')!; + parent.removeChild(els[3]); + parent.removeChild(els[2]); + parent.removeChild(els[1]); + parent.removeChild(els[0]); + parent.insertBefore(els[3], els[4]); + parent.insertBefore(els[2], els[4]); + parent.insertBefore(els[1], els[4]); + parent.insertBefore(els[0], els[4]); + return parent.innerText; + }); + + expect(text).to.equal('4\n3\n2\n1\n5'); + }); });