import {Event, VirtualDocument} from './VirtualDocument.mjs'; describe('VirtualDocument', () => { const doc = new VirtualDocument(); describe('createElement', () => { it('creates elements which conform to the DOM API', () => { const o = doc.createElement('div'); expect(o.ownerDocument).toEqual(doc); expect(o.tagName).toEqual('div'); expect(o.namespaceURI).toEqual(''); expect(o.parentNode).toEqual(null); expect(o.childNodes.length).toEqual(0); }); it('claims all elements are always connected', () => { const o = doc.createElement('div'); expect(o.isConnected).toEqual(true); }); }); describe('appendChild', () => { it('adds a child to the element', () => { const o = doc.createElement('div'); o.appendChild(doc.createElement('span')); expect(o.childNodes.length).toEqual(1); expect(o.childNodes[0].tagName).toEqual('span'); }); it('removes the child from its old parent', () => { const o = doc.createElement('div'); const oldParent = doc.createElement('div'); const child = doc.createElement('span'); oldParent.appendChild(child); o.appendChild(child); expect(oldParent.childNodes.length).toEqual(0); }); it('rejects loops', () => { const o1 = doc.createElement('div'); const o2 = doc.createElement('div'); const o3 = doc.createElement('div'); o1.appendChild(o2); o2.appendChild(o3); expect(() => o3.appendChild(o1)).toThrow(); }); }); describe('removeChild', () => { it('removes a child from the element', () => { const o = doc.createElement('div'); const child = doc.createElement('span'); o.appendChild(child); o.removeChild(child); expect(o.childNodes.length).toEqual(0); }); it('rejects nodes which are not children', () => { const o = doc.createElement('div'); const child = doc.createElement('span'); expect(() => o.removeChild(child)).toThrow(); }); }); describe('firstChild', () => { it('returns the first child of the node', () => { const o = doc.createElement('div'); o.appendChild(doc.createElement('a')); o.appendChild(doc.createElement('b')); expect(o.firstChild.tagName).toEqual('a'); }); it('returns null if there are no children', () => { const o = doc.createElement('div'); expect(o.firstChild).toEqual(null); }); }); describe('lastChild', () => { it('returns the last child of the node', () => { const o = doc.createElement('div'); o.appendChild(doc.createElement('a')); o.appendChild(doc.createElement('b')); expect(o.lastChild.tagName).toEqual('b'); }); it('returns null if there are no children', () => { const o = doc.createElement('div'); expect(o.lastChild).toEqual(null); }); }); describe('contains', () => { it('returns true if the given node is within the current node', () => { const o = doc.createElement('div'); const child = doc.createElement('div'); o.appendChild(child); expect(o.contains(child)).toEqual(true); }); it('performs a deep check', () => { const o = doc.createElement('div'); const middle = doc.createElement('div'); const child = doc.createElement('div'); o.appendChild(middle); middle.appendChild(child); expect(o.contains(child)).toEqual(true); }); it('returns true if the nodes are the same', () => { const o = doc.createElement('div'); expect(o.contains(o)).toEqual(true); }); it('returns false if the node is not within the current node', () => { const o = doc.createElement('div'); const o2 = doc.createElement('div'); expect(o.contains(o2)).toEqual(false); }); }); describe('textContent', () => { it('replaces the content of the element', () => { const o = doc.createElement('div'); o.appendChild(doc.createElement('span')); o.textContent = 'foo'; expect(o.innerHTML).toEqual('foo'); }); it('returns the text content of all child nodes', () => { const o = doc.createElement('div'); const child = doc.createElement('span'); o.appendChild(doc.createTextNode('abc')); o.appendChild(child); child.appendChild(doc.createTextNode('def')); o.appendChild(doc.createTextNode('ghi')); expect(o.textContent).toEqual('abcdefghi'); }); }); describe('attributes', () => { it('keeps a key/value map of attributes', () => { const o = doc.createElement('div'); o.setAttribute('foo', 'bar'); o.setAttribute('zig', 'zag'); o.setAttribute('foo', 'baz'); expect(o.getAttribute('foo')).toEqual('baz'); expect(o.getAttribute('zig')).toEqual('zag'); expect(o.getAttribute('nope')).not.toBeDefined(); }); }); describe('events', () => { let o = null; let called = null; let fn = null; beforeEach(() => { o = doc.createElement('div'); called = 0; fn = () => { ++ called; }; }); it('stores and triggers event listeners', () => { o.addEventListener('foo', fn); o.dispatchEvent(new Event('foo')); expect(called).toEqual(1); }); it('removes listeners when removeEventListener is called', () => { o.addEventListener('foo', fn); o.removeEventListener('foo', fn); o.dispatchEvent(new Event('foo')); expect(called).toEqual(0); }); it('stores multiple event listeners', () => { const fn2 = () => { called += 10; }; o.addEventListener('foo', fn); o.addEventListener('foo', fn2); o.dispatchEvent(new Event('foo')); expect(called).toEqual(11); }); it('invokes listeners according to their type', () => { o.addEventListener('foo', fn); o.dispatchEvent(new Event('bar')); expect(called).toEqual(0); }); }); describe('outerHTML', () => { it('returns the tag in HTML form', () => { const o = doc.createElement('div'); expect(o.outerHTML).toEqual('
'); }); it('includes attributes', () => { const o = doc.createElement('div'); o.setAttribute('foo', 'bar'); o.setAttribute('zig', 'zag'); expect(o.outerHTML).toEqual('
'); }); it('escapes attributes', () => { const o = doc.createElement('div'); o.setAttribute('foo', 'b&a"r'); expect(o.outerHTML).toEqual('
'); }); it('includes all children', () => { const o = doc.createElement('div'); const child = doc.createElement('span'); o.appendChild(doc.createTextNode('abc')); o.appendChild(child); child.appendChild(doc.createTextNode('def')); o.appendChild(doc.createTextNode('ghi')); expect(o.outerHTML).toEqual('
abcdefghi
'); }); it('escapes text content', () => { const o = doc.createElement('div'); o.appendChild(doc.createTextNode('ac')); expect(o.outerHTML).toEqual('
a<b>c
'); }); }); });