Files
rrweb/packages/rrdom/test/virtual-dom.test.ts
Justin Halsall 5a85ddf013 Chore: Migrate build to vite (#1033)
* Chore: Add move most types from rrweb to @rrweb/types package

* Split off type imports

* Split off type import to its own line

* Get vite to generate type definitions

* Apply formatting changes

* noEmit not allowed in tsconfig, moved it to build step

* Migrate rrdom-nodejs build to vite

* Apply formatting changes

* Migrate rrweb-snapshot to vite

* Unify configs

* Chore: Migrate rrdom to vite

Turns out what we where doing by overwriting `public textContent: string | undefined` as a getter in a subclass is something that isn't allowed in typescript. Because we where using `// @ts-ignore`  to hide this error our bundler chose to allow the overwrite. Vite choses to disallow the overwrite making all subclasses' `textContent` undefined.
To mitigate this we're using an abstract class, which does allow sub classes to decide if they wan't to use getters or not.

* Chore: Migrate rrweb to vite WIP

* build:browser was removed (for now)

* BREAKING: moved rrweb-plugin-console to its own npm module

This removes console from rrweb-all.js

* Support cjs files in startServer

* Move canvas-webrtc plugin to its own package

* Chore: move sequential-id plugin to its own package

* Chore: Configure rrweb's vite bundling

* `Id` had lowercase `d` before, making it lowercase again

* Test: Move console tests to their own package

* remove unused utils from rrdom

* pull in latest version of master
something when wrong earlier when resolving merge conflicts, this should be correct

* Fix type casting issue in diff.ts

* Fix typo

* Fix duplicate entries in package.json and tsconfig.json

* Apply formatting changes

* Update dependencies in package.json files

* Update dependencies to use Vite 5.2.8 in package.json files

* Get tests passing for rrdom

`apply virtual style rules to node` tests need to be moved to rrweb to avoid circular dependencies

* Fix image loading issue in integration tests

* Move pack/unpack to its own @rrweb/packer module

* Get tests to work in rrdom-nodejs

* Port tests in rrweb-snapshot to vitest and fix them

* Fix tests for rrweb-plugin-console-record

* Add @rrweb/all package

* Fix publint and attw errors for rrdom and @rrweb/types

* Use shared vitest.config.ts in rrweb-snapshot package

* Fix publint and attw issues for rrweb-snapshot

* Export `ReplayPlugin` type directly from rrweb

* Fix publint and attw issues for packages

* Fix publint & attw issue.

I was bumping into this issue: 3729bc2a3c/docs/problems/NoResolution.md

And had to choose one of these three methods described here:
https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility
And I ended up going for the method described here:
1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)

The redirect method seemed the least invasive and most effective.

* Fix publint & attw issue.

I was bumping into this issue: 3729bc2a3c/docs/problems/NoResolution.md

And had to choose one of these three methods described here:
https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file#typescript-friendly-strategies-for-packagejson-subpath-exports-compatibility
And I ended up going for the method described here:
1ffe3425b0/examples/node_modules/package-json-redirects (package-json-redirects)

The redirect method seemed the least invasive and most effective.

* move some rrdom tests that require rrweb to rrweb package

* Use pre-jest 29 syntax for snapshotting

* get rrweb passing publint and attw

* const enum does not work with isolated modules flag

* Fix script tag type in webgl.test.ts.snap and update rrweb.umd.cjs path in webgl.test.ts

* Fix paths

* Move tests for console record plugin and fix bundle path

* Fix tests for rrweb

* pack integration tests were moved to @rrweb/all

* Update rrweb bundle path in test files

* Fix flaky scroll emit from test

* Migrate rrweb's tests over to vitest and make them pass

* Make sure benchmarks & updating tests work

* Remove jest from rrweb

* Fix paths

* always use rrweb's own cssom

* Update tsconfig.json for rrweb-plugin-sequential-id-record

Fixes this error:
Error: @rrweb/rrweb-plugin-sequential-id-record:prepublish: tsconfig.json(9,5): error TS6377: Cannot write file '/home/runner/work/rrweb/rrweb/tsconfig.tsbuildinfo' because it will overwrite '.tsbuildinfo' file generated by referenced project '/home/runner/work/rrweb/rrweb/packages/rrweb'

* Add tsbuildinfo config to extended tsconfig files

* Move rrdom over to vitest

* Apply formatting changes

* Update rrweb imports to use the new package structure

* extend rrweb-snapshot's tsconfig from monorepo base config

* extend @rrweb/types's tsconfig from monorepo base config

* extend rrdom's tsconfig from monorepo base config

* extend rrdom-nodejs's tsconfig from monorepo base config

* extend web-extension's tsconfig from monorepo base config

* unify tsconfigs

* Continue when tests fail

* Add stricter type checking

* Add check-types global command

* remove jest

* Remove unused code

* Add check-types command to build script

* Fix linting issues

* Add setup Chrome action for CI/CD workflow

* Update puppeteer version in package.json for rrweb

* Update Chrome setup in CI/CD workflow

* Update Chrome setup in CI/CD workflow

* Add Chrome setup and test cache location

* Update CI/CD workflow to test chrome cache location

* Add chrome installation step to CI/CD workflow

* Update Puppeteer configuration for headless testing

* Update dependencies and workflow configuration

* Use same version of chrome on CI as is run locally

* Use version of chrome that seems to work with rrdom tests

* Try using puppeteerrc to define chrome version

* Add .cache directory to .gitignore

* Move global flag to vitest config

* Update puppeteer version to 20.9.0

* Update console log messages in rrweb-plugin-console-record for new puppeteer version

* Remove redundant Chrome setup from CI/CD workflow

* Add minification and umd for all built files

* Update import paths for rrweb dist files

* Add @rrweb/replay and @rrweb/record

* Add script to lint packages

* Apply formatting changes

* exclude styles export from typescript package type checking

* WIP Move rrweb-player over to vite

* Apply formatting changes

* chore: Update rrweb plugin import paths

* Remove rollup from rrweb-player

* Fix typing issues

* Fix typing issues

* chore: Update rrweb-player to use vite for build process

* Apply formatting changes

* chore: Export Player class in rrweb-player/src/main.ts

Makes attw happy

* Apply formatting changes

* Gets wiped by yarn workspaces-to-typescript-project-references

* Add .eslintignore and .eslintrc.cjs files for rrweb-player package

* Apply formatting changes

* Update dependencies in rrweb-player/package.json

* Apply formatting changes

* chore: Update eslint configuration for rrweb-player package

* Apply formatting changes

* chore: Remove unused files from rrweb-player package

* Apply formatting changes

* chore: Update rrweb-player import path to use rrweb-player.cjs

* chore: Update addEventListener signature in rrweb-player

* Apply formatting changes

* Add .eslintignore and update .gitignore files for to root

* Apply formatting changes

* Update documentation

* Update @rrweb/types package description

* Apply formatting changes

* Update build and run commands in CONTRIBUTING.md

* Apply formatting changes

* Update package versions to 2.0.0-alpha.13

* Apply formatting changes

* Apply formatting changes

* Fix import statement in media/index.ts

* Apply formatting changes

* chore: Update .gitignore to exclude build and dist directories

* Apply formatting changes

* Apply formatting changes

* Migrate setTimeout to vitest

* Apply formatting changes

* Apply formatting changes

* Fix isNativeShadowDom function signature in utils.ts

* try out jsr

* Apply formatting changes

* Update package versions to 2.0.0-alpha.14

* Apply formatting changes

* Fix name of rrwebSnapshot object

* Apply formatting changes

* Remove unused lock files

* Apply formatting changes

* Update rrweb bundle path to use umd.cjs format

* Apply formatting changes

* Trigger tests to run again

* Rename snapshots for vitest

* Apply formatting changes

* Ping CI

* Apply formatting changes

* Ping CI

* Apply formatting changes

* Ignore files generated by svelte-kit for prettier

* Correct Player object
2026-04-01 12:00:00 +08:00

537 lines
19 KiB
TypeScript

/**
* @jest-environment jsdom
*/
import * as fs from 'fs';
import * as path from 'path';
import * as puppeteer from 'puppeteer';
import { vi } from 'vitest';
import { JSDOM } from 'jsdom';
import {
cdataNode,
commentNode,
documentNode,
documentTypeNode,
elementNode,
Mirror,
NodeType,
NodeType as RRNodeType,
textNode,
} from 'rrweb-snapshot';
import {
buildFromDom,
buildFromNode,
createMirror,
getDefaultSN,
RRCanvasElement,
RRDocument,
RRElement,
BaseRRNode as RRNode,
} from '../src';
const printRRDomCode = `
/**
* Print the RRDom as a string.
* @param rootNode the root node of the RRDom tree
* @returns printed string
*/
function printRRDom(rootNode, mirror) {
return walk(rootNode, mirror, '');
}
function walk(node, mirror, blankSpace) {
let printText = \`\${blankSpace}\${mirror.getId(node)} \${node.toString()}\n\`;
if(node instanceof rrdom.RRElement && node.shadowRoot)
printText += walk(node.shadowRoot, mirror, blankSpace + ' ');
for (const child of node.childNodes)
printText += walk(child, mirror, blankSpace + ' ');
if (node instanceof rrdom.RRIFrameElement)
printText += walk(node.contentDocument, mirror, blankSpace + ' ');
return printText;
}
`;
describe('RRDocument for browser environment', () => {
vi.setConfig({ testTimeout: 60_000 });
let mirror: Mirror;
beforeEach(() => {
mirror = new Mirror();
});
describe('create a RRNode from a real Node', () => {
it('should support quicksmode documents', () => {
// separate jsdom document as changes to the document would otherwise bleed into other tests
const dom = new JSDOM();
const document = dom.window.document;
expect(document.doctype).toBeNull(); // confirm compatMode is 'BackCompat' in JSDOM
const rrdom = new RRDocument();
let rrNode = buildFromNode(document, rrdom, mirror)!;
expect((rrNode as RRDocument).compatMode).toBe('BackCompat');
});
it('can patch serialized ID for an unserialized node', () => {
// build from document
expect(mirror.getMeta(document)).toBeNull();
const rrdom = new RRDocument();
let rrNode = buildFromNode(document, rrdom, mirror)!;
expect(mirror.getMeta(document)).toBeDefined();
expect(mirror.getId(document)).toEqual(-2);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document);
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
expect(rrNode).toBe(rrdom);
// build from document type
expect(mirror.getMeta(document.doctype!)).toBeNull();
rrNode = buildFromNode(document.doctype!, rrdom, mirror)!;
expect(mirror.getMeta(document.doctype!)).toBeDefined();
expect(mirror.getId(document.doctype)).toEqual(-3);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(
RRNodeType.DocumentType,
);
expect(rrdom.mirror.getId(rrNode)).toEqual(-3);
// build from element
expect(mirror.getMeta(document.documentElement)).toBeNull();
rrNode = buildFromNode(
document.documentElement as unknown as Node,
rrdom,
mirror,
)!;
expect(mirror.getMeta(document.documentElement)).toBeDefined();
expect(mirror.getId(document.documentElement)).toEqual(-4);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Element);
expect(rrdom.mirror.getId(rrNode)).toEqual(-4);
// build from text
const text = document.createTextNode('text');
expect(mirror.getMeta(text)).toBeNull();
rrNode = buildFromNode(text, rrdom, mirror)!;
expect(mirror.getMeta(text)).toBeDefined();
expect(mirror.getId(text)).toEqual(-5);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Text);
expect(rrdom.mirror.getId(rrNode)).toEqual(-5);
// build from comment
const comment = document.createComment('comment');
expect(mirror.getMeta(comment)).toBeNull();
rrNode = buildFromNode(comment, rrdom, mirror)!;
expect(mirror.getMeta(comment)).toBeDefined();
expect(mirror.getId(comment)).toEqual(-6);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Comment);
expect(rrdom.mirror.getId(rrNode)).toEqual(-6);
// build from CDATASection
const xmlDoc = new DOMParser().parseFromString(
'<xml></xml>',
'application/xml',
);
const cdata = 'Some <CDATA> data & then some';
var cdataSection = xmlDoc.createCDATASection(cdata);
expect(mirror.getMeta(cdataSection)).toBeNull();
expect(mirror.getMeta(cdataSection)).toBeNull();
rrNode = buildFromNode(cdataSection, rrdom, mirror)!;
expect(mirror.getMeta(cdataSection)).toBeDefined();
expect(mirror.getId(cdataSection)).toEqual(-7);
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.CDATA);
expect(rrdom.mirror.getId(rrNode)).toEqual(-7);
expect(rrNode.textContent).toEqual(cdata);
});
it('can record scroll position from HTMLElements', () => {
expect(document.body.scrollLeft).toEqual(0);
expect(document.body.scrollTop).toEqual(0);
const rrdom = new RRDocument();
let rrNode = buildFromNode(document.body, rrdom, mirror)!;
expect((rrNode as RRElement).scrollLeft).toBeUndefined();
expect((rrNode as RRElement).scrollTop).toBeUndefined();
document.body.scrollLeft = 100;
document.body.scrollTop = 200;
expect(document.body.scrollLeft).toEqual(100);
expect(document.body.scrollTop).toEqual(200);
rrNode = buildFromNode(document.body, rrdom, mirror)!;
expect((rrNode as RRElement).scrollLeft).toEqual(100);
expect((rrNode as RRElement).scrollTop).toEqual(200);
});
it('can build contentDocument from an iframe element', () => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
expect(iframe.contentDocument).not.toBeNull();
const rrdom = new RRDocument();
const RRIFrame = rrdom.createElement('iframe');
const rrNode = buildFromNode(
iframe.contentDocument!,
rrdom,
mirror,
RRIFrame,
)!;
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document);
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
expect(mirror.getId(iframe.contentDocument)).toEqual(-2);
expect(rrNode).toBe(RRIFrame.contentDocument);
});
it('can build from a shadow dom', () => {
const div = document.createElement('div');
div.attachShadow({ mode: 'open' });
expect(div.shadowRoot).toBeDefined();
const rrdom = new RRDocument();
const parentRRNode = rrdom.createElement('div');
const rrNode = buildFromNode(
div.shadowRoot!,
rrdom,
mirror,
parentRRNode,
)!;
expect(rrNode).not.toBeNull();
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
expect(mirror.getId(div.shadowRoot)).toEqual(-2);
expect(rrNode.RRNodeType).toEqual(RRNodeType.Element);
expect((rrNode as RRElement).tagName).toEqual('SHADOWROOT');
expect(rrNode).toBe(parentRRNode.shadowRoot);
});
});
describe('create a RRDocument from a html document', () => {
let browser: puppeteer.Browser;
let code: string;
let page: puppeteer.Page;
beforeAll(async () => {
browser = await puppeteer.launch();
code = fs.readFileSync(
path.resolve(__dirname, '../dist/rrdom.umd.cjs'),
'utf8',
);
});
afterAll(async () => {
await browser.close();
});
beforeEach(async () => {
page = await browser.newPage();
await page.goto('about:blank');
await page.evaluate(code + printRRDomCode);
});
afterEach(async () => {
await page.close();
});
it('can build from a common html', async () => {
await page.setContent(getHtml('main.html'));
const result = await page.evaluate(`
const doc = new rrdom.RRDocument();
rrdom.buildFromDom(document, undefined, doc);
printRRDom(doc, doc.mirror);
`);
expect(result).toMatchSnapshot();
});
it('can build from an iframe html ', async () => {
await page.setContent(getHtml('iframe.html'));
const result = await page.evaluate(`
const doc = new rrdom.RRDocument();
rrdom.buildFromDom(document, undefined, doc);
printRRDom(doc, doc.mirror);
`);
expect(result).toMatchSnapshot();
});
it('can build from a html containing nested shadow doms', async () => {
await page.setContent(getHtml('shadow-dom.html'));
const result = await page.evaluate(`
const doc = new rrdom.RRDocument();
rrdom.buildFromDom(document, undefined, doc);
printRRDom(doc, doc.mirror);
`);
expect(result).toMatchSnapshot();
});
it('can build from a xml page', async () => {
const result = await page.evaluate(`
var docu = new DOMParser().parseFromString('<xml></xml>', 'application/xml');
var cdata = docu.createCDATASection('Some <CDATA> data & then some');
docu.getElementsByTagName('xml')[0].appendChild(cdata);
// Displays: <xml><![CDATA[Some <CDATA> data & then some]]></xml>
const doc = new rrdom.RRDocument();
rrdom.buildFromDom(docu, undefined, doc);
printRRDom(doc, doc.mirror);
`);
expect(result).toMatchSnapshot();
});
});
describe('RRDocument build for virtual dom', () => {
it('can access a unique, decremented unserializedId every time', () => {
const node = new RRDocument();
for (let i = 2; i <= 100; i++) expect(node.unserializedId).toBe(-i);
});
it('can create a new RRDocument', () => {
const dom = new RRDocument();
const newDom = dom.createDocument('', '');
expect(newDom).toBeInstanceOf(RRDocument);
});
it('can create a new RRDocument receiving a mirror parameter', () => {
const mirror = createMirror();
const dom = new RRDocument(mirror);
const newDom = dom.createDocument('', '');
expect(newDom).toBeInstanceOf(RRDocument);
expect(dom.mirror).toBe(mirror);
});
it('can build a RRDocument from a real Dom', () => {
const result = buildFromDom(document, mirror);
expect(result.childNodes.length).toBe(2);
expect(result.documentElement).toBeDefined();
expect(result.head).toBeDefined();
expect(result.head!.tagName).toBe('HEAD');
expect(result.body).toBeDefined();
expect(result.body!.tagName).toBe('BODY');
});
it('can destroy a RRDocument tree', () => {
const dom = new RRDocument();
const node1 = dom.createDocumentType('', '', '');
dom.appendChild(node1);
dom.mirror.add(node1, {
id: 0,
type: NodeType.DocumentType,
name: '',
publicId: '',
systemId: '',
});
const node2 = dom.createElement('html');
dom.appendChild(node2);
dom.mirror.add(node1, {
id: 1,
type: NodeType.Document,
childNodes: [],
});
expect(dom.childNodes.length).toEqual(2);
expect(dom.mirror.has(0)).toBeTruthy();
expect(dom.mirror.has(1)).toBeTruthy();
dom.destroyTree();
expect(dom.childNodes.length).toEqual(0);
expect(dom.mirror.has(0)).toBeFalsy();
expect(dom.mirror.has(1)).toBeFalsy();
});
it('can close and open a RRDocument', () => {
const dom = new RRDocument();
const documentType = dom.createDocumentType('html', '', '');
dom.appendChild(documentType);
expect(dom.childNodes[0]).toBe(documentType);
expect(dom.unserializedId).toBe(-2);
expect(dom.unserializedId).toBe(-3);
expect(dom.close());
expect(dom.open());
expect(dom.childNodes.length).toEqual(0);
expect(dom.unserializedId).toBe(-2);
});
it('can execute a dummy getContext function in RRCanvasElement', () => {
const canvas = new RRCanvasElement('CANVAS');
expect(canvas.getContext).toBeDefined();
expect(canvas.getContext()).toBeNull();
});
describe('Mirror in the RRDocument', () => {
it('should have a mirror to store id and node', () => {
const dom = new RRDocument();
expect(dom.mirror).toBeDefined();
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
const node2 = dom.createTextNode('text');
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.getNode(0)).toBe(node1);
expect(dom.mirror.getNode(1)).toBe(node2);
expect(dom.mirror.getNode(2)).toBeNull();
expect(dom.mirror.getNode(-1)).toBeNull();
});
it('can get node id', () => {
const dom = new RRDocument();
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
expect(dom.mirror.getId(node1)).toEqual(0);
const node2 = dom.createTextNode('text');
expect(dom.mirror.getId(node2)).toEqual(-1);
expect(dom.mirror.getId(null as unknown as RRNode)).toEqual(-1);
});
it('has() should return whether the mirror has an ID', () => {
const dom = new RRDocument();
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
const node2 = dom.createTextNode('text');
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.has(0)).toBeTruthy();
expect(dom.mirror.has(1)).toBeTruthy();
expect(dom.mirror.has(2)).toBeFalsy();
expect(dom.mirror.has(-1)).toBeFalsy();
});
it('can remove node from the mirror', () => {
const dom = new RRDocument();
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
const node2 = dom.createTextNode('text');
dom.mirror.add(node2, getDefaultSN(node2, 1));
node1.appendChild(node2);
expect(dom.mirror.has(0)).toBeTruthy();
expect(dom.mirror.has(1)).toBeTruthy();
dom.mirror.removeNodeFromMap(node2);
expect(dom.mirror.has(0)).toBeTruthy();
expect(dom.mirror.has(1)).toBeFalsy();
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.has(1)).toBeTruthy();
// To remove node1 and its child node2 from the mirror.
dom.mirror.removeNodeFromMap(node1);
expect(dom.mirror.has(0)).toBeFalsy();
expect(dom.mirror.has(1)).toBeFalsy();
});
it('can reset the mirror', () => {
const dom = new RRDocument();
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
const node2 = dom.createTextNode('text');
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.has(0)).toBeTruthy();
expect(dom.mirror.has(1)).toBeTruthy();
dom.mirror.reset();
expect(dom.mirror.has(0)).toBeFalsy();
expect(dom.mirror.has(1)).toBeFalsy();
});
it('hasNode() should return whether the mirror has a node', () => {
const dom = new RRDocument();
const node1 = dom.createElement('div');
const node2 = dom.createTextNode('text');
expect(dom.mirror.hasNode(node1)).toBeFalsy();
dom.mirror.add(node1, getDefaultSN(node1, 0));
expect(dom.mirror.hasNode(node1)).toBeTruthy();
expect(dom.mirror.hasNode(node2)).toBeFalsy();
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.hasNode(node2)).toBeTruthy();
});
it('can get all IDs from the mirror', () => {
const dom = new RRDocument();
expect(dom.mirror.getIds().length).toBe(0);
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
const node2 = dom.createTextNode('text');
dom.mirror.add(node2, getDefaultSN(node2, 1));
expect(dom.mirror.getIds().length).toBe(2);
expect(dom.mirror.getIds()).toStrictEqual([0, 1]);
});
it('can replace nodes', () => {
const dom = new RRDocument();
expect(dom.mirror.getIds().length).toBe(0);
const node1 = dom.createElement('div');
dom.mirror.add(node1, getDefaultSN(node1, 0));
expect(dom.mirror.getNode(0)).toBe(node1);
const node2 = dom.createTextNode('text');
dom.mirror.replace(0, node2);
expect(dom.mirror.getNode(0)).toBe(node2);
});
});
});
describe('can get default SN value from a RRNode', () => {
const rrdom = new RRDocument();
it('can get from RRDocument', () => {
const node = rrdom;
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.Document);
expect((sn as documentNode).childNodes).toBeInstanceOf(Array);
});
it('can get from RRDocumentType', () => {
const name = 'name',
publicId = 'publicId',
systemId = 'systemId';
const node = rrdom.createDocumentType(name, publicId, systemId);
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.DocumentType);
expect((sn as documentTypeNode).name).toEqual(name);
expect((sn as documentTypeNode).publicId).toEqual(publicId);
expect((sn as documentTypeNode).systemId).toEqual(systemId);
});
it('can get from RRElement', () => {
const node = rrdom.createElement('div');
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.Element);
expect((sn as elementNode).tagName).toEqual('div');
expect((sn as elementNode).attributes).toBeDefined();
expect((sn as elementNode).childNodes).toBeInstanceOf(Array);
});
it('can get from RRText', () => {
const node = rrdom.createTextNode('text');
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.Text);
expect((sn as textNode).textContent).toEqual('text');
});
it('can get from RRComment', () => {
const node = rrdom.createComment('comment');
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.Comment);
expect((sn as commentNode).textContent).toEqual('comment');
});
it('can get from RRCDATASection', () => {
const node = rrdom.createCDATASection('data');
const sn = getDefaultSN(node, 1);
expect(sn).toBeDefined();
expect(sn.type).toEqual(RRNodeType.CDATA);
expect((sn as cdataNode).textContent).toEqual('');
});
});
});
function getHtml(fileName: string) {
const filePath = path.resolve(__dirname, `./html/${fileName}`);
return fs.readFileSync(filePath, 'utf8');
}