* create rrdom package * test(rrdom): add unit tests for polyfill.ts * fix(rrweb snapshot): type check errors Errors are caused by the declaration similarity of @types/mocha and @types/jest if we install both of them in the whole project. * Set tagNames to upper case by default This mirrors the `Element.tagName` implementation: ``` For DOM trees which represent HTML documents, the returned tag name is always in the canonical upper-case form. For example, tagName called on a <div> element returns "DIV". ``` https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName * Add workspace file * VSCode settings for rrdom tests * Add basic test for RRDocument * Only setup jest tests for rrdom * mock Node type and Event type for nodejs environment * test(rrdom): add snapshot for document.test.ts * fix issue of nwsapi import and add unit tests for rrdom * fix: querySelectorAll returns nothing when querying elements with ids and classNames * fix: error of unit test for Event polyfill Since Event class is built in nodejs after v15.0.0 * add a dummy implementation of canvas * add style element support * add unit test for style element Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
260 lines
8.8 KiB
TypeScript
260 lines
8.8 KiB
TypeScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { RRDocument, RRElement, RRStyleElement } from '../src/document-nodejs';
|
|
import { printRRDom } from './util';
|
|
|
|
describe('RRDocument for nodejs environment', () => {
|
|
describe('buildFromDom', () => {
|
|
it('should create an RRDocument from a html document', () => {
|
|
// setup document
|
|
document.write(getHtml('main.html'));
|
|
|
|
// create RRDocument from document
|
|
const rrdoc = new RRDocument();
|
|
rrdoc.buildFromDom(document);
|
|
expect(printRRDom(rrdoc)).toMatchSnapshot();
|
|
});
|
|
});
|
|
|
|
describe('RRDocument API', () => {
|
|
let rrdom: RRDocument;
|
|
beforeAll(() => {
|
|
// initialize rrdom
|
|
document.write(getHtml('main.html'));
|
|
rrdom = new RRDocument();
|
|
rrdom.buildFromDom(document);
|
|
});
|
|
|
|
it('get className', () => {
|
|
expect(rrdom.getElementsByTagName('DIV')[0].className).toEqual(
|
|
'blocks blocks1',
|
|
);
|
|
expect(rrdom.getElementsByTagName('DIV')[1].className).toEqual(
|
|
'blocks blocks1 :hover',
|
|
);
|
|
});
|
|
|
|
it('get id', () => {
|
|
expect(rrdom.getElementsByTagName('DIV')[0].id).toEqual('block1');
|
|
expect(rrdom.getElementsByTagName('DIV')[1].id).toEqual('block2');
|
|
expect(rrdom.getElementsByTagName('DIV')[2].id).toEqual('block3');
|
|
});
|
|
|
|
it('get attribute name', () => {
|
|
expect(
|
|
rrdom.getElementsByTagName('DIV')[0].getAttribute('class'),
|
|
).toEqual('blocks blocks1');
|
|
expect(
|
|
rrdom.getElementsByTagName('dIv')[0].getAttribute('cLaSs'),
|
|
).toEqual('blocks blocks1');
|
|
expect(rrdom.getElementsByTagName('DIV')[0].getAttribute('id')).toEqual(
|
|
'block1',
|
|
);
|
|
expect(rrdom.getElementsByTagName('div')[0].getAttribute('iD')).toEqual(
|
|
'block1',
|
|
);
|
|
expect(
|
|
rrdom.getElementsByTagName('p')[0].getAttribute('class'),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('get firstElementChild', () => {
|
|
expect(rrdom.firstElementChild).toBeDefined();
|
|
expect(rrdom.firstElementChild.tagName).toEqual('HTML');
|
|
|
|
const div1 = rrdom.getElementById('block1');
|
|
expect(div1).toBeDefined();
|
|
expect(div1!.firstElementChild).toBeDefined();
|
|
expect(div1!.firstElementChild!.id).toEqual('block2');
|
|
const div2 = div1!.firstElementChild;
|
|
expect(div2!.firstElementChild!.id).toEqual('block3');
|
|
});
|
|
|
|
it('get nextElementSibling', () => {
|
|
expect(rrdom.documentElement.firstElementChild).not.toBeNull();
|
|
expect(rrdom.documentElement.firstElementChild!.tagName).toEqual('HEAD');
|
|
expect(
|
|
rrdom.documentElement.firstElementChild!.nextElementSibling,
|
|
).not.toBeNull();
|
|
expect(
|
|
rrdom.documentElement.firstElementChild!.nextElementSibling!.tagName,
|
|
).toEqual('BODY');
|
|
expect(
|
|
rrdom.documentElement.firstElementChild!.nextElementSibling!
|
|
.nextElementSibling,
|
|
).toBeNull();
|
|
|
|
expect(rrdom.getElementsByTagName('h1').length).toEqual(2);
|
|
const element1 = rrdom.getElementsByTagName('h1')[0];
|
|
const element2 = rrdom.getElementsByTagName('h1')[1];
|
|
expect(element1.tagName).toEqual('H1');
|
|
expect(element2.tagName).toEqual('H1');
|
|
expect(element1.nextElementSibling).toEqual(element2);
|
|
expect(element2.nextElementSibling).not.toBeNull();
|
|
expect(element2.nextElementSibling!.id).toEqual('block1');
|
|
expect(element2.nextElementSibling!.nextElementSibling).toBeNull();
|
|
});
|
|
|
|
it('getElementsByTagName', () => {
|
|
for (let tagname of [
|
|
'HTML',
|
|
'BODY',
|
|
'HEAD',
|
|
'STYLE',
|
|
'META',
|
|
'TITLE',
|
|
'SCRIPT',
|
|
'LINK',
|
|
'DIV',
|
|
'H1',
|
|
'P',
|
|
'BUTTON',
|
|
'IMG',
|
|
'CANVAS',
|
|
]) {
|
|
const expectedResult = document.getElementsByTagName(tagname).length;
|
|
expect(rrdom.getElementsByTagName(tagname).length).toEqual(
|
|
expectedResult,
|
|
);
|
|
expect(
|
|
rrdom.getElementsByTagName(tagname.toLowerCase()).length,
|
|
).toEqual(expectedResult);
|
|
for (let node of rrdom.getElementsByTagName(tagname)) {
|
|
expect(node.tagName).toEqual(tagname);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('getElementsByClassName', () => {
|
|
for (let className of [
|
|
'blocks',
|
|
'blocks1',
|
|
':hover',
|
|
'blocks1 blocks',
|
|
'blocks blocks1',
|
|
':hover blocks1',
|
|
':hover blocks1 blocks',
|
|
':hover blocks1 block',
|
|
]) {
|
|
const msg = `queried class name: '${className}'`;
|
|
expect({
|
|
message: msg,
|
|
result: rrdom.getElementsByClassName(className).length,
|
|
}).toEqual({
|
|
message: msg,
|
|
result: document.getElementsByClassName(className).length,
|
|
});
|
|
}
|
|
});
|
|
|
|
it('getElementById', () => {
|
|
for (let elementId of ['block1', 'block2', 'block3']) {
|
|
expect(rrdom.getElementById(elementId)).not.toBeNull();
|
|
expect(rrdom.getElementById(elementId)!.id).toEqual(elementId);
|
|
}
|
|
for (let elementId of ['block', 'blocks', 'blocks1'])
|
|
expect(rrdom.getElementById(elementId)).toBeNull();
|
|
});
|
|
|
|
it('querySelectorAll querying tag name', () => {
|
|
expect(rrdom.querySelectorAll('H1')).toHaveLength(2);
|
|
expect(rrdom.querySelectorAll('H1')[0]).toBeInstanceOf(RRElement);
|
|
expect((rrdom.querySelectorAll('H1')[0] as RRElement).tagName).toEqual(
|
|
'H1',
|
|
);
|
|
expect(rrdom.querySelectorAll('H1')[1]).toBeInstanceOf(RRElement);
|
|
expect((rrdom.querySelectorAll('H1')[1] as RRElement).tagName).toEqual(
|
|
'H1',
|
|
);
|
|
});
|
|
|
|
it('querySelectorAll querying class name', () => {
|
|
for (let className of [
|
|
'.blocks',
|
|
'.blocks1',
|
|
'.\\:hover',
|
|
'.blocks1.blocks',
|
|
'.blocks.blocks1',
|
|
'.\\:hover.blocks1',
|
|
'.\\:hover.blocks1.blocks',
|
|
'.\\:hover.blocks1.block',
|
|
]) {
|
|
const msg = `queried class name: '${className}'`;
|
|
expect({
|
|
message: msg,
|
|
result: rrdom.querySelectorAll(className).length,
|
|
}).toEqual({
|
|
message: msg,
|
|
result: document.querySelectorAll(className).length,
|
|
});
|
|
}
|
|
for (let element of rrdom.querySelectorAll('.\\:hover')) {
|
|
expect(element).toBeInstanceOf(RRElement);
|
|
expect((element as RRElement).classList).toContain(':hover');
|
|
}
|
|
});
|
|
|
|
it('querySelectorAll querying id', () => {
|
|
for (let query of ['#block1', '#block2', '#block3']) {
|
|
expect(rrdom.querySelectorAll(query).length).toEqual(1);
|
|
const targetElement = rrdom.querySelectorAll(query)[0] as RRElement;
|
|
expect(targetElement.id).toEqual(query.substring(1, query.length));
|
|
}
|
|
for (let query of ['#block', '#blocks', '#block1#block2'])
|
|
expect(rrdom.querySelectorAll(query).length).toEqual(0);
|
|
});
|
|
|
|
it('querySelectorAll', () => {
|
|
expect(rrdom.querySelectorAll('link[rel="stylesheet"]').length).toEqual(
|
|
1,
|
|
);
|
|
const targetLink = rrdom.querySelectorAll(
|
|
'link[rel="stylesheet"]',
|
|
)[0] as RRElement;
|
|
expect(targetLink.tagName).toEqual('LINK');
|
|
expect(targetLink.getAttribute('rel')).toEqual('stylesheet');
|
|
|
|
expect(rrdom.querySelectorAll('.blocks#block1').length).toEqual(1);
|
|
expect(rrdom.querySelectorAll('.blocks#block3').length).toEqual(0);
|
|
});
|
|
|
|
it('style element', () => {
|
|
expect(rrdom.getElementsByTagName('style').length).not.toEqual(0);
|
|
expect(rrdom.getElementsByTagName('style')[0].tagName).toEqual('STYLE');
|
|
const styleElement = rrdom.getElementsByTagName(
|
|
'style',
|
|
)[0] as RRStyleElement;
|
|
expect(styleElement.sheet).toBeDefined();
|
|
expect(styleElement.sheet!.cssRules).toBeDefined();
|
|
expect(styleElement.sheet!.cssRules.length).toEqual(5);
|
|
const rules = styleElement.sheet!.cssRules;
|
|
expect(rules[0].cssText).toEqual(`h1 {color: 'black';}`);
|
|
expect(rules[1].cssText).toEqual(`.blocks {padding: 0;}`);
|
|
expect(rules[2].cssText).toEqual(`.blocks1 {margin: 0;}`);
|
|
expect(rules[3].cssText).toEqual(
|
|
`#block1 {width: 100px; height: 200px;}`,
|
|
);
|
|
expect(rules[4].cssText).toEqual(`@import url(main.css);`);
|
|
expect((rules[4] as CSSImportRule).href).toEqual('main.css');
|
|
|
|
expect(styleElement.sheet!.insertRule).toBeDefined();
|
|
const newRule = "p {color: 'black';}";
|
|
styleElement.sheet!.insertRule(newRule, 5);
|
|
expect(rules[5].cssText).toEqual(newRule);
|
|
|
|
expect(styleElement.sheet!.deleteRule).toBeDefined();
|
|
styleElement.sheet!.deleteRule(5);
|
|
expect(rules[5]).toBeUndefined();
|
|
expect(rules[4].cssText).toEqual(`@import url(main.css);`);
|
|
});
|
|
});
|
|
});
|
|
|
|
function getHtml(fileName: string) {
|
|
const filePath = path.resolve(__dirname, `./html/${fileName}`);
|
|
return fs.readFileSync(filePath, 'utf8');
|
|
}
|