748 lines
33 KiB
JavaScript
748 lines
33 KiB
JavaScript
|
|
describe('ComponentBase', function() {
|
|
|
|
async function importComponentBase(context)
|
|
{
|
|
const [Instrumentation, CommonComponentBase, ComponentBase] = await context.importScripts(
|
|
['instrumentation.js', '../shared/common-component-base.js', 'components/base.js'],
|
|
'Instrumentation', 'CommonComponentBase', 'ComponentBase');
|
|
return ComponentBase;
|
|
}
|
|
|
|
function createTestToCheckExistenceOfShadowTree(callback, options = {htmlTemplate: false, cssTemplate: true})
|
|
{
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
if (options.htmlTemplate)
|
|
SomeComponent.htmlTemplate = () => { return '<div id="div" style="height: 10px;"></div>'; };
|
|
if (options.cssTemplate)
|
|
SomeComponent.cssTemplate = () => { return ':host { height: 10px; }'; };
|
|
|
|
let instance = new SomeComponent('some-component');
|
|
instance.element().style.display = 'block';
|
|
context.document.body.appendChild(instance.element());
|
|
return callback(instance, () => { return instance.element().offsetHeight == 10; });
|
|
});
|
|
}
|
|
|
|
it('must enqueue a connected component to render', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let renderCall = 0;
|
|
class SomeComponent extends ComponentBase {
|
|
render() { renderCall++; }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
let requestAnimationFrameCount = 0;
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
callback = newCallback;
|
|
requestAnimationFrameCount++;
|
|
}
|
|
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
const instance = new SomeComponent;
|
|
context.document.body.appendChild(instance.element());
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
callback();
|
|
expect(renderCall).to.be(1);
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('must enqueue a connected component to render upon a resize event if enqueueToRenderOnResize is true', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase {
|
|
static get enqueueToRenderOnResize() { return true; }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
let requestAnimationFrameCount = 0;
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
callback = newCallback;
|
|
requestAnimationFrameCount++;
|
|
}
|
|
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
const instance = new SomeComponent;
|
|
context.global.dispatchEvent(new Event('resize'));
|
|
context.document.body.appendChild(instance.element());
|
|
context.global.dispatchEvent(new Event('resize'));
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('must not enqueue a disconnected component to render upon a resize event if enqueueToRenderOnResize is true', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase {
|
|
static get enqueueToRenderOnResize() { return true; }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
let requestAnimationFrameCount = 0;
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
callback = newCallback;
|
|
requestAnimationFrameCount++;
|
|
}
|
|
|
|
const instance = new SomeComponent;
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
context.global.dispatchEvent(new Event('resize'));
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
});
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('is a function', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
expect(ComponentBase).to.be.a('function');
|
|
});
|
|
});
|
|
|
|
it('can be instantiated', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
let callCount = 0;
|
|
class SomeComponent extends ComponentBase {
|
|
constructor() {
|
|
super('some-component');
|
|
callCount++;
|
|
}
|
|
}
|
|
let instance = new SomeComponent;
|
|
expect(instance).to.be.a(ComponentBase);
|
|
expect(instance).to.be.a(SomeComponent);
|
|
expect(callCount).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('must not create shadow tree eagerly', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
expect(hasShadowTree()).to.be(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('element()', () => {
|
|
it('must return an element', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
let instance = new SomeComponent('some-component');
|
|
expect(instance.element()).to.be.a(context.global.HTMLElement);
|
|
});
|
|
});
|
|
|
|
it('must return an element whose component() matches the component', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
let instance = new SomeComponent('some-component');
|
|
expect(instance.element().component()).to.be(instance);
|
|
});
|
|
});
|
|
|
|
it('must not create shadow tree eagerly', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.element();
|
|
expect(hasShadowTree()).to.be(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('content()', () => {
|
|
it('must create shadow tree', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.content();
|
|
expect(hasShadowTree()).to.be(true);
|
|
});
|
|
});
|
|
|
|
it('must return the same shadow tree each time it is called', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
expect(instance.content()).to.be(instance.content());
|
|
});
|
|
});
|
|
|
|
it('must return the element matching the id if an id is specified', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase {
|
|
static htmlTemplate() { return '<div id="part1" title="foo"></div><div id="part1"></div>'; }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
const instance = new SomeComponent;
|
|
const part1 = instance.content('part1');
|
|
expect(part1.localName).to.be('div');
|
|
expect(part1.title).to.be('foo');
|
|
expect(instance.content('part2')).to.be(null);
|
|
});
|
|
});
|
|
|
|
it('it must create DOM tree from contentTemplate', async () => {
|
|
const context = new BrowsingContext();
|
|
const ComponentBase = await importComponentBase(context);
|
|
class SomeComponent extends ComponentBase { };
|
|
SomeComponent.contentTemplate = ['div', {id: 'container'}, 'hello, world'];
|
|
const instance = new SomeComponent('some-component');
|
|
const container = instance.content('container');
|
|
expect(container).to.be.a(context.global.HTMLDivElement);
|
|
expect(container.textContent).to.be('hello, world');
|
|
});
|
|
|
|
it('it must create content using derived class content template', async () => {
|
|
const context = new BrowsingContext();
|
|
const ComponentBase = await importComponentBase(context);
|
|
|
|
class BaseClass extends ComponentBase { };
|
|
BaseClass.contentTemplate = ['div', {id: 'container'}, 'base-class'];
|
|
const baseInstance = new BaseClass('base-class');
|
|
|
|
class DerivedClass extends BaseClass {};
|
|
DerivedClass.contentTemplate = ['div', {id: 'container'}, 'derived-class'];
|
|
const derivedInstance = new DerivedClass('derived-class');
|
|
|
|
const baseContainer = baseInstance.content('container');
|
|
expect(baseContainer).to.be.a(context.global.HTMLDivElement);
|
|
expect(baseContainer.textContent).to.be('base-class');
|
|
|
|
const derivedContainer = derivedInstance.content('container');
|
|
expect(derivedContainer).to.be.a(context.global.HTMLDivElement);
|
|
expect(derivedContainer.textContent).to.be('derived-class');
|
|
});
|
|
|
|
it('it must create stylsheet from styleTemplate', async () => {
|
|
const context = new BrowsingContext();
|
|
const ComponentBase = await importComponentBase(context);
|
|
class SomeComponent extends ComponentBase { };
|
|
SomeComponent.contentTemplate = ['span', 'hello, world'];
|
|
SomeComponent.styleTemplate = {':host': {'font-weight': 'bold'}};
|
|
const instance = new SomeComponent('some-component');
|
|
context.document.body.append(instance.element());
|
|
expect(context.global.getComputedStyle(instance.content().firstChild).fontWeight).to.be('bold');
|
|
});
|
|
});
|
|
|
|
describe('part()', () => {
|
|
it('must create shadow tree', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.part('foo');
|
|
expect(hasShadowTree()).to.be(true);
|
|
});
|
|
});
|
|
|
|
it('must return the component matching the id if an id is specified', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
class OtherComponent extends ComponentBase {
|
|
static htmlTemplate() { return '<some-component id="foo"></some-component>'; }
|
|
}
|
|
ComponentBase.defineElement('other-component', OtherComponent);
|
|
|
|
const otherComponent = new OtherComponent;
|
|
const someComponent = otherComponent.part('foo');
|
|
expect(someComponent).to.be.a(SomeComponent);
|
|
expect(someComponent.element().id).to.be('foo');
|
|
expect(otherComponent.part('foo')).to.be(someComponent);
|
|
expect(otherComponent.part('bar')).to.be(null);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('dispatchAction()', () => {
|
|
it('must invoke a callback specified in listenToAction', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
const instance = new SomeComponent;
|
|
|
|
const calls = [];
|
|
instance.listenToAction('action', (...args) => {
|
|
calls.push(args);
|
|
});
|
|
const object = {'foo': 1};
|
|
instance.dispatchAction('action', 'bar', object, 5);
|
|
|
|
expect(calls.length).to.be(1);
|
|
expect(calls[0][0]).to.be('bar');
|
|
expect(calls[0][1]).to.be(object);
|
|
expect(calls[0][2]).to.be(5);
|
|
});
|
|
});
|
|
|
|
it('must not do anything when there are no callbacks', () => {
|
|
return importComponentBase(new BrowsingContext()).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
const object = {'foo': 1};
|
|
(new SomeComponent).dispatchAction('action', 'bar', object, 5);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('enqueueToRender()', () => {
|
|
it('must not immediately call render()', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
context.global.requestAnimationFrame = () => {}
|
|
|
|
let renderCallCount = 0;
|
|
const SomeComponent = class extends ComponentBase {
|
|
render() { renderCallCount++; }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
(new SomeComponent).enqueueToRender();
|
|
expect(renderCallCount).to.be(0);
|
|
|
|
(new SomeComponent).enqueueToRender();
|
|
expect(renderCallCount).to.be(0);
|
|
});
|
|
});
|
|
|
|
it('must request an animation frame exactly once', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let requestAnimationFrameCount = 0;
|
|
context.global.requestAnimationFrame = () => { requestAnimationFrameCount++; }
|
|
|
|
const SomeComponent = class extends ComponentBase { }
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
let instance = new SomeComponent;
|
|
instance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
|
|
instance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
|
|
(new SomeComponent).enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
|
|
const AnotherComponent = class extends ComponentBase { }
|
|
ComponentBase.defineElement('another-component', AnotherComponent);
|
|
(new AnotherComponent).enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('must invoke render() when the callback to requestAnimationFrame is called', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
expect(callback).to.be(null);
|
|
expect(newCallback).to.not.be(null);
|
|
callback = newCallback;
|
|
}
|
|
|
|
let renderCalls = [];
|
|
const SomeComponent = class extends ComponentBase {
|
|
render() {
|
|
renderCalls.push(this);
|
|
}
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
expect(renderCalls.length).to.be(0);
|
|
const instance = new SomeComponent;
|
|
instance.enqueueToRender();
|
|
instance.enqueueToRender();
|
|
|
|
const anotherInstance = new SomeComponent;
|
|
anotherInstance.enqueueToRender();
|
|
expect(renderCalls.length).to.be(0);
|
|
|
|
callback();
|
|
|
|
expect(renderCalls.length).to.be(2);
|
|
expect(renderCalls[0]).to.be(instance);
|
|
expect(renderCalls[1]).to.be(anotherInstance);
|
|
});
|
|
});
|
|
|
|
it('must immediately invoke render() on a component enqueued inside another render() call', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
expect(callback).to.be(null);
|
|
expect(newCallback).to.not.be(null);
|
|
callback = newCallback;
|
|
}
|
|
|
|
let renderCalls = [];
|
|
let instanceToEnqueue = null;
|
|
const SomeComponent = class extends ComponentBase {
|
|
render() {
|
|
renderCalls.push(this);
|
|
if (instanceToEnqueue)
|
|
instanceToEnqueue.enqueueToRender();
|
|
instanceToEnqueue = null;
|
|
}
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
expect(renderCalls.length).to.be(0);
|
|
const instance = new SomeComponent;
|
|
const anotherInstance = new SomeComponent;
|
|
instance.enqueueToRender();
|
|
instanceToEnqueue = anotherInstance;
|
|
callback();
|
|
callback = null;
|
|
expect(renderCalls.length).to.be(2);
|
|
expect(renderCalls[0]).to.be(instance);
|
|
expect(renderCalls[1]).to.be(anotherInstance);
|
|
renderCalls = [];
|
|
|
|
instance.enqueueToRender();
|
|
anotherInstance.enqueueToRender();
|
|
instanceToEnqueue = instance;
|
|
callback();
|
|
expect(renderCalls.length).to.be(3);
|
|
expect(renderCalls[0]).to.be(instance);
|
|
expect(renderCalls[1]).to.be(anotherInstance);
|
|
expect(renderCalls[2]).to.be(instance);
|
|
});
|
|
});
|
|
|
|
it('must request a new animation frame once it exited the callback from requestAnimationFrame', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let requestAnimationFrameCount = 0;
|
|
let callback = null;
|
|
context.global.requestAnimationFrame = (newCallback) => {
|
|
expect(callback).to.be(null);
|
|
expect(newCallback).to.not.be(null);
|
|
callback = newCallback;
|
|
requestAnimationFrameCount++;
|
|
}
|
|
|
|
let renderCalls = [];
|
|
const SomeComponent = class extends ComponentBase {
|
|
render() { renderCalls.push(this); }
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
const instance = new SomeComponent;
|
|
const anotherInstance = new SomeComponent;
|
|
expect(requestAnimationFrameCount).to.be(0);
|
|
|
|
instance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
anotherInstance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
|
|
expect(renderCalls.length).to.be(0);
|
|
callback();
|
|
callback = null;
|
|
expect(renderCalls.length).to.be(2);
|
|
expect(renderCalls[0]).to.be(instance);
|
|
expect(renderCalls[1]).to.be(anotherInstance);
|
|
expect(requestAnimationFrameCount).to.be(1);
|
|
|
|
anotherInstance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(2);
|
|
instance.enqueueToRender();
|
|
expect(requestAnimationFrameCount).to.be(2);
|
|
|
|
expect(renderCalls.length).to.be(2);
|
|
callback();
|
|
callback = null;
|
|
expect(renderCalls.length).to.be(4);
|
|
expect(renderCalls[0]).to.be(instance);
|
|
expect(renderCalls[1]).to.be(anotherInstance);
|
|
expect(renderCalls[2]).to.be(anotherInstance);
|
|
expect(renderCalls[3]).to.be(instance);
|
|
expect(requestAnimationFrameCount).to.be(2);
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('render()', () => {
|
|
it('must create shadow tree', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.render();
|
|
expect(hasShadowTree()).to.be(true);
|
|
});
|
|
});
|
|
|
|
it('must not create shadow tree when neither htmlTemplate nor cssTemplate are present', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.render();
|
|
expect(hasShadowTree()).to.be(false);
|
|
}, {htmlTemplate: false, cssTemplate: false});
|
|
});
|
|
|
|
it('must create shadow tree when htmlTemplate is present and cssTemplate is not', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.render();
|
|
expect(hasShadowTree()).to.be(true);
|
|
}, {htmlTemplate: true, cssTemplate: false});
|
|
});
|
|
|
|
it('must create shadow tree when cssTemplate is present and htmlTemplate is not', () => {
|
|
return createTestToCheckExistenceOfShadowTree((instance, hasShadowTree) => {
|
|
instance.render();
|
|
expect(hasShadowTree()).to.be(true);
|
|
}, {htmlTemplate: false, cssTemplate: true});
|
|
});
|
|
|
|
it('must invoke didConstructShadowTree after creating the shadow tree', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let didConstructShadowTreeCount = 0;
|
|
let htmlTemplateCount = 0;
|
|
|
|
class SomeComponent extends ComponentBase {
|
|
didConstructShadowTree()
|
|
{
|
|
expect(this.content()).to.be.a(context.global.ShadowRoot);
|
|
didConstructShadowTreeCount++;
|
|
}
|
|
|
|
static htmlTemplate()
|
|
{
|
|
htmlTemplateCount++;
|
|
return '';
|
|
}
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
const instance = new SomeComponent;
|
|
expect(didConstructShadowTreeCount).to.be(0);
|
|
expect(htmlTemplateCount).to.be(0);
|
|
instance.render();
|
|
expect(didConstructShadowTreeCount).to.be(1);
|
|
expect(htmlTemplateCount).to.be(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createElement()', () => {
|
|
|
|
it('should create an element of the specified name', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const div = ComponentBase.createElement('div');
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
});
|
|
});
|
|
|
|
it('should allow to create an element with attributes to be null or undefined', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const input = ComponentBase.createElement('input', {'title': null, 'required': undefined});
|
|
expect(input).to.be.a(context.global.HTMLInputElement);
|
|
expect(input.attributes.length).to.be(2);
|
|
expect(input.attributes[0].localName).to.be('title');
|
|
expect(input.attributes[0].value).to.be('null');
|
|
expect(input.attributes[1].localName).to.be('required');
|
|
expect(input.attributes[1].value).to.be('undefined');
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified attributes', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const input = ComponentBase.createElement('input', {'title': 'hi', 'id': 'foo', 'required': false, 'checked': true});
|
|
expect(input).to.be.a(context.global.HTMLInputElement);
|
|
expect(input.attributes.length).to.be(3);
|
|
expect(input.attributes[0].localName).to.be('title');
|
|
expect(input.attributes[0].value).to.be('hi');
|
|
expect(input.attributes[1].localName).to.be('id');
|
|
expect(input.attributes[1].value).to.be('foo');
|
|
expect(input.attributes[2].localName).to.be('checked');
|
|
expect(input.attributes[2].value).to.be('');
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified event handlers and attributes', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let clickCount = 0;
|
|
const div = ComponentBase.createElement('div', {'title': 'hi', 'onclick': () => clickCount++});
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(1);
|
|
expect(div.attributes[0].localName).to.be('title');
|
|
expect(div.attributes[0].value).to.be('hi');
|
|
expect(clickCount).to.be(0);
|
|
div.click();
|
|
expect(clickCount).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified children when there is no attribute specified', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const element = ComponentBase.createElement;
|
|
const span = element('span');
|
|
const div = element('div', [span, 'hi']);
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(0);
|
|
expect(div.childNodes.length).to.be(2);
|
|
expect(div.childNodes[0]).to.be(span);
|
|
expect(div.childNodes[1]).to.be.a(context.global.Text);
|
|
expect(div.childNodes[1].data).to.be('hi');
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified children when the second argument is a span', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const element = ComponentBase.createElement;
|
|
const span = element('span');
|
|
const div = element('div', span);
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(0);
|
|
expect(div.childNodes.length).to.be(1);
|
|
expect(div.childNodes[0]).to.be(span);
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified children when the second argument is a Text node', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const element = ComponentBase.createElement;
|
|
const text = context.document.createTextNode('hi');
|
|
const div = element('div', text);
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(0);
|
|
expect(div.childNodes.length).to.be(1);
|
|
expect(div.childNodes[0]).to.be(text);
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified children when the second argument is a component', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { };
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
const element = ComponentBase.createElement;
|
|
const component = new SomeComponent;
|
|
const div = element('div', component);
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(0);
|
|
expect(div.childNodes.length).to.be(1);
|
|
expect(div.childNodes[0]).to.be(component.element());
|
|
});
|
|
});
|
|
|
|
it('should create an element with the specified attributes and children', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
const element = ComponentBase.createElement;
|
|
const span = element('span');
|
|
const div = element('div', {'lang': 'en'}, [span, 'hi']);
|
|
expect(div).to.be.a(context.global.HTMLDivElement);
|
|
expect(div.attributes.length).to.be(1);
|
|
expect(div.attributes[0].localName).to.be('lang');
|
|
expect(div.attributes[0].value).to.be('en');
|
|
expect(div.childNodes.length).to.be(2);
|
|
expect(div.childNodes[0]).to.be(span);
|
|
expect(div.childNodes[1]).to.be.a(context.global.Text);
|
|
expect(div.childNodes[1].data).to.be('hi');
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('defineElement()', () => {
|
|
|
|
it('must define a custom element with a class of an appropriate name', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
class SomeComponent extends ComponentBase { }
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
let elementClass = context.global.customElements.get('some-component');
|
|
expect(elementClass).to.be.a('function');
|
|
expect(elementClass.name).to.be('SomeComponentElement');
|
|
});
|
|
});
|
|
|
|
it('must define a custom element that can be instantiated via document.createElement', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let instances = [];
|
|
class SomeComponent extends ComponentBase {
|
|
constructor() {
|
|
super();
|
|
instances.push(this);
|
|
}
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
expect(instances.length).to.be(0);
|
|
let element = context.document.createElement('some-component');
|
|
expect(instances.length).to.be(1);
|
|
|
|
expect(element).to.be.a(context.global.HTMLElement);
|
|
expect(element.component()).to.be(instances[0]);
|
|
expect(instances[0].element()).to.be(element);
|
|
expect(instances.length).to.be(1);
|
|
});
|
|
});
|
|
|
|
it('must define a custom element that can be instantiated via new', () => {
|
|
const context = new BrowsingContext();
|
|
return importComponentBase(context).then((ComponentBase) => {
|
|
let instances = [];
|
|
class SomeComponent extends ComponentBase {
|
|
constructor() {
|
|
super();
|
|
instances.push(this);
|
|
}
|
|
}
|
|
ComponentBase.defineElement('some-component', SomeComponent);
|
|
|
|
expect(instances.length).to.be(0);
|
|
let component = new SomeComponent;
|
|
expect(instances.length).to.be(1);
|
|
|
|
expect(component).to.be(instances[0]);
|
|
expect(component.element()).to.be.a(context.global.HTMLElement);
|
|
expect(component.element().component()).to.be(component);
|
|
expect(instances.length).to.be(1);
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('_ensureShadowTree', () => {
|
|
it('should parse derived component after parsing base component', async () => {
|
|
const context = new BrowsingContext();
|
|
const ComponentBase = await importComponentBase(context);
|
|
class DerivedComponent extends ComponentBase {};
|
|
const baseInstance = new ComponentBase;
|
|
expect(ComponentBase.hasOwnProperty('_parsed')).to.be(false);
|
|
expect(DerivedComponent.hasOwnProperty('_parsed')).to.be(false);
|
|
|
|
baseInstance._ensureShadowTree();
|
|
expect(ComponentBase.hasOwnProperty('_parsed')).to.be(true);
|
|
expect(DerivedComponent.hasOwnProperty('_parsed')).to.be(false);
|
|
expect(!!ComponentBase._parsed).to.be(true);
|
|
expect(!!DerivedComponent._parsed).to.be(true);
|
|
|
|
const derivedInstance = new DerivedComponent;
|
|
derivedInstance._ensureShadowTree();
|
|
expect(ComponentBase.hasOwnProperty('_parsed')).to.be(true);
|
|
expect(DerivedComponent.hasOwnProperty('_parsed')).to.be(true);
|
|
expect(!!ComponentBase._parsed).to.be(true);
|
|
expect(!!DerivedComponent._parsed).to.be(true);
|
|
});
|
|
});
|
|
|
|
});
|