dom-layer
Version:
Virtual DOM implementation.
542 lines (365 loc) • 13.9 kB
JavaScript
var h = require('../helper/h');
var Tag = require('../../src/node/tag');
var Tree = require('../../src/tree/tree');
describe("Tag", function() {
it("verifies the type value", function() {
expect(new Tag().type).toBe('Tag');
});
it("sets some defaults value on new instance", function() {
var tag = h();
expect(tag.tagName).toBe('div');
expect(tag.children).toEqual([]);
expect(tag.props).toBe(undefined);
expect(tag.attrs).toBe(undefined);
expect(tag.attrsNS).toBe(undefined);
expect(tag.events).toBe(undefined);
expect(tag.hooks).toBe(undefined);
expect(tag.data).toBe(undefined);
expect(tag.params).toBe(undefined);
expect(tag.element).toBe(undefined);
expect(tag.parent).toBe(undefined);
expect(tag.key).toBe(undefined);
expect(tag.namespace).toBe(null);
expect(tag.is).toBe(null);
});
it("sets the `tagName` property", function() {
var tag = h({ tagName: 'span' });
expect(tag.tagName).toBe('span');
});
it("sets the `children` property", function() {
var tag = h({}, ['child1', 'child2']);
expect(tag.children.length).toBe(2);
expect(tag.children[0].data).toBe('child1');
expect(tag.children[1].data).toBe('child2');
});
it("sets the `props` property", function() {
var props = {};
var tag = h({ props: props });
expect(tag.props).toBe(props);
});
it("sets the `attrs` property", function() {
var attrs = {};
var tag = h({ attrs: attrs });
expect(tag.attrs).toBe(attrs);
});
it("sets the `attrsNS` property", function() {
var attrsNS = {};
var tag = h({ attrsNS: attrsNS });
expect(tag.attrsNS).toBe(attrsNS);
});
it("sets the `events` property", function() {
var events = {};
var tag = h({ events: events });
expect(tag.events).toBe(events);
});
it("sets the `hooks` property", function() {
var hooks = {};
var tag = h({ hooks: hooks });
expect(tag.hooks).toBe(hooks);
});
it("sets the `data` property", function() {
var data = {};
var tag = h({ data: data });
expect(tag.data).toBe(data);
});
it("sets the `params` property", function() {
var params = {};
var tag = h({ params: params });
expect(tag.params).toBe(params);
});
it("sets the `key` property", function() {
var tag = h({ key: '10' });
expect(tag.key).toBe('10');
});
it("sets the namespace", function() {
var tag = h({ tagName: 'circle', attrs : { xmlns: 'http://www.w3.org/2000/svg' } });
expect(tag.namespace).toBe('http://www.w3.org/2000/svg');
});
it("sets the type extension", function() {
var tag = h({ tagName: 'button', attrs : { is: 'mega-button' } });
expect(tag.is).toBe('mega-button');
});
it("sets SVG as default namespace for <svg>", function() {
var tag = h({ tagName: 'svg' });
var element = tag.render();
expect(tag.namespace).toBe('http://www.w3.org/2000/svg');
expect(element.namespaceURI).toBe('http://www.w3.org/2000/svg');
});
it("sets MathML as default namespace for <math>", function() {
var tag = h({ tagName: 'math' });
var element = tag.render();
expect(tag.namespace).toBe('http://www.w3.org/1998/Math/MathML');
expect(element.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML');
});
it("uses the parent namespace by default", function() {
var circle = h({ tagName: 'circle' });
var tag = h({ tagName: 'svg' }, [circle]);
var element = tag.render();
expect(circle.namespace).toBe('http://www.w3.org/2000/svg');
expect(element.childNodes[0].namespaceURI).toBe('http://www.w3.org/2000/svg');
});
describe("with `hooks` defined", function() {
var testBody, tree, mountPoint;
beforeEach(function() {
testBody = document.getElementById('test');
testBody.innerHTML = '<div id="mount-point"></div>';
mountPoint = document.getElementById('mount-point');
tree = new Tree();
})
afterEach(function() {
testBody.innerHTML = '';
});
it("calls the `created` hook on creation", function() {
var params;
var tag = h({ hooks: { created : function(node, element) {
params = Array.prototype.slice.call(arguments);
} } });
var element = tag.render();
expect(params).toEqual([tag, element]);
});
it("calls the `created` hook on attachement", function() {
var params;
var tag = h({ hooks: { created : function(node, element) {
params = Array.prototype.slice.call(arguments);
} } });
var element = tag.attach(document.createElement('div'));
expect(params).toEqual([tag, element]);
});
it("calls the `inserted` hook on creation", function() {
var params;
var tag = h({ hooks: { inserted : function(node, element) {
params = Array.prototype.slice.call(arguments);
return element;
} } });
var element = tag.render(mountPoint);
expect(params).toEqual([tag, element]);
});
it("doesn't calls the `inserted` hook on fragments", function() {
var params;
var tag = h({ hooks: { inserted : function(node, element) {
params = Array.prototype.slice.call(arguments);
return element;
} } });
var element = tag.render();
expect(params).toBe(undefined);
});
it("calls the `inserted` hook on attachement", function() {
var params;
var tag = h({ hooks: { inserted : function(node, element) {
params = Array.prototype.slice.call(arguments);
return element;
} } });
var element = tag.attach(document.createElement('div'));
expect(params).toEqual([tag, element]);
});
it("calls the `updated` callback on update", function() {
var params = [];
var hooks = { updated : function() {
params.push(Array.prototype.slice.call(arguments));
} };
var from = h();
var element = from.render();
var to1 = h({ hooks: hooks });
from.patch(to1);
var to2 = h({ hooks: hooks });
to1.patch(to2);
expect(params).toEqual([[to1, from, element], [to2, to1, element]]);
});
it("calls hooks in the correct order with the correct params", function() {
var hooks = [];
var from = h({
hooks: {
created : function(node, element) {
hooks.push({
hook: 'created',
params: Array.prototype.slice.call(arguments)
});
},
inserted : function(node, element) {
hooks.push({
hook: 'inserted',
params: Array.prototype.slice.call(arguments)
});
return element;
}
}
});
var element = from.render(mountPoint);
var to = h({
hooks: {
update : function(to, from, element) {
hooks.push({
hook: 'update',
params: Array.prototype.slice.call(arguments)
});
},
updated : function(to, from, element) {
hooks.push({
hook: 'updated',
params: Array.prototype.slice.call(arguments)
});
}
}
});
from.patch(to);
expect(hooks).toEqual([
{ hook: 'created', params: [from, element] },
{ hook: 'inserted', params: [from, element] },
{ hook: 'update', params: [to, from, element] },
{ hook: 'updated', params: [to, from, element] }
]);
});
it("calls the `remove` & `destroy` callback on remove", function() {
var destroyCallback;
var params = [];
var tag = h({ hooks: {
remove : function() {
params.push(Array.prototype.slice.call(arguments));
},
destroy : function(element, callback) {
destroyCallback = callback;
params.push([element, destroyCallback]);
}
}
});
var mountId = tree.mount('#mount-point', tag);
tree.unmount(mountId);
expect(mountPoint.innerHTML).toBe('<div></div>');
expect(params).toEqual([[tag, tag.element], [tag.element, destroyCallback]]);
destroyCallback();
expect(mountPoint.innerHTML).toBe('');
});
it("calls `remove` on children first", function() {
var logs = [];
var tag = h({ hooks: {
remove : function() {
logs.push('parent removed last');
}
}
}, [
h({ hooks: {
remove : function() {
logs.push('child removed first');
}
}
})
]);
var mountId = tree.mount('#mount-point', tag);
tree.unmount(mountId);
expect(logs).toEqual(['child removed first', 'parent removed last']);
});
});
describe(".destroy()", function() {
it("silently aborts if the tag hasn't been rendered", function() {
var tag = h();
expect(tag.destroy()).toBe(undefined);
});
it("destroys a tag", function() {
var tag = h();
var element = tag.render();
var fragment = element.parentNode;
expect(tag.destroy()).toBe(element);
expect(fragment.hasChildNodes()).toBe(false);
});
});
describe(".toHtml()", function() {
it("renders a select", function() {
var select = h({ tagName: 'select', attrs: { value: 'bar' } }, [
h({tagName: 'option', attrs: {value: 'foo'}}, ['foo']),
h({tagName: 'option', attrs: {value: 'bar'}}, ['bar'])
]);
var html = select.toHtml();
var expected = '<select>';
expected += '<option value="foo">foo</option>';
expected += '<option value="bar" selected="selected">bar</option>';
expected += '</select>';
expect(html).toBe(expected);
});
it("renders a select multiple using groups", function() {
var select = h({ tagName: 'select', attrs: { multiple: 'multiple', value: ['foo', 'bar'] } }, [
h({tagName: 'optgroup', attrs: {label: 'foo-group'}}, [
h({tagName: 'option', attrs: {value: 'foo'}}, ['foo'])
]),
h({tagName: 'optgroup', attrs: {label: 'bar-group'}}, [
h({tagName: 'option', attrs: {value: 'bar'}}, ['bar'])
])
]);
var html = select.toHtml();
var expected = '<select multiple="multiple">';
expected += '<optgroup label="foo-group"><option value="foo" selected="selected">foo</option></optgroup>';
expected += '<optgroup label="bar-group"><option value="bar" selected="selected">bar</option></optgroup>';
expected += '</select>';
expect(html).toBe(expected);
});
it("renders a select multiple using groups", function() {
var image = h({ tagName: 'input', attrs: { type: 'file', multiple: 'multiple', capture: 'capture', accept: 'image/*' } });
var html = image.toHtml();
expect(html).toBe('<input type="file" multiple="multiple" capture="capture" accept="image/*">');
});
it("renders a `style` attribute using an string", function() {
var div = h({ tagName: 'div', attrs: { style: 'border:1px solid rgb(0, 0, 0);padding:2px' } });
var html = div.toHtml();
expect(html).toBe('<div style="border:1px solid rgb(0, 0, 0);padding:2px"></div>');
});
it("renders a `style` attribute using an object", function() {
var div = h({ tagName: 'div', attrs: { style: {
border: '1px solid rgb(0, 0, 0)',
padding: '2px'
} } });
var html = div.toHtml();
expect(html).toBe('<div style="border:1px solid rgb(0, 0, 0);padding:2px"></div>');
});
it("renders a `class` attribute using an object", function() {
var div = h({ tagName: 'div', attrs: { 'class': {
active1: true,
inavtive: false,
active2: true
} } });
var html = div.toHtml();
expect(html).toBe('<div class="active1 active2"></div>');
});
it("renders namespaced attributes", function() {
var div = h({
tagName: 'image',
attrs: {
xmlns: 'http://www.w3.org/2000/svg'
},
attrsNS: {
'xlink:href': 'test.jpg'
}
});
var html = div.toHtml();
expect(html).toBe('<image xmlns="http://www.w3.org/2000/svg" xlink:href="test.jpg"></image>');
});
it("casts rendered attributes to string value", function() {
var checkbox = h({ tagName: 'input', attrs: { type: 'checkbox', value: true } });
var html = checkbox.toHtml();
expect(html).toBe('<input type="checkbox" value="true">');
});
it("doesn't ignore textarea value attribute", function() {
var textarea = h({ tagName: 'textarea', attrs: { value: 'should not be ignored' } });
var html = textarea.toHtml();
expect(html).toBe('<textarea>should not be ignored</textarea>');
});
it("doesn't ignore contenteditable value attribute", function() {
var div = h({ tagName: 'div', attrs: { contenteditable: 'true', value: 'should not be ignored' } });
var html = div.toHtml();
expect(html).toBe('<div contenteditable="true">should not be ignored</div>');
});
it('renders the `innerHTML` property if present and no children has been defined', function () {
var div = h({ tagName: 'div', props: { innerHTML: '<span>Hello World</span>' }});
var html = div.toHtml();
expect(html).toBe('<div><span>Hello World</span></div>');
});
it("renders a void element", function() {
var br = h({ tagName: 'br' });
var html = br.toHtml();
expect(html).toBe('<br>');
});
it("ignores null elements", function() {
var br = h({ tagName: 'div' }, ['child1', null, 'child2']);
var html = br.toHtml();
expect(html).toBe('<div>child1child2</div>');
});
});
});