diff --git a/src/record/collection.ts b/src/record/collection.ts index f384a342..01df8c6e 100644 --- a/src/record/collection.ts +++ b/src/record/collection.ts @@ -29,13 +29,13 @@ export function isParentRemoved( return isParentRemoved(removes, parentNode); } -export function isParentDropped(droppedSet: Set, n: Node): boolean { +export function isAncestorInSet(set: Set, n: Node): boolean { const { parentNode } = n; if (!parentNode) { return false; } - if (droppedSet.has(parentNode)) { + if (set.has(parentNode)) { return true; } - return isParentDropped(droppedSet, parentNode); + return isAncestorInSet(set, parentNode); } diff --git a/src/record/observer.ts b/src/record/observer.ts index 51484073..1907641e 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -28,7 +28,12 @@ import { attributeCursor, blockClass, } from '../types'; -import { deepDelete, isParentRemoved, isParentDropped } from './collection'; +import { deepDelete, isParentRemoved, isAncestorInSet } from './collection'; + +const moveKey = (id: number, parentId: number) => `${id}@${parentId}`; +function isINode(n: Node | INode): n is INode { + return '__sn' in n; +} /** * Mutation observer will merge several mutations into an array and pass @@ -55,20 +60,35 @@ function initMutationObserver( const observer = new MutationObserver(mutations => { const texts: textCursor[] = []; const attributes: attributeCursor[] = []; - const removes: removedNodeMutation[] = []; + let removes: removedNodeMutation[] = []; const adds: addedNodeMutation[] = []; - const addsSet = new Set(); + const addedSet = new Set(); + const movedSet = new Set(); const droppedSet = new Set(); - const genAdds = (n: Node) => { + const movedMap: Record = {}; + + const genAdds = (n: Node | INode, target?: Node | INode) => { if (isBlocked(n, blockClass)) { return; } - addsSet.add(n); - droppedSet.delete(n); + if (isINode(n)) { + movedSet.add(n); + let targetId: number | null = null; + if (target && isINode(target)) { + targetId = target.__sn.id; + } + if (targetId) { + movedMap[moveKey(n.__sn.id, targetId)] = true; + } + } else { + addedSet.add(n); + droppedSet.delete(n); + } n.childNodes.forEach(childN => genAdds(childN)); }; + mutations.forEach(mutation => { const { type, @@ -109,7 +129,7 @@ function initMutationObserver( break; } case 'childList': { - addedNodes.forEach(n => genAdds(n)); + addedNodes.forEach(n => genAdds(n, target)); removedNodes.forEach(n => { const nodeId = mirror.getId(n as INode); const parentId = mirror.getId(target as INode); @@ -117,14 +137,15 @@ function initMutationObserver( return; } // removed node has not been serialized yet, just remove it from the Set - if (addsSet.has(n)) { - deepDelete(addsSet, n); + if (addedSet.has(n)) { + deepDelete(addedSet, n); droppedSet.add(n); - } else if (addsSet.has(target) && nodeId === -1) { + } else if (addedSet.has(target) && nodeId === -1) { /** * If target was newly added and removed child node was * not serialized, it means the child node has been removed - * before callback fired, so we can ignore it. + * before callback fired, so we can ignore it because + * newly added node will be serialized without child nodes. * TODO: verify this */ } else if (isAncestorRemoved(target as INode)) { @@ -134,6 +155,8 @@ function initMutationObserver( * the node is also removed which we do not need to track * and replay. */ + } else if (movedSet.has(n) && movedMap[moveKey(nodeId, parentId)]) { + deepDelete(movedSet, n); } else { removes.push({ parentId, @@ -149,23 +172,63 @@ function initMutationObserver( } }); - Array.from(addsSet).forEach(n => { - if (!isParentDropped(droppedSet, n) && !isParentRemoved(removes, n)) { - adds.push({ - parentId: mirror.getId((n.parentNode as Node) as INode), - previousId: !n.previousSibling - ? n.previousSibling - : mirror.getId(n.previousSibling as INode), - nextId: !n.nextSibling - ? n.nextSibling - : mirror.getId((n.nextSibling as unknown) as INode), - node: serializeNodeWithId(n, document, mirror.map, blockClass, true)!, - }); + /** + * Sometimes child node may be pushed before its newly added + * parent, so we init a queue to store these nodes. + */ + const addQueue: Node[] = []; + const pushAdd = (n: Node) => { + const parentId = mirror.getId((n.parentNode as Node) as INode); + if (parentId === -1) { + return addQueue.push(n); + } + adds.push({ + parentId, + previousId: !n.previousSibling + ? n.previousSibling + : mirror.getId(n.previousSibling as INode), + nextId: !n.nextSibling + ? n.nextSibling + : mirror.getId((n.nextSibling as unknown) as INode), + node: serializeNodeWithId( + n, + document, + mirror.map, + blockClass, + true, + inlineStylesheet, + )!, + }); + }; + + Array.from(movedSet).forEach(pushAdd); + + Array.from(addedSet).forEach(n => { + if (!isAncestorInSet(droppedSet, n) && !isParentRemoved(removes, n)) { + pushAdd(n); + } else if (isAncestorInSet(movedSet, n)) { + pushAdd(n); } else { droppedSet.add(n); } }); + while (addQueue.length) { + if ( + addQueue.every( + n => mirror.getId((n.parentNode as Node) as INode) === -1, + ) + ) { + /** + * If all nodes in queue could not find a serialized parent, + * it may be a bug or corner case. We need to escape the + * dead while loop at once. + */ + break; + } + pushAdd(addQueue.shift()!); + } + const payload = { texts: texts .map(text => ({ diff --git a/src/replay/index.ts b/src/replay/index.ts index 75007bc5..9539b46d 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -353,17 +353,19 @@ export class Replayer { }); const missingNodeMap: missingNodeMap = { ...this.missingNodeRetryMap }; - d.adds.forEach(mutation => { + const queue: addedNodeMutation[] = []; + + const appendNode = (mutation: addedNodeMutation) => { + const parent = mirror.getNode(mutation.parentId); + if (!parent) { + return queue.push(mutation); + } const target = buildNodeWithSN( mutation.node, this.iframe.contentDocument!, mirror.map, true, ) as Node; - const parent = mirror.getNode(mutation.parentId); - if (!parent) { - return this.warnNodeNotFound(d, mutation.parentId); - } let previous: Node | null = null; let next: Node | null = null; if (mutation.previousId) { @@ -396,7 +398,20 @@ export class Replayer { if (mutation.previousId || mutation.nextId) { this.resolveMissingNode(missingNodeMap, parent, target, mutation); } + }; + + d.adds.forEach(mutation => { + appendNode(mutation); }); + + while (queue.length) { + if (queue.every(m => !Boolean(mirror.getNode(m.parentId)))) { + return queue.forEach(m => this.warnNodeNotFound(d, m.node.id)); + } + const mutation = queue.shift()!; + appendNode(mutation); + } + if (Object.keys(missingNodeMap).length) { Object.assign(this.missingNodeRetryMap, missingNodeMap); } diff --git a/test/__snapshots__/integration.test.ts.snap b/test/__snapshots__/integration.test.ts.snap index 56a09d17..74231900 100644 --- a/test/__snapshots__/integration.test.ts.snap +++ b/test/__snapshots__/integration.test.ts.snap @@ -1613,7 +1613,7 @@ exports[`ignore 1`] = ` ]" `; -exports[`move-node 1`] = ` +exports[`move-node-1 1`] = ` "[ { \\"type\\": 0, @@ -1798,81 +1798,361 @@ exports[`move-node 1`] = ` \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 23 + \\"id\\": 11 } }, { - \\"parentId\\": 23, + \\"parentId\\": 11, \\"previousId\\": null, \\"nextId\\": 13, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 24 + \\"id\\": 12 } }, { - \\"parentId\\": 23, - \\"previousId\\": 24, + \\"parentId\\": 11, + \\"previousId\\": 12, \\"nextId\\": 18, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"i\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 25 + \\"id\\": 13 } }, { - \\"parentId\\": 25, + \\"parentId\\": 13, \\"previousId\\": null, \\"nextId\\": 15, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 26 + \\"id\\": 14 } }, { - \\"parentId\\": 25, - \\"previousId\\": 26, + \\"parentId\\": 13, + \\"previousId\\": 14, \\"nextId\\": 17, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"b\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 27 + \\"id\\": 15 } }, { - \\"parentId\\": 27, + \\"parentId\\": 15, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"1\\", - \\"id\\": 28 + \\"id\\": 16 } }, { - \\"parentId\\": 25, - \\"previousId\\": 27, + \\"parentId\\": 13, + \\"previousId\\": 15, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 29 + \\"id\\": 17 } }, { - \\"parentId\\": 23, - \\"previousId\\": 25, + \\"parentId\\": 11, + \\"previousId\\": 13, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\", - \\"id\\": 30 + \\"id\\": 18 + } + } + ] + } + } +]" +`; + +exports[`move-node-2 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\\": \\"div\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + } + ], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 10 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 16 + } + ], + \\"id\\": 15 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + } + ], + \\"id\\": 13 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + ], + \\"id\\": 11 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 21 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 22 + } + ], + \\"id\\": 4 + } + ], + \\"id\\": 2 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 4, + \\"id\\": 11 + } + ], + \\"adds\\": [ + { + \\"parentId\\": 11, + \\"previousId\\": null, + \\"nextId\\": 13, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + } + }, + { + \\"parentId\\": 11, + \\"previousId\\": 12, + \\"nextId\\": 18, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"i\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 13 + } + }, + { + \\"parentId\\": 13, + \\"previousId\\": null, + \\"nextId\\": 15, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + }, + { + \\"parentId\\": 13, + \\"previousId\\": 14, + \\"nextId\\": 17, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"b\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 15 + } + }, + { + \\"parentId\\": 15, + \\"previousId\\": null, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 16 + } + }, + { + \\"parentId\\": 13, + \\"previousId\\": 15, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + } + }, + { + \\"parentId\\": 11, + \\"previousId\\": 13, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 + } + }, + { + \\"parentId\\": 4, + \\"previousId\\": 22, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"div\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 23 + } + }, + { + \\"parentId\\": 23, + \\"previousId\\": null, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 11 } } ] @@ -2432,20 +2712,20 @@ exports[`select2 1`] = ` } }, { - \\"id\\": 75, + \\"id\\": 36, \\"attributes\\": { \\"id\\": \\"select2-drop\\", \\"class\\": \\"select2-drop select2-display-none select2-with-searchbox select2-drop-active\\" } }, { - \\"id\\": 93, + \\"id\\": 75, \\"attributes\\": { \\"style\\": \\"\\" } }, { - \\"id\\": 81, + \\"id\\": 42, \\"attributes\\": { \\"class\\": \\"select2-input select2-focused\\", \\"aria-activedescendant\\": \\"select2-result-label-2\\" @@ -2458,7 +2738,7 @@ exports[`select2 1`] = ` } }, { - \\"id\\": 85, + \\"id\\": 67, \\"attributes\\": { \\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\" } @@ -2488,22 +2768,22 @@ exports[`select2 1`] = ` \\"tabindex\\": \\"-1\\" }, \\"childNodes\\": [], - \\"id\\": 67 + \\"id\\": 26 } }, { - \\"parentId\\": 67, + \\"parentId\\": 26, \\"previousId\\": null, \\"nextId\\": 28, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 68 + \\"id\\": 27 } }, { - \\"parentId\\": 67, - \\"previousId\\": 68, + \\"parentId\\": 26, + \\"previousId\\": 27, \\"nextId\\": 30, \\"node\\": { \\"type\\": 2, @@ -2513,22 +2793,22 @@ exports[`select2 1`] = ` \\"id\\": \\"select2-chosen-1\\" }, \\"childNodes\\": [], - \\"id\\": 69 + \\"id\\": 28 } }, { - \\"parentId\\": 69, + \\"parentId\\": 28, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"A\\", - \\"id\\": 70 + \\"id\\": 29 } }, { - \\"parentId\\": 67, - \\"previousId\\": 69, + \\"parentId\\": 26, + \\"previousId\\": 28, \\"nextId\\": 31, \\"node\\": { \\"type\\": 2, @@ -2537,22 +2817,22 @@ exports[`select2 1`] = ` \\"class\\": \\"select2-search-choice-close\\" }, \\"childNodes\\": [], - \\"id\\": 71 + \\"id\\": 30 } }, { - \\"parentId\\": 67, - \\"previousId\\": 71, + \\"parentId\\": 26, + \\"previousId\\": 30, \\"nextId\\": 32, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 72 + \\"id\\": 31 } }, { - \\"parentId\\": 67, - \\"previousId\\": 72, + \\"parentId\\": 26, + \\"previousId\\": 31, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2562,11 +2842,11 @@ exports[`select2 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 73 + \\"id\\": 32 } }, { - \\"parentId\\": 73, + \\"parentId\\": 32, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { @@ -2576,7 +2856,7 @@ exports[`select2 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 74 + \\"id\\": 33 } }, { @@ -2591,22 +2871,22 @@ exports[`select2 1`] = ` \\"id\\": \\"select2-drop\\" }, \\"childNodes\\": [], - \\"id\\": 75 + \\"id\\": 36 } }, { - \\"parentId\\": 75, + \\"parentId\\": 36, \\"previousId\\": null, \\"nextId\\": 38, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 76 + \\"id\\": 37 } }, { - \\"parentId\\": 75, - \\"previousId\\": 76, + \\"parentId\\": 36, + \\"previousId\\": 37, \\"nextId\\": 44, \\"node\\": { \\"type\\": 2, @@ -2615,22 +2895,22 @@ exports[`select2 1`] = ` \\"class\\": \\"select2-search\\" }, \\"childNodes\\": [], - \\"id\\": 77 + \\"id\\": 38 } }, { - \\"parentId\\": 77, + \\"parentId\\": 38, \\"previousId\\": null, \\"nextId\\": 40, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 78 + \\"id\\": 39 } }, { - \\"parentId\\": 77, - \\"previousId\\": 78, + \\"parentId\\": 38, + \\"previousId\\": 39, \\"nextId\\": 41, \\"node\\": { \\"type\\": 2, @@ -2640,22 +2920,22 @@ exports[`select2 1`] = ` \\"class\\": \\"select2-offscreen\\" }, \\"childNodes\\": [], - \\"id\\": 79 + \\"id\\": 40 } }, { - \\"parentId\\": 77, - \\"previousId\\": 79, + \\"parentId\\": 38, + \\"previousId\\": 40, \\"nextId\\": 42, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 80 + \\"id\\": 41 } }, { - \\"parentId\\": 77, - \\"previousId\\": 80, + \\"parentId\\": 38, + \\"previousId\\": 41, \\"nextId\\": 43, \\"node\\": { \\"type\\": 2, @@ -2676,32 +2956,32 @@ exports[`select2 1`] = ` \\"aria-activedescendant\\": \\"select2-result-label-2\\" }, \\"childNodes\\": [], - \\"id\\": 81 + \\"id\\": 42 } }, { - \\"parentId\\": 77, - \\"previousId\\": 81, + \\"parentId\\": 38, + \\"previousId\\": 42, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 82 + \\"id\\": 43 } }, { - \\"parentId\\": 75, - \\"previousId\\": 77, + \\"parentId\\": 36, + \\"previousId\\": 38, \\"nextId\\": 45, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\" \\", - \\"id\\": 83 + \\"id\\": 44 } }, { - \\"parentId\\": 75, - \\"previousId\\": 83, + \\"parentId\\": 36, + \\"previousId\\": 44, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2712,11 +2992,11 @@ exports[`select2 1`] = ` \\"id\\": \\"select2-results-1\\" }, \\"childNodes\\": [], - \\"id\\": 84 + \\"id\\": 45 } }, { - \\"parentId\\": 84, + \\"parentId\\": 45, \\"previousId\\": null, \\"nextId\\": -1, \\"node\\": { @@ -2727,11 +3007,11 @@ exports[`select2 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 85 + \\"id\\": 67 } }, { - \\"parentId\\": 85, + \\"parentId\\": 67, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { @@ -2743,11 +3023,11 @@ exports[`select2 1`] = ` \\"role\\": \\"option\\" }, \\"childNodes\\": [], - \\"id\\": 86 + \\"id\\": 68 } }, { - \\"parentId\\": 86, + \\"parentId\\": 68, \\"previousId\\": null, \\"nextId\\": -1, \\"node\\": { @@ -2757,22 +3037,22 @@ exports[`select2 1`] = ` \\"class\\": \\"select2-match\\" }, \\"childNodes\\": [], - \\"id\\": 87 + \\"id\\": 69 } }, { - \\"parentId\\": 86, - \\"previousId\\": 87, + \\"parentId\\": 68, + \\"previousId\\": 69, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"A\\", - \\"id\\": 88 + \\"id\\": 70 } }, { - \\"parentId\\": 84, - \\"previousId\\": 85, + \\"parentId\\": 45, + \\"previousId\\": 67, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -2782,11 +3062,11 @@ exports[`select2 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 89 + \\"id\\": 71 } }, { - \\"parentId\\": 89, + \\"parentId\\": 71, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { @@ -2798,11 +3078,11 @@ exports[`select2 1`] = ` \\"role\\": \\"option\\" }, \\"childNodes\\": [], - \\"id\\": 90 + \\"id\\": 72 } }, { - \\"parentId\\": 90, + \\"parentId\\": 72, \\"previousId\\": null, \\"nextId\\": -1, \\"node\\": { @@ -2812,23 +3092,23 @@ exports[`select2 1`] = ` \\"class\\": \\"select2-match\\" }, \\"childNodes\\": [], - \\"id\\": 91 + \\"id\\": 73 } }, { - \\"parentId\\": 90, - \\"previousId\\": 91, + \\"parentId\\": 72, + \\"previousId\\": 73, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"B\\", - \\"id\\": 92 + \\"id\\": 74 } }, { \\"parentId\\": 18, \\"previousId\\": 66, - \\"nextId\\": 75, + \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"div\\", @@ -2838,7 +3118,7 @@ exports[`select2 1`] = ` \\"style\\": \\"\\" }, \\"childNodes\\": [], - \\"id\\": 93 + \\"id\\": 75 } }, { @@ -2848,7 +3128,7 @@ exports[`select2 1`] = ` \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", - \\"id\\": 94 + \\"id\\": 76 } } ] @@ -2859,7 +3139,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 93 + \\"id\\": 75 } }, { @@ -2868,7 +3148,7 @@ exports[`select2 1`] = ` \\"source\\": 5, \\"text\\": \\"\\", \\"isChecked\\": false, - \\"id\\": 81 + \\"id\\": 42 } }, { @@ -2885,7 +3165,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 1, - \\"id\\": 93 + \\"id\\": 75 } }, { @@ -2893,7 +3173,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 6, - \\"id\\": 81 + \\"id\\": 42 } }, { @@ -2911,13 +3191,13 @@ exports[`select2 1`] = ` \\"texts\\": [], \\"attributes\\": [ { - \\"id\\": 95, + \\"id\\": 75, \\"attributes\\": { \\"style\\": \\"display: none;\\" } }, { - \\"id\\": 75, + \\"id\\": 36, \\"attributes\\": { \\"id\\": null } @@ -2935,7 +3215,7 @@ exports[`select2 1`] = ` } }, { - \\"id\\": 81, + \\"id\\": 42, \\"attributes\\": { \\"class\\": \\"select2-input\\" } @@ -2944,22 +3224,22 @@ exports[`select2 1`] = ` \\"removes\\": [ { \\"parentId\\": 18, - \\"id\\": 93 + \\"id\\": 75 }, { - \\"parentId\\": 84, - \\"id\\": 85 + \\"parentId\\": 45, + \\"id\\": 67 }, { - \\"parentId\\": 84, - \\"id\\": 89 + \\"parentId\\": 45, + \\"id\\": 71 } ], \\"adds\\": [ { \\"parentId\\": 18, \\"previousId\\": 66, - \\"nextId\\": 75, + \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"div\\", @@ -2969,7 +3249,7 @@ exports[`select2 1`] = ` \\"style\\": \\"display: none;\\" }, \\"childNodes\\": [], - \\"id\\": 95 + \\"id\\": 75 } } ] @@ -2980,7 +3260,7 @@ exports[`select2 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 67 + \\"id\\": 26 } } ]" diff --git a/test/__snapshots__/record.test.ts.snap b/test/__snapshots__/record.test.ts.snap index f23f0c8d..22b67cdd 100644 --- a/test/__snapshots__/record.test.ts.snap +++ b/test/__snapshots__/record.test.ts.snap @@ -167,7 +167,7 @@ exports[`async-checkout 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n \\", - \\"id\\": 6 + \\"id\\": 7 }, { \\"type\\": 2, @@ -182,13 +182,13 @@ exports[`async-checkout 1`] = ` { \\"type\\": 3, \\"textContent\\": \\"test\\", - \\"id\\": 9 + \\"id\\": 10 } ], - \\"id\\": 8 + \\"id\\": 9 } ], - \\"id\\": 7 + \\"id\\": 8 } ], \\"id\\": 4 @@ -213,31 +213,31 @@ exports[`async-checkout 1`] = ` \\"attributes\\": [], \\"removes\\": [ { - \\"parentId\\": 7, - \\"id\\": 8 + \\"parentId\\": 8, + \\"id\\": 9 } ], \\"adds\\": [ { \\"parentId\\": 4, - \\"previousId\\": 7, + \\"previousId\\": 8, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": {}, \\"childNodes\\": [], - \\"id\\": 10 + \\"id\\": 9 } }, { - \\"parentId\\": 10, + \\"parentId\\": 9, \\"previousId\\": null, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"test\\", - \\"id\\": 11 + \\"id\\": 10 } } ] diff --git a/test/integration.test.ts b/test/integration.test.ts index 8501e400..9eb59776 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -161,7 +161,7 @@ describe('record integration tests', function(this: ISuite) { assertSnapshot(snapshots, __filename, 'block'); }); - it('should record DOM node movement', async () => { + it('should record DOM node movement 1', async () => { const page: puppeteer.Page = await this.browser.newPage(); await page.goto('about:blank'); await page.setContent(getHtml.call(this, 'move-node.html')); @@ -176,6 +176,21 @@ describe('record integration tests', function(this: ISuite) { div.appendChild(span); }); const snapshots = await page.evaluate('window.snapshots'); - assertSnapshot(snapshots, __filename, 'move-node'); + assertSnapshot(snapshots, __filename, 'move-node-1'); + }); + + it('should record DOM node movement 2', async () => { + const page: puppeteer.Page = await this.browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'move-node.html')); + + await page.evaluate(() => { + const div = document.createElement('div'); + const span = document.querySelector('span')!; + document.body.appendChild(div); + div.appendChild(span); + }); + const snapshots = await page.evaluate('window.snapshots'); + assertSnapshot(snapshots, __filename, 'move-node-2'); }); });