replace script tag with noscript and inline the states of form field components
This commit is contained in:
@@ -11,11 +11,20 @@ function buildNode(n: serializedNodeWithId): Node | null {
|
||||
n.systemId,
|
||||
);
|
||||
case NodeType.Element:
|
||||
const node = document.createElement(n.tagName);
|
||||
const tagName = n.tagName === 'script' ? 'noscript' : n.tagName;
|
||||
const node = document.createElement(tagName);
|
||||
for (const name in n.attributes) {
|
||||
if (n.attributes.hasOwnProperty(name)) {
|
||||
let value = n.attributes[name];
|
||||
value = typeof value === 'boolean' ? '' : value;
|
||||
// textarea hack
|
||||
if (n.tagName === 'textarea' && name === 'value') {
|
||||
const child = document.createTextNode(value);
|
||||
node.appendChild(child);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
node.setAttribute(name, n.attributes[name]);
|
||||
node.setAttribute(name, value);
|
||||
} catch (error) {
|
||||
// skip invalid attribute
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ function genId(): number {
|
||||
return _id++;
|
||||
}
|
||||
|
||||
function resetId() {
|
||||
_id = 1;
|
||||
}
|
||||
|
||||
function serializeNode(n: Node): serializedNode | false {
|
||||
switch (n.nodeType) {
|
||||
case n.DOCUMENT_NODE:
|
||||
@@ -31,6 +35,28 @@ function serializeNode(n: Node): serializedNode | false {
|
||||
for (const { name, value } of Array.from((n as HTMLElement).attributes)) {
|
||||
attributes[name] = value;
|
||||
}
|
||||
if (
|
||||
tagName === 'input' ||
|
||||
tagName === 'textarea' ||
|
||||
tagName === 'select'
|
||||
) {
|
||||
const value = (n as HTMLInputElement | HTMLTextAreaElement).value;
|
||||
if (
|
||||
attributes.type !== 'radio' &&
|
||||
attributes.type !== 'checkbox' &&
|
||||
value
|
||||
) {
|
||||
attributes.value = value;
|
||||
} else if ((n as HTMLInputElement).checked) {
|
||||
attributes.checked = (n as HTMLInputElement).checked;
|
||||
}
|
||||
}
|
||||
if (tagName === 'option') {
|
||||
const selectValue = (n as HTMLOptionElement).parentElement;
|
||||
if (attributes.value === (selectValue as HTMLSelectElement).value) {
|
||||
attributes.selected = (n as HTMLOptionElement).selected;
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: NodeType.Element,
|
||||
tagName,
|
||||
@@ -65,7 +91,7 @@ function serializeNode(n: Node): serializedNode | false {
|
||||
}
|
||||
}
|
||||
|
||||
function snapshot(n: Node): serializedNodeWithId | null {
|
||||
function _snapshot(n: Node): serializedNodeWithId | null {
|
||||
const _serializedNode = serializeNode(n);
|
||||
if (!_serializedNode) {
|
||||
// TODO: dev only
|
||||
@@ -80,10 +106,15 @@ function snapshot(n: Node): serializedNodeWithId | null {
|
||||
serializedNode.type === NodeType.Element
|
||||
) {
|
||||
for (const childN of Array.from(n.childNodes)) {
|
||||
serializedNode.childNodes.push(snapshot(childN));
|
||||
serializedNode.childNodes.push(_snapshot(childN));
|
||||
}
|
||||
}
|
||||
return serializedNode;
|
||||
}
|
||||
|
||||
function snapshot(n: Node): serializedNodeWithId | null {
|
||||
resetId();
|
||||
return _snapshot(n);
|
||||
}
|
||||
|
||||
export default snapshot;
|
||||
|
||||
@@ -20,7 +20,7 @@ export type documentTypeNode = {
|
||||
};
|
||||
|
||||
export type attributes = {
|
||||
[key: string]: string;
|
||||
[key: string]: string | boolean;
|
||||
};
|
||||
export type elementNode = {
|
||||
type: NodeType.Element;
|
||||
|
||||
18
test/html/with-script.html
Normal file
18
test/html/with-script.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>with script</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="a.js"></script>
|
||||
<script>
|
||||
console.log(1)
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'mocha';
|
||||
import mochaDom = require('mocha-jsdom');
|
||||
import { expect } from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { snapshot, rebuild } from '../src';
|
||||
|
||||
const htmlFolder = path.join(__dirname, 'html');
|
||||
const htmls = fs.readdirSync(htmlFolder).map(filePath => {
|
||||
return {
|
||||
filePath,
|
||||
content: fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('integration tests', () => {
|
||||
mochaDom({ url: 'http://localhost' });
|
||||
|
||||
it('will snapshot document type', () => {
|
||||
const raw = '<html></html>';
|
||||
const dom = new JSDOM(raw);
|
||||
const snap = snapshot(dom.window.document);
|
||||
expect(snap).to.deep.equal({
|
||||
type: 0,
|
||||
childNodes: [
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'html',
|
||||
attributes: {},
|
||||
childNodes: [
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'head',
|
||||
attributes: {},
|
||||
childNodes: [],
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'body',
|
||||
attributes: {},
|
||||
childNodes: [],
|
||||
id: 4,
|
||||
},
|
||||
],
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
id: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('will not throw error with invalid attribute', () => {
|
||||
const raw = `<html foo='bar' ''></html>`;
|
||||
const dom = new JSDOM(raw);
|
||||
expect(() => rebuild(snapshot(dom.window.document))).not.to.throw();
|
||||
});
|
||||
|
||||
for (const html of htmls) {
|
||||
it('[html file]:' + html.filePath, () => {
|
||||
const dom = new JSDOM(html.content);
|
||||
const snap = snapshot(dom.window.document);
|
||||
const rebuildDom = rebuild(snap);
|
||||
expect((rebuildDom as Document).documentElement.outerHTML).to.equal(
|
||||
dom.window.document.documentElement.outerHTML,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
132
test/integration.ts
Normal file
132
test/integration.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'mocha';
|
||||
import mochaDom = require('mocha-jsdom');
|
||||
import { expect } from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { snapshot, rebuild } from '../src';
|
||||
|
||||
const htmlFolder = path.join(__dirname, 'html');
|
||||
const htmls = fs.readdirSync(htmlFolder).map(filePath => {
|
||||
return {
|
||||
filePath,
|
||||
content: fs.readFileSync(path.resolve(htmlFolder, filePath), 'utf-8'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('integration tests', () => {
|
||||
mochaDom({ url: 'http://localhost' });
|
||||
|
||||
for (const html of htmls) {
|
||||
it('[html file]:' + html.filePath, () => {
|
||||
const dom = new JSDOM(html.content);
|
||||
const snap = snapshot(dom.window.document);
|
||||
const rebuildDom = rebuild(snap);
|
||||
const htmlStr = dom.window.document.documentElement.outerHTML
|
||||
.replace(/<script>(.|\n)*?<\/script>/g, '<script></script>')
|
||||
.replace(/<script(.*?)>/g, '<noscript$1>')
|
||||
.replace(/<\/script>/g, '</noscript>');
|
||||
expect((rebuildDom as Document).documentElement.outerHTML).to.equal(
|
||||
htmlStr,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it('will snapshot document type', () => {
|
||||
const raw = '<html></html>';
|
||||
const dom = new JSDOM(raw);
|
||||
const snap = snapshot(dom.window.document);
|
||||
expect(snap).to.deep.equal({
|
||||
type: 0,
|
||||
childNodes: [
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'html',
|
||||
attributes: {},
|
||||
childNodes: [
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'head',
|
||||
attributes: {},
|
||||
childNodes: [],
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
tagName: 'body',
|
||||
attributes: {},
|
||||
childNodes: [],
|
||||
id: 4,
|
||||
},
|
||||
],
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
id: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('will not throw error with invalid attribute', () => {
|
||||
const raw = `<html foo='bar' ''></html>`;
|
||||
const dom = new JSDOM(raw);
|
||||
expect(() => rebuild(snapshot(dom.window.document))).not.to.throw();
|
||||
});
|
||||
|
||||
it('will inline text input value', () => {
|
||||
const raw = '<input type="text">';
|
||||
const dom = new JSDOM(raw);
|
||||
dom.window.document.querySelector('input').value = '1';
|
||||
const rebuildDom = rebuild(snapshot(dom.window.document));
|
||||
expect((rebuildDom as Document).querySelector('input').outerHTML).to.equal(
|
||||
'<input type="text" value="1">',
|
||||
);
|
||||
});
|
||||
|
||||
it('will inline radio input value', () => {
|
||||
const raw = '<input type="radio">';
|
||||
const dom = new JSDOM(raw);
|
||||
dom.window.document.querySelector('input').checked = true;
|
||||
const rebuildDom = rebuild(snapshot(dom.window.document));
|
||||
expect((rebuildDom as Document).querySelector('input').outerHTML).to.equal(
|
||||
'<input type="radio" checked="">',
|
||||
);
|
||||
});
|
||||
|
||||
it('will inline checkbox input value', () => {
|
||||
const raw = '<input type="checkbox">';
|
||||
const dom = new JSDOM(raw);
|
||||
dom.window.document.querySelector('input').checked = true;
|
||||
const rebuildDom = rebuild(snapshot(dom.window.document));
|
||||
expect((rebuildDom as Document).querySelector('input').outerHTML).to.equal(
|
||||
'<input type="checkbox" checked="">',
|
||||
);
|
||||
});
|
||||
|
||||
it('will inline textarea value into text node', () => {
|
||||
const raw = '<textarea name="" id="" cols="30" rows="10"></textarea>';
|
||||
const dom = new JSDOM(raw);
|
||||
dom.window.document.querySelector('textarea').value = '1234';
|
||||
const rebuildDom = rebuild(snapshot(dom.window.document));
|
||||
expect(
|
||||
(rebuildDom as Document).querySelector('textarea').outerHTML,
|
||||
).to.equal('<textarea name="" id="" cols="30" rows="10">1234</textarea>');
|
||||
});
|
||||
|
||||
it('will inline options state', () => {
|
||||
const raw = `
|
||||
<select name="" id="">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
`;
|
||||
const dom = new JSDOM(raw);
|
||||
dom.window.document.querySelector('select').value = '2';
|
||||
const rebuildDom = rebuild(snapshot(dom.window.document));
|
||||
expect((rebuildDom as Document).querySelector('select').outerHTML).to.equal(
|
||||
`<select name="" id="" value="2">
|
||||
<option value="1">1</option>
|
||||
<option value="2" selected="">2</option>
|
||||
</select>`,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user