improve rrdom robustness (#1091)
This commit is contained in:
@@ -1,15 +1,29 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { getDefaultSN, RRDocument, RRMediaElement } from '../src';
|
||||
import { createOrGetNode, diff, ReplayerHandler } from '../src/diff';
|
||||
import * as path from 'path';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import {
|
||||
NodeType as RRNodeType,
|
||||
serializedNodeWithId,
|
||||
createMirror,
|
||||
Mirror,
|
||||
Mirror as NodeMirror,
|
||||
} from 'rrweb-snapshot';
|
||||
import type { IRRNode } from '../src/document';
|
||||
import {
|
||||
buildFromDom,
|
||||
getDefaultSN,
|
||||
Mirror as RRNodeMirror,
|
||||
RRDocument,
|
||||
RRMediaElement,
|
||||
} from '../src';
|
||||
import {
|
||||
createOrGetNode,
|
||||
diff,
|
||||
ReplayerHandler,
|
||||
nodeMatching,
|
||||
sameNodeType,
|
||||
} from '../src/diff';
|
||||
import type { IRRElement, IRRNode } from '../src/document';
|
||||
import { Replayer } from 'rrweb';
|
||||
import type {
|
||||
eventWithTime,
|
||||
@@ -18,6 +32,7 @@ import type {
|
||||
styleSheetRuleData,
|
||||
} from '@rrweb/types';
|
||||
import { EventType, IncrementalSource } from '@rrweb/types';
|
||||
import { compileTSCode } from './utils';
|
||||
|
||||
const elementSn = {
|
||||
type: RRNodeType.Element,
|
||||
@@ -45,7 +60,7 @@ type RRNode = IRRNode;
|
||||
function createTree(
|
||||
treeNode: ElementType,
|
||||
rrDocument?: RRDocument,
|
||||
mirror: Mirror = createMirror(),
|
||||
mirror: NodeMirror = createMirror(),
|
||||
): Node | RRNode {
|
||||
type TNode = typeof rrDocument extends RRDocument ? RRNode : Node;
|
||||
let root: TNode;
|
||||
@@ -87,7 +102,7 @@ function shuffle(list: number[]) {
|
||||
}
|
||||
|
||||
describe('diff algorithm for rrdom', () => {
|
||||
let mirror: Mirror;
|
||||
let mirror: NodeMirror;
|
||||
let replayer: ReplayerHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -98,7 +113,9 @@ describe('diff algorithm for rrdom', () => {
|
||||
applyInput: () => {},
|
||||
applyScroll: () => {},
|
||||
applyStyleSheetMutation: () => {},
|
||||
afterAppend: () => {},
|
||||
};
|
||||
document.write('<!DOCTYPE html><html><head></head><body></body></html>');
|
||||
});
|
||||
|
||||
describe('diff single node', () => {
|
||||
@@ -117,7 +134,7 @@ describe('diff algorithm for rrdom', () => {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
replayer.applyScroll = jest.fn();
|
||||
const applyScrollFn = jest.spyOn(replayer, 'applyScroll');
|
||||
diff(document, rrNode, replayer);
|
||||
expect(document.childNodes.length).toEqual(1);
|
||||
expect(document.childNodes[0]).toBeInstanceOf(DocumentType);
|
||||
@@ -126,7 +143,24 @@ describe('diff algorithm for rrdom', () => {
|
||||
'-//W3C//DTD XHTML 1.0 Transitional//EN',
|
||||
);
|
||||
expect(document.doctype?.systemId).toEqual('');
|
||||
expect(replayer.applyScroll).toBeCalledTimes(1);
|
||||
expect(applyScrollFn).toHaveBeenCalledTimes(1);
|
||||
applyScrollFn.mockRestore();
|
||||
});
|
||||
|
||||
it('should apply scroll data on an element', () => {
|
||||
const element = document.createElement('div');
|
||||
const rrDocument = new RRDocument();
|
||||
const rrNode = rrDocument.createElement('div');
|
||||
rrNode.scrollData = {
|
||||
source: IncrementalSource.Scroll,
|
||||
id: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
const applyScrollFn = jest.spyOn(replayer, 'applyScroll');
|
||||
diff(element, rrNode, replayer);
|
||||
expect(applyScrollFn).toHaveBeenCalledTimes(1);
|
||||
applyScrollFn.mockRestore();
|
||||
});
|
||||
|
||||
it('should apply input data on an input element', () => {
|
||||
@@ -247,6 +281,29 @@ describe('diff algorithm for rrdom', () => {
|
||||
expect(element.paused).toEqual(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should diff a node with different node type', () => {
|
||||
// When the diff target has a different node type.
|
||||
let parentNode: Node = document.createElement('div');
|
||||
let unreliableNode: Node = document.createTextNode('');
|
||||
parentNode.appendChild(unreliableNode);
|
||||
const rrNode = new RRDocument().createElement('li');
|
||||
diff(unreliableNode, rrNode, replayer);
|
||||
expect(parentNode.childNodes.length).toEqual(1);
|
||||
expect(parentNode.childNodes[0]).toBeInstanceOf(HTMLElement);
|
||||
expect((parentNode.childNodes[0] as HTMLElement).tagName).toEqual('LI');
|
||||
|
||||
// When the diff target has the same node type but with different tagName.
|
||||
parentNode = document.createElement('div');
|
||||
unreliableNode = document.createElement('span');
|
||||
parentNode.appendChild(unreliableNode);
|
||||
diff(unreliableNode, rrNode, replayer);
|
||||
expect((parentNode.childNodes[0] as HTMLElement).tagName).toEqual('LI');
|
||||
|
||||
// When the diff target is a node without parentNode.
|
||||
unreliableNode = document.createComment('');
|
||||
diff(unreliableNode, rrNode, replayer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diff properties', () => {
|
||||
@@ -1001,6 +1058,67 @@ describe('diff algorithm for rrdom', () => {
|
||||
newElementsIds,
|
||||
);
|
||||
});
|
||||
|
||||
it('should diff children with unreliable Mirror', () => {
|
||||
const parentNode = createTree(
|
||||
{
|
||||
tagName: 'div',
|
||||
id: 0,
|
||||
children: [],
|
||||
},
|
||||
undefined,
|
||||
mirror,
|
||||
) as Node;
|
||||
// Construct unreliable Mirror data.
|
||||
const unreliableChild = document.createTextNode('');
|
||||
const unreliableSN = {
|
||||
id: 1,
|
||||
textContent: '',
|
||||
type: RRNodeType.Text,
|
||||
} as serializedNodeWithId;
|
||||
mirror.add(unreliableChild, unreliableSN);
|
||||
parentNode.appendChild(unreliableChild);
|
||||
createTree(
|
||||
{
|
||||
tagName: 'div',
|
||||
id: 2,
|
||||
children: [],
|
||||
},
|
||||
undefined,
|
||||
mirror,
|
||||
);
|
||||
|
||||
const rrParentNode = createTree(
|
||||
{
|
||||
tagName: 'div',
|
||||
id: 0,
|
||||
children: [1].map((c) => ({
|
||||
tagName: 'span',
|
||||
id: c,
|
||||
children: [2].map((c1) => ({
|
||||
tagName: 'li',
|
||||
id: c1,
|
||||
})),
|
||||
})),
|
||||
},
|
||||
new RRDocument(),
|
||||
) as RRNode;
|
||||
const id = 'correctElement';
|
||||
(rrParentNode.childNodes[0] as IRRElement).setAttribute('id', id);
|
||||
diff(parentNode, rrParentNode, replayer);
|
||||
|
||||
expect(parentNode.childNodes.length).toEqual(1);
|
||||
expect(parentNode.childNodes[0]).toBeInstanceOf(HTMLElement);
|
||||
|
||||
const spanChild = parentNode.childNodes[0] as HTMLElement;
|
||||
expect(spanChild.tagName).toEqual('SPAN');
|
||||
expect(spanChild.id).toEqual(id);
|
||||
expect(spanChild.childNodes.length).toEqual(1);
|
||||
expect(spanChild.childNodes[0]).toBeInstanceOf(HTMLElement);
|
||||
|
||||
const liChild = spanChild.childNodes[0] as HTMLElement;
|
||||
expect(liChild.tagName).toEqual('LI');
|
||||
});
|
||||
});
|
||||
|
||||
describe('diff shadow dom', () => {
|
||||
@@ -1040,6 +1158,8 @@ describe('diff algorithm for rrdom', () => {
|
||||
});
|
||||
|
||||
describe('diff iframe elements', () => {
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
it('should add an element to the contentDocument of an iframe element', () => {
|
||||
document.write('<html></html>');
|
||||
const node = document.createElement('iframe');
|
||||
@@ -1191,6 +1311,306 @@ describe('diff algorithm for rrdom', () => {
|
||||
expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE);
|
||||
expect(mirror.getId(element)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('should remove children from document before adding new nodes 4', () => {
|
||||
/**
|
||||
* This case aims to test whether the diff function can remove all the old doctype and html element from the document before adding new doctype and html element.
|
||||
* If not, the diff function will throw errors or warnings.
|
||||
*/
|
||||
// Mock the original console.warn function to make the test fail once console.warn is called.
|
||||
const warn = jest.spyOn(global.console, 'warn');
|
||||
|
||||
document.write('<!DOCTYPE html><html><body></body></html>');
|
||||
const rrdom = new RRDocument();
|
||||
/**
|
||||
* Make the structure of document and RRDom look like this:
|
||||
* -2 Document
|
||||
* -3 DocumentType
|
||||
* -4 HTML
|
||||
* -5 HEAD
|
||||
* -6 BODY
|
||||
*/
|
||||
buildFromDom(document, mirror, rrdom);
|
||||
expect(mirror.getId(document)).toBe(-2);
|
||||
expect(mirror.getId(document.body)).toBe(-6);
|
||||
expect(rrdom.mirror.getId(rrdom)).toBe(-2);
|
||||
expect(rrdom.mirror.getId(rrdom.body)).toBe(-6);
|
||||
|
||||
rrdom.childNodes = [];
|
||||
/**
|
||||
* Rebuild the rrdom and make it looks like this:
|
||||
* -7 RRDocument
|
||||
* -8 RRDocumentType
|
||||
* -9 HTML
|
||||
* -10 HEAD
|
||||
* -11 BODY
|
||||
*/
|
||||
buildFromDom(document, undefined, rrdom);
|
||||
// Keep the ids of real document unchanged.
|
||||
expect(mirror.getId(document)).toBe(-2);
|
||||
expect(mirror.getId(document.body)).toBe(-6);
|
||||
|
||||
expect(rrdom.mirror.getId(rrdom)).toBe(-7);
|
||||
expect(rrdom.mirror.getId(rrdom.body)).toBe(-11);
|
||||
|
||||
// Diff the document with the new rrdom.
|
||||
diff(document, rrdom, replayer);
|
||||
// Check that warn was not called (fail on warning)
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
|
||||
// Check that the old nodes are removed from the NodeMirror.
|
||||
[-2, -3, -4, -5, -6].forEach((id) =>
|
||||
expect(mirror.getNode(id)).toBeNull(),
|
||||
);
|
||||
expect(mirror.getId(document)).toBe(-7);
|
||||
expect(mirror.getId(document.doctype)).toBe(-8);
|
||||
expect(mirror.getId(document.documentElement)).toBe(-9);
|
||||
expect(mirror.getId(document.head)).toBe(-10);
|
||||
expect(mirror.getId(document.body)).toBe(-11);
|
||||
|
||||
warn.mockRestore();
|
||||
});
|
||||
|
||||
it('selectors should be case-sensitive for matching in iframe dom', async () => {
|
||||
/**
|
||||
* If the selector match is case insensitive, it will cause some CSS style problems in the replayer.
|
||||
* This test result executed in JSDom is different from that in real browser so we use puppeteer as test environment.
|
||||
*/
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
|
||||
try {
|
||||
const code = await compileTSCode(
|
||||
path.resolve(__dirname, '../src/index.ts'),
|
||||
);
|
||||
await page.evaluate(code);
|
||||
|
||||
const className = 'case-sensitive';
|
||||
// To show the selector match pattern (case sensitive) in normal dom.
|
||||
const caseInsensitiveInNormalDom = await page.evaluate((className) => {
|
||||
document.write(
|
||||
'<!DOCTYPE html><html><body><iframe></iframe></body></html>',
|
||||
);
|
||||
const htmlEl = document.documentElement;
|
||||
htmlEl.className = className.toLowerCase();
|
||||
return htmlEl.matches(`.${className.toUpperCase()}`);
|
||||
}, className);
|
||||
expect(caseInsensitiveInNormalDom).toBeFalsy();
|
||||
|
||||
// To show the selector match pattern (case insensitive) in auto mounted iframe dom.
|
||||
const caseInsensitiveInDefaultIFrameDom = await page.evaluate(
|
||||
(className) => {
|
||||
const iframeEl = document.querySelector('iframe');
|
||||
const htmlEl = iframeEl?.contentDocument?.documentElement;
|
||||
if (htmlEl) {
|
||||
htmlEl.className = className.toLowerCase();
|
||||
return htmlEl.matches(`.${className.toUpperCase()}`);
|
||||
}
|
||||
},
|
||||
className,
|
||||
);
|
||||
expect(caseInsensitiveInDefaultIFrameDom).toBeTruthy();
|
||||
|
||||
const iframeElId = 3,
|
||||
iframeDomId = 4,
|
||||
htmlElId = 5;
|
||||
const result = await page.evaluate(`
|
||||
const iframeEl = document.querySelector('iframe');
|
||||
|
||||
// Construct a virtual dom tree.
|
||||
const rrDocument = new rrdom.RRDocument();
|
||||
const rrIframeEl = rrDocument.createElement('iframe');
|
||||
rrDocument.mirror.add(rrIframeEl, rrdom.getDefaultSN(rrIframeEl, ${iframeElId}));
|
||||
rrDocument.appendChild(rrIframeEl);
|
||||
rrDocument.mirror.add(
|
||||
rrIframeEl.contentDocument,
|
||||
rrdom.getDefaultSN(rrIframeEl.contentDocument, ${iframeDomId}),
|
||||
);
|
||||
const rrDocType = rrDocument.createDocumentType('html', '', '');
|
||||
rrIframeEl.contentDocument.appendChild(rrDocType);
|
||||
const rrHtmlEl = rrDocument.createElement('html');
|
||||
rrDocument.mirror.add(rrHtmlEl, rrdom.getDefaultSN(rrHtmlEl, ${htmlElId}));
|
||||
rrIframeEl.contentDocument.appendChild(rrHtmlEl);
|
||||
|
||||
const replayer = {
|
||||
mirror: rrdom.createMirror(),
|
||||
applyCanvas: () => {},
|
||||
applyInput: () => {},
|
||||
applyScroll: () => {},
|
||||
applyStyleSheetMutation: () => {},
|
||||
};
|
||||
rrdom.diff(iframeEl, rrIframeEl, replayer);
|
||||
|
||||
iframeEl.contentDocument.documentElement.className =
|
||||
'${className.toLowerCase()}';
|
||||
iframeEl.contentDocument.childNodes.length === 2 &&
|
||||
replayer.mirror.getId(iframeEl.contentDocument.documentElement) === ${htmlElId} &&
|
||||
// To test whether the selector match of the updated iframe document is case sensitive or not.
|
||||
!iframeEl.contentDocument.documentElement.matches(
|
||||
'.${className.toUpperCase()}',
|
||||
);
|
||||
`);
|
||||
// IFrame document has two children, mirror id of documentElement is ${htmlElId}, and selectors should be case-sensitive for matching in iframe dom (consistent with the normal dom).
|
||||
expect(result).toBeTruthy();
|
||||
} finally {
|
||||
await page.close();
|
||||
await browser.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterAppend callback', () => {
|
||||
it('should call afterAppend callback', () => {
|
||||
const afterAppendFn = jest.spyOn(replayer, 'afterAppend');
|
||||
const node = createTree(
|
||||
{
|
||||
tagName: 'div',
|
||||
id: 1,
|
||||
},
|
||||
undefined,
|
||||
mirror,
|
||||
) as Node;
|
||||
|
||||
const rrdom = new RRDocument();
|
||||
const rrNode = createTree(
|
||||
{
|
||||
tagName: 'div',
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
tagName: 'span',
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
rrdom,
|
||||
) as RRNode;
|
||||
diff(node, rrNode, replayer);
|
||||
expect(afterAppendFn).toHaveBeenCalledTimes(1);
|
||||
expect(afterAppendFn).toHaveBeenCalledWith(node.childNodes[0], 2);
|
||||
afterAppendFn.mockRestore();
|
||||
});
|
||||
|
||||
it('should diff without afterAppend callback', () => {
|
||||
replayer.afterAppend = undefined;
|
||||
const rrdom = buildFromDom(document);
|
||||
document.open();
|
||||
diff(document, rrdom, replayer);
|
||||
replayer.afterAppend = () => {};
|
||||
});
|
||||
|
||||
it('should call afterAppend callback in the post traversal order', () => {
|
||||
const afterAppendFn = jest.spyOn(replayer, 'afterAppend');
|
||||
document.open();
|
||||
|
||||
const rrdom = new RRDocument();
|
||||
rrdom.mirror.add(rrdom, getDefaultSN(rrdom, 1));
|
||||
const rrNode = createTree(
|
||||
{
|
||||
tagName: 'html',
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
tagName: 'head',
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
tagName: 'body',
|
||||
id: 3,
|
||||
children: [
|
||||
{
|
||||
tagName: 'span',
|
||||
id: 4,
|
||||
children: [
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 5,
|
||||
},
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tagName: 'p',
|
||||
id: 7,
|
||||
},
|
||||
{
|
||||
tagName: 'p',
|
||||
id: 8,
|
||||
children: [
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 9,
|
||||
},
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
rrdom,
|
||||
) as RRNode;
|
||||
diff(document, rrNode, replayer);
|
||||
|
||||
expect(afterAppendFn).toHaveBeenCalledTimes(10);
|
||||
// the correct traversal order
|
||||
[2, 5, 6, 4, 7, 9, 10, 8, 3, 1].forEach((id, index) => {
|
||||
expect((mirror.getNode(id) as HTMLElement).tagName).toEqual(
|
||||
(rrdom.mirror.getNode(id) as IRRElement).tagName,
|
||||
);
|
||||
expect(afterAppendFn).toHaveBeenNthCalledWith(
|
||||
index + 1,
|
||||
mirror.getNode(id),
|
||||
id,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only call afterAppend for newly created nodes', () => {
|
||||
const afterAppendFn = jest.spyOn(replayer, 'afterAppend');
|
||||
const rrdom = buildFromDom(document, replayer.mirror) as RRDocument;
|
||||
|
||||
// Append 3 nodes to rrdom.
|
||||
const rrNode = createTree(
|
||||
{
|
||||
tagName: 'span',
|
||||
id: 1,
|
||||
children: [
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
tagName: 'li',
|
||||
id: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
rrdom,
|
||||
) as RRNode;
|
||||
rrdom.body?.appendChild(rrNode);
|
||||
diff(document, rrdom, replayer);
|
||||
expect(afterAppendFn).toHaveBeenCalledTimes(3);
|
||||
// Should only call afterAppend for 3 newly appended nodes.
|
||||
[2, 3, 1].forEach((id, index) => {
|
||||
expect((mirror.getNode(id) as HTMLElement).tagName).toEqual(
|
||||
(rrdom.mirror.getNode(id) as IRRElement).tagName,
|
||||
);
|
||||
expect(afterAppendFn).toHaveBeenNthCalledWith(
|
||||
index + 1,
|
||||
mirror.getNode(id),
|
||||
id,
|
||||
);
|
||||
});
|
||||
afterAppendFn.mockClear();
|
||||
});
|
||||
});
|
||||
|
||||
describe('create or get a Node', () => {
|
||||
@@ -1431,4 +1851,116 @@ describe('diff algorithm for rrdom', () => {
|
||||
).toEqual('a {color: blue;}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('test sameNodeType function', () => {
|
||||
const rrdom = new RRDocument();
|
||||
it('should return true when two elements have same tagNames', () => {
|
||||
const div1 = document.createElement('div');
|
||||
const div2 = rrdom.createElement('div');
|
||||
expect(sameNodeType(div1, div2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when two elements have different tagNames', () => {
|
||||
const div1 = document.createElement('div');
|
||||
const div2 = rrdom.createElement('span');
|
||||
expect(sameNodeType(div1, div2)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false when two nodes have the same node type', () => {
|
||||
let node1: Node = new Document();
|
||||
let node2: IRRNode = new RRDocument();
|
||||
expect(sameNodeType(node1, node2)).toBeTruthy();
|
||||
|
||||
node1 = document.implementation.createDocumentType('html', '', '');
|
||||
node2 = rrdom.createDocumentType('', '', '');
|
||||
expect(sameNodeType(node1, node2)).toBeTruthy();
|
||||
|
||||
node1 = document.createTextNode('node1');
|
||||
node2 = rrdom.createTextNode('node2');
|
||||
expect(sameNodeType(node1, node2)).toBeTruthy();
|
||||
|
||||
node1 = document.createComment('node1');
|
||||
node2 = rrdom.createComment('node2');
|
||||
expect(sameNodeType(node1, node2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when two nodes have different node types', () => {
|
||||
let node1: Node = new Document();
|
||||
let node2: IRRNode = rrdom.createDocumentType('', '', '');
|
||||
expect(sameNodeType(node1, node2)).toBeFalsy();
|
||||
|
||||
node1 = document.implementation.createDocumentType('html', '', '');
|
||||
node2 = new RRDocument();
|
||||
expect(sameNodeType(node1, node2)).toBeFalsy();
|
||||
|
||||
node1 = document.createTextNode('node1');
|
||||
node2 = rrdom.createComment('node2');
|
||||
expect(sameNodeType(node1, node2)).toBeFalsy();
|
||||
|
||||
node1 = document.createComment('node1');
|
||||
node2 = rrdom.createTextNode('node2');
|
||||
expect(sameNodeType(node1, node2)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('test nodeMatching function', () => {
|
||||
const rrdom = new RRDocument();
|
||||
const NodeMirror = createMirror();
|
||||
const rrdomMirror = new RRNodeMirror();
|
||||
beforeEach(() => {
|
||||
NodeMirror.reset();
|
||||
rrdomMirror.reset();
|
||||
});
|
||||
|
||||
it('should return false when two nodes have different Ids', () => {
|
||||
const node1 = document.createElement('div');
|
||||
const node2 = rrdom.createElement('div');
|
||||
NodeMirror.add(node1, getDefaultSN(node2, 1));
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 2));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false when two nodes have same Ids but different node types', () => {
|
||||
// Compare an element with a comment node
|
||||
let node1: Node = document.createElement('div');
|
||||
NodeMirror.add(node1, getDefaultSN(rrdom.createElement('div'), 1));
|
||||
let node2: IRRNode = rrdom.createComment('test');
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
|
||||
// Compare an element node with a text node
|
||||
node2 = rrdom.createTextNode('');
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
|
||||
// Compare a document with a text node
|
||||
node1 = new Document();
|
||||
NodeMirror.add(node1, getDefaultSN(rrdom, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
|
||||
// Compare a document with a document type node
|
||||
node2 = rrdom.createDocumentType('', '', '');
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should compare two elements', () => {
|
||||
// Compare two elements with different tagNames
|
||||
let node1 = document.createElement('div');
|
||||
let node2 = rrdom.createElement('span');
|
||||
NodeMirror.add(node1, getDefaultSN(rrdom.createElement('div'), 1));
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
|
||||
// Compare two elements with same tagNames but different attributes
|
||||
node2 = rrdom.createElement('div');
|
||||
node2.setAttribute('class', 'test');
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 1));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeTruthy();
|
||||
|
||||
// Should return false when two elements have same tagNames and attributes but different children
|
||||
rrdomMirror.add(node2, getDefaultSN(node2, 2));
|
||||
expect(nodeMatching(node1, node2, NodeMirror, rrdomMirror)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user