Add change reporting to the fake DOM.
This commit is contained in:
parent
3230a3ef5a
commit
66149ab830
185
packages/projector/spec/document.nondom.spec.js
Normal file
185
packages/projector/spec/document.nondom.spec.js
Normal file
@ -0,0 +1,185 @@
|
||||
const { CreateDocument } = require('../lib/document.js');
|
||||
|
||||
describe('Document', () => {
|
||||
let doc;
|
||||
const changes = [];
|
||||
|
||||
beforeEach(() => {
|
||||
doc = new CreateDocument(change => changes.push(change));
|
||||
changes.splice(0, changes.length);
|
||||
});
|
||||
|
||||
describe('element insertion', () => {
|
||||
it('to do nothing on creation', () => {
|
||||
const el = doc.createElement('div');
|
||||
expect(changes).toEqual([]);
|
||||
});
|
||||
|
||||
it('to produce a patch upon append', () => {
|
||||
const el = doc.createElement('div');
|
||||
expect(changes).toEqual([]);
|
||||
doc.body.appendChild(el);
|
||||
expect(changes).toEqual([
|
||||
[
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'DIV',
|
||||
p: [],
|
||||
i: doc.body._id + 1,
|
||||
c: []
|
||||
},
|
||||
undefined
|
||||
]
|
||||
]);
|
||||
});
|
||||
|
||||
it('to produce a patch upon append with a full tree', () => {
|
||||
const el1 = doc.createElement('div');
|
||||
const el2 = doc.createElement('span');
|
||||
expect(changes).toEqual([]);
|
||||
el1.appendChild(el2);
|
||||
expect(changes).toEqual([]);
|
||||
doc.body.appendChild(el1);
|
||||
expect(changes).toEqual([
|
||||
[
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'DIV',
|
||||
p: [],
|
||||
i: doc.body._id + 1,
|
||||
c: [
|
||||
{
|
||||
t: 1,
|
||||
n: 'SPAN',
|
||||
p: [],
|
||||
i: doc.body._id + 2,
|
||||
c: []
|
||||
}
|
||||
]
|
||||
},
|
||||
undefined
|
||||
]
|
||||
]);
|
||||
expect(changes.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('to produce a patch upon insert', () => {
|
||||
const el1 = doc.createElement('div');
|
||||
const el2 = doc.createElement('span');
|
||||
expect(changes).toEqual([]);
|
||||
doc.body.appendChild(el1);
|
||||
expect(changes).toEqual([
|
||||
[
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'DIV',
|
||||
p: [],
|
||||
i: doc.body._id + 1,
|
||||
c: []
|
||||
},
|
||||
undefined
|
||||
]
|
||||
]);
|
||||
doc.body.insertBefore(el2, el1);
|
||||
expect(changes.length).toEqual(2);
|
||||
expect(changes[1]).toEqual([
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'SPAN',
|
||||
p: [],
|
||||
i: doc.body._id + 2,
|
||||
c: []
|
||||
},
|
||||
doc.body._id + 1
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('element removal', () => {
|
||||
it('to propagate no changes after removal', () => {
|
||||
const el1 = doc.createElement('div');
|
||||
const el2 = doc.createElement('span');
|
||||
expect(changes).toEqual([]);
|
||||
el1.appendChild(el2);
|
||||
expect(changes).toEqual([]);
|
||||
doc.body.appendChild(el1);
|
||||
expect(changes).toEqual([
|
||||
[
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'DIV',
|
||||
p: [],
|
||||
i: doc.body._id + 1,
|
||||
c: [
|
||||
{
|
||||
t: 1,
|
||||
n: 'SPAN',
|
||||
p: [],
|
||||
i: doc.body._id + 2,
|
||||
c: []
|
||||
}
|
||||
]
|
||||
},
|
||||
undefined
|
||||
]
|
||||
]);
|
||||
expect(changes.length).toEqual(1);
|
||||
doc.body.removeChild(el1);
|
||||
expect(changes.length).toEqual(2);
|
||||
expect(changes[1]).toEqual([2, doc.body._id + 1]);
|
||||
el1.removeChild(el2);
|
||||
expect(changes.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('element attribute changes', () => {
|
||||
it('to propagate when attached', () => {
|
||||
const el1 = doc.createElement('div');
|
||||
const el2 = doc.createElement('span');
|
||||
el1.setAttribute('class', '1');
|
||||
expect(changes).toEqual([]);
|
||||
el1.appendChild(el2);
|
||||
expect(changes).toEqual([]);
|
||||
doc.body.appendChild(el1);
|
||||
expect(changes).toEqual([
|
||||
[
|
||||
0,
|
||||
doc.body._id,
|
||||
{
|
||||
t: 1,
|
||||
n: 'DIV',
|
||||
p: [{ ns: null, name: 'class', value: '1' }],
|
||||
i: doc.body._id + 1,
|
||||
c: [
|
||||
{
|
||||
t: 1,
|
||||
n: 'SPAN',
|
||||
p: [],
|
||||
i: doc.body._id + 2,
|
||||
c: []
|
||||
}
|
||||
]
|
||||
},
|
||||
undefined
|
||||
]
|
||||
]);
|
||||
expect(changes.length).toEqual(1);
|
||||
el2.setAttribute('class', '2');
|
||||
expect(changes.length).toEqual(2);
|
||||
expect(changes[1]).toEqual([1, doc.body._id + 2, { ns: null, name: 'class', value: '2' }]);
|
||||
el2.removeAttribute('class');
|
||||
expect(changes.length).toEqual(3);
|
||||
expect(changes[2]).toEqual([1, doc.body._id + 2, { name: 'class', value: null }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"spec_dir": "spec",
|
||||
"spec_files": ["**/*[sS]pec.js"],
|
||||
"spec_files": ["**/*.nondom.[sS]pec.js"],
|
||||
"helpers": ["helpers/**/*.js"],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
|
||||
@ -15,6 +15,8 @@ const NODE_TYPES = {
|
||||
};
|
||||
*/
|
||||
|
||||
let COUNTER = 0;
|
||||
|
||||
function toLower(str) {
|
||||
return String(str).toLowerCase();
|
||||
}
|
||||
@ -38,7 +40,7 @@ function createAttributeFilter(ns, name) {
|
||||
/** Create a minimally viable DOM Document
|
||||
* @returns {Document} document
|
||||
*/
|
||||
export function CreateDocument() {
|
||||
export function CreateDocument(onChange) {
|
||||
function isElement(node) {
|
||||
return node.nodeType === 1;
|
||||
}
|
||||
@ -48,6 +50,8 @@ export function CreateDocument() {
|
||||
this.nodeType = nodeType;
|
||||
this.nodeName = nodeName;
|
||||
this.childNodes = [];
|
||||
this._id = COUNTER++;
|
||||
this._attached = false;
|
||||
}
|
||||
get nextSibling() {
|
||||
let p = this.parentNode;
|
||||
@ -63,14 +67,35 @@ export function CreateDocument() {
|
||||
get lastChild() {
|
||||
return this.childNodes[this.childNodes.length - 1];
|
||||
}
|
||||
_attach(attach) {
|
||||
this._attached = attach;
|
||||
this.childNodes.forEach(n => n._attach(attach));
|
||||
}
|
||||
_toDataObj() {
|
||||
return {
|
||||
t: this.nodeType,
|
||||
n: this.nodeName,
|
||||
p: {},
|
||||
i: this._id,
|
||||
c: this.childNodes.map(n => n._toDataObj())
|
||||
};
|
||||
}
|
||||
appendChild(child) {
|
||||
this.insertBefore(child);
|
||||
}
|
||||
insertBefore(child, ref) {
|
||||
child.remove();
|
||||
child.parentNode = this;
|
||||
if (!ref) this.childNodes.push(child);
|
||||
else splice(this.childNodes, ref, child);
|
||||
if (!ref) {
|
||||
this.childNodes.push(child);
|
||||
} else {
|
||||
splice(this.childNodes, ref, child);
|
||||
}
|
||||
|
||||
if (this._attached) {
|
||||
child._attach(true);
|
||||
onChange([0, this._id, child._toDataObj(), ref && ref._id]);
|
||||
}
|
||||
}
|
||||
replaceChild(child, ref) {
|
||||
if (ref.parentNode === this) {
|
||||
@ -80,9 +105,15 @@ export function CreateDocument() {
|
||||
}
|
||||
removeChild(child) {
|
||||
splice(this.childNodes, child);
|
||||
if (this._attached) {
|
||||
child._attach(false);
|
||||
onChange([2, child._id]);
|
||||
}
|
||||
}
|
||||
remove() {
|
||||
if (this.parentNode) this.parentNode.removeChild(this);
|
||||
if (this.parentNode) {
|
||||
this.parentNode.removeChild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +122,15 @@ export function CreateDocument() {
|
||||
super(3, '#text'); // TEXT_NODE
|
||||
this.nodeValue = text;
|
||||
}
|
||||
_toDataObj() {
|
||||
return {
|
||||
t: this.nodeType,
|
||||
n: this.nodeName,
|
||||
p: { textContent: this.nodeValue },
|
||||
i: this._id,
|
||||
c: []
|
||||
};
|
||||
}
|
||||
set textContent(text) {
|
||||
this.nodeValue = text;
|
||||
}
|
||||
@ -110,6 +150,16 @@ export function CreateDocument() {
|
||||
return this.childNodes.filter(isElement);
|
||||
}
|
||||
|
||||
_toDataObj() {
|
||||
return {
|
||||
t: this.nodeType,
|
||||
n: this.nodeName,
|
||||
p: this.attributes,
|
||||
i: this._id,
|
||||
c: this.childNodes.map(n => n._toDataObj())
|
||||
};
|
||||
}
|
||||
|
||||
setAttribute(key, value) {
|
||||
this.setAttributeNS(null, key, value);
|
||||
}
|
||||
@ -124,6 +174,9 @@ export function CreateDocument() {
|
||||
let attr = findWhere(this.attributes, createAttributeFilter(ns, name));
|
||||
if (!attr) this.attributes.push((attr = { ns, name }));
|
||||
attr.value = String(value);
|
||||
if (this._attached) {
|
||||
onChange([1, this._id, attr]);
|
||||
}
|
||||
}
|
||||
getAttributeNS(ns, name) {
|
||||
let attr = findWhere(this.attributes, createAttributeFilter(ns, name));
|
||||
@ -131,6 +184,9 @@ export function CreateDocument() {
|
||||
}
|
||||
removeAttributeNS(ns, name) {
|
||||
splice(this.attributes, createAttributeFilter(ns, name));
|
||||
if (this._attached) {
|
||||
onChange([1, this._id, { name: name, value: null }]);
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener(type, handler) {
|
||||
@ -158,6 +214,7 @@ export function CreateDocument() {
|
||||
class Document extends Element {
|
||||
constructor() {
|
||||
super(9, '#document'); // DOCUMENT_NODE
|
||||
this._attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export { Projector } from './projector.js';
|
||||
// export { Scanner } from './scanner.js';
|
||||
export { CreateDocument } from './document.js';
|
||||
|
||||
@ -45,7 +45,7 @@ export function Projector(domRoot) {
|
||||
}
|
||||
|
||||
function setAttributes(element, props) {
|
||||
Object.entries(props).forEach(([name, value]) => {
|
||||
props.forEach(({ name, value }) => {
|
||||
if (name in element) {
|
||||
if (name.startsWith('on')) {
|
||||
const eventName = name.substr(2);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user