diff --git a/package.json b/package.json index 36f993e..abfc7b8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "lint": "eslint \"./src/index.js\"", + "lint": "eslint ./src/index.js ./test/index.js", "test": "npm run build && npm run lint && esbuild --bundle test/index.js | tape-run", "ci:test:tape-run": "esbuild --bundle test/index.js | tape-run", "test:open": "npm run build && esbuild --bundle test/index.js | tape-run --browser chrome --keep-open", @@ -29,8 +29,8 @@ "uuid": "^9.0.0" }, "exports": { - ".": "./index.js", - "./*.min.js": "./dist/*.min.js" + ".": "./dist/index.js", + "./min": "./dist/*.min.js" }, "contributors": [ { diff --git a/test/index.js b/test/index.js index ece8b8e..5376fba 100644 --- a/test/index.js +++ b/test/index.js @@ -5,199 +5,199 @@ import Tonic from '../index.js' const sleep = async t => new Promise(resolve => setTimeout(resolve, t)) test('sanity', async t => { - t.ok(true) + t.ok(true) - const version = Tonic.version - const parts = version.split('.') - t.ok(parseInt(parts[0]) >= 10) + const version = Tonic.version + const parts = version.split('.') + t.ok(parseInt(parts[0]) >= 10) }) test('pass an async function as an event handler', t => { - t.plan(1) + t.plan(1) - class TheApp extends Tonic { - async clicker (msg) { - t.equal(msg, 'hello', 'should get the event') - } + class TheApp extends Tonic { + async clicker (msg) { + t.equal(msg, 'hello', 'should get the event') + } - render () { - return this.html`
+ render () { + return this.html`
` + } } - } - class FnExample extends Tonic { - click (ev) { - ev.preventDefault() - this.props.onbtnclick('hello') - } + class FnExample extends Tonic { + click (ev) { + ev.preventDefault() + this.props.onbtnclick('hello') + } - render () { - return this.html`
+ render () { + return this.html`
example
` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(FnExample) - Tonic.add(TheApp) + Tonic.add(FnExample) + Tonic.add(TheApp) - document.getElementById('btn').click() + document.getElementById('btn').click() }) test('get kebab case from camel case', t => { - const kebab = Tonic.getTagName('MyExample') - t.equal(typeof kebab, 'string', 'should return a string') - t.equal(kebab, 'my-example', 'should create kebab case given camel case') + const kebab = Tonic.getTagName('MyExample') + t.equal(typeof kebab, 'string', 'should return a string') + t.equal(kebab, 'my-example', 'should create kebab case given camel case') - class MyExample extends Tonic { - render () { - return this.html`
example
` + class MyExample extends Tonic { + render () { + return this.html`
example
` + } } - } - t.equal(Tonic.getTagName(MyExample.name), 'my-example', - 'example using a tonic component') + t.equal(Tonic.getTagName(MyExample.name), 'my-example', + 'example using a tonic component') }) test('attach to dom', async t => { - class ComponentA extends Tonic { - render () { - return this.html`
` + class ComponentA extends Tonic { + render () { + return this.html`
` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(ComponentA) + Tonic.add(ComponentA) - const div = document.querySelector('div') - t.ok(div, 'a div was created and attached') + const div = document.querySelector('div') + t.ok(div, 'a div was created and attached') }) test('render-only component', async t => { - function ComponentFun () { - return this.html` + function ComponentFun () { + return this.html`
` - } + } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(ComponentFun) + Tonic.add(ComponentFun) - const div = document.querySelector('div') - t.ok(div, 'a div was created and attached') + const div = document.querySelector('div') + t.ok(div, 'a div was created and attached') }) test('render-only component (non-contextual)', async t => { - function ComponentVeryFun (html, props) { - return html` + function ComponentVeryFun (html, props) { + return html`
` - } + } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(ComponentVeryFun) + Tonic.add(ComponentVeryFun) - const div = document.querySelector('div') - t.ok(div, 'a div was created and attached') - t.equal(div.dataset.id, 'okokok') + const div = document.querySelector('div') + t.ok(div, 'a div was created and attached') + t.equal(div.dataset.id, 'okokok') }) test('Tonic escapes text', async t => { - class Comp extends Tonic { - render () { - const userInput = this.props.userInput - return this.html`
${userInput}
` + class Comp extends Tonic { + render () { + const userInput = this.props.userInput + return this.html`
${userInput}
` + } } - } - const compName = `x-${uuid()}` - Tonic.add(Comp, compName) + const compName = `x-${uuid()}` + Tonic.add(Comp, compName) - const userInput = '
lol
' - document.body.innerHTML = ` + const userInput = '
lol
' + document.body.innerHTML = ` <${compName} user-input="${userInput}"> ` - const divs = document.querySelectorAll('div') - t.equal(divs.length, 1) - const div = divs[0] - t.equal(div.childNodes.length, 1) - t.equal(div.childNodes[0].nodeType, 3) - t.equal(div.innerHTML, '<pre>lol</pre>') - t.equal(div.childNodes[0].data, '
lol
') + const divs = document.querySelectorAll('div') + t.equal(divs.length, 1) + const div = divs[0] + t.equal(div.childNodes.length, 1) + t.equal(div.childNodes[0].nodeType, 3) + t.equal(div.innerHTML, '<pre>lol</pre>') + t.equal(div.childNodes[0].data, '
lol
') }) test('Tonic supports array of templates', async t => { - class Comp1 extends Tonic { - render () { - const options = [] - for (const o of ['one', 'two', 'three']) { - options.push(this.html` + class Comp1 extends Tonic { + render () { + const options = [] + for (const o of ['one', 'two', 'three']) { + options.push(this.html` `) - } + } - return this.html`` + return this.html`` + } } - } - const compName = `x-${uuid()}` - Tonic.add(Comp1, compName) + const compName = `x-${uuid()}` + Tonic.add(Comp1, compName) - document.body.innerHTML = `<${compName}>` - const options = document.body.querySelectorAll('option') - t.equal(options.length, 3) + document.body.innerHTML = `<${compName}>` + const options = document.body.querySelectorAll('option') + t.equal(options.length, 3) }) test('Tonic escapes attribute injection', async t => { - class Comp1 extends Tonic { - render () { - const userInput2 = '" onload="console.log(42)' - const userInput = '">' - const userInput3 = 'a" onmouseover="alert(1)"' - - const input = this.props.input === 'script' - ? userInput - : this.props.input === 'space' - ? userInput3 - : userInput2 - - if (this.props.spread) { - return this.html` + class Comp1 extends Tonic { + render () { + const userInput2 = '" onload="console.log(42)' + const userInput = '">' + const userInput3 = 'a" onmouseover="alert(1)"' + + const input = this.props.input === 'script' + ? userInput + : this.props.input === 'space' + ? userInput3 + : userInput2 + + if (this.props.spread) { + return this.html`
` - } + } - if (this.props.quoted) { - return this.html` + if (this.props.quoted) { + return this.html`
` - } + } - return this.html` + return this.html`
` + } } - } - const compName = `x-${uuid()}` - Tonic.add(Comp1, compName) + const compName = `x-${uuid()}` + Tonic.add(Comp1, compName) - document.body.innerHTML = ` + document.body.innerHTML = ` <${compName} input="space" quoted="1"> <${compName} input="space" spread="1"> @@ -211,66 +211,66 @@ test('Tonic escapes attribute injection', async t => { <${compName}> ` - const divs = document.querySelectorAll('div') - t.equal(divs.length, 9) - for (let i = 0; i < divs.length; i++) { - const div = divs[i] - t.equal(div.childNodes.length, 0) - t.equal(div.hasAttribute('onmouseover'), i === 2) - t.equal(div.hasAttribute('onload'), i === 8) - } + const divs = document.querySelectorAll('div') + t.equal(divs.length, 9) + for (let i = 0; i < divs.length; i++) { + const div = divs[i] + t.equal(div.childNodes.length, 0) + t.equal(div.hasAttribute('onmouseover'), i === 2) + t.equal(div.hasAttribute('onload'), i === 8) + } }) test('attach to dom with shadow', async t => { - Tonic.add(class ShadowComponent extends Tonic { - constructor (o) { - super(o) - this.attachShadow({ mode: 'open' }) - } + Tonic.add(class ShadowComponent extends Tonic { + constructor (o) { + super(o) + this.attachShadow({ mode: 'open' }) + } - render () { - return this.html` + render () { + return this.html`
` - } - }) + } + }) - document.body.innerHTML = ` + document.body.innerHTML = ` ` - const c = document.querySelector('shadow-component') - const el = document.querySelector('div') - t.ok(!el, 'no div found in document') - const div = c.shadowRoot.querySelector('div') - t.ok(div, 'a div was created and attached to the shadow root') - t.ok(div.hasAttribute('num'), 'attributes added correctly') - t.ok(div.hasAttribute('str'), 'attributes added correctly') + const c = document.querySelector('shadow-component') + const el = document.querySelector('div') + t.ok(!el, 'no div found in document') + const div = c.shadowRoot.querySelector('div') + t.ok(div, 'a div was created and attached to the shadow root') + t.ok(div.hasAttribute('num'), 'attributes added correctly') + t.ok(div.hasAttribute('str'), 'attributes added correctly') }) test('pass props', async t => { - Tonic.add(class ComponentBB extends Tonic { - render () { - return this.html`
${this.props.data[0].foo}
` - } - }) - - Tonic.add(class ComponentB extends Tonic { - connected () { - this.setAttribute('id', this.props.id) - t.equal(this.props.disabled, '', 'disabled property was found') - t.equal(this.props.empty, '', 'empty property was found') - t.ok(this.props.testItem, 'automatically camelcase props') - } + Tonic.add(class ComponentBB extends Tonic { + render () { + return this.html`
${this.props.data[0].foo}
` + } + }) + + Tonic.add(class ComponentB extends Tonic { + connected () { + this.setAttribute('id', this.props.id) + t.equal(this.props.disabled, '', 'disabled property was found') + t.equal(this.props.empty, '', 'empty property was found') + t.ok(this.props.testItem, 'automatically camelcase props') + } - render () { - const test = [ - { foo: 'hello, world' } - ] + render () { + const test = [ + { foo: 'hello, world' } + ] - return this.html` + return this.html` { weakmap=${new WeakMap([[document, 'baz']])}> ` - } - }) + } + }) - document.body.innerHTML = ` + document.body.innerHTML = ` { ` - const bb = document.getElementById('y') - { - const props = bb.props - t.equal(props.fn(), 'hello, world', 'passed a function') - t.equal(props.number, 42.42, 'float parsed properly') - t.equal(props.set.has('foo'), true, 'set parsed properly') - t.equal(props.map.get('bar'), 'bar', 'map parsed properly') - t.equal(props.weakmap.get(document), 'baz', 'weak map parsed properly') - } + const bb = document.getElementById('y') + { + const props = bb.props + t.equal(props.fn(), 'hello, world', 'passed a function') + t.equal(props.number, 42.42, 'float parsed properly') + t.equal(props.set.has('foo'), true, 'set parsed properly') + t.equal(props.map.get('bar'), 'bar', 'map parsed properly') + t.equal(props.weakmap.get(document), 'baz', 'weak map parsed properly') + } - const div1 = document.getElementsByTagName('div')[0] - t.equal(div1.textContent, 'hello, world', 'data prop received properly') + const div1 = document.getElementsByTagName('div')[0] + t.equal(div1.textContent, 'hello, world', 'data prop received properly') - const div2 = document.getElementById('x') - t.ok(div2) + const div2 = document.getElementById('x') + t.ok(div2) - const props = div2.props - t.equal(props.testItem, 'true', 'correct props') + const props = div2.props + t.equal(props.testItem, 'true', 'correct props') }) test('get element by id and set properties via the api', async t => { - document.body.innerHTML = ` + document.body.innerHTML = ` ` - class ComponentC extends Tonic { - willConnect () { - this.setAttribute('id', 'test') - } + class ComponentC extends Tonic { + willConnect () { + this.setAttribute('id', 'test') + } - render () { - return this.html`
${String(this.props.number)}
` + render () { + return this.html`
${String(this.props.number)}
` + } } - } - Tonic.add(ComponentC) + Tonic.add(ComponentC) - { - const div = document.getElementById('test') - t.ok(div, 'a component was found by its id') - t.equal(div.textContent, '1', 'initial value is set by props') - t.ok(div.reRender, 'a component has the reRender method') - } + { + const div = document.getElementById('test') + t.ok(div, 'a component was found by its id') + t.equal(div.textContent, '1', 'initial value is set by props') + t.ok(div.reRender, 'a component has the reRender method') + } - const div = document.getElementById('test') - div.reRender({ number: 2 }) + const div = document.getElementById('test') + div.reRender({ number: 2 }) - await sleep(1) - t.equal(div.textContent, '2', 'the value was changed by reRender') + await sleep(1) + t.equal(div.textContent, '2', 'the value was changed by reRender') }) test('inheritance and super.render()', async t => { - class Stuff extends Tonic { - render () { - return this.html`
nice stuff
` + class Stuff extends Tonic { + render () { + return this.html`
nice stuff
` + } } - } - class SpecificStuff extends Stuff { - render () { - return this.html` + class SpecificStuff extends Stuff { + render () { + return this.html`
A header
${super.render()}
` + } } - } - const compName = `x-${uuid()}` - Tonic.add(SpecificStuff, compName) + const compName = `x-${uuid()}` + Tonic.add(SpecificStuff, compName) - document.body.innerHTML = ` + document.body.innerHTML = ` <${compName}> ` - const divs = document.querySelectorAll('div') - t.equal(divs.length, 2) + const divs = document.querySelectorAll('div') + t.equal(divs.length, 2) - const first = divs[0] - t.equal(first.childNodes.length, 5) - t.equal(first.childNodes[1].tagName, 'HEADER') - t.equal(first.childNodes[3].tagName, 'DIV') - t.equal(first.childNodes[3].textContent, 'nice stuff') + const first = divs[0] + t.equal(first.childNodes.length, 5) + t.equal(first.childNodes[1].tagName, 'HEADER') + t.equal(first.childNodes[3].tagName, 'DIV') + t.equal(first.childNodes[3].textContent, 'nice stuff') }) test('Tonic#html returns raw string', async t => { - class Stuff extends Tonic { - render () { - return this.html`
nice stuff
` + class Stuff extends Tonic { + render () { + return this.html`
nice stuff
` + } } - } - class SpecificStuff extends Stuff { - render () { - return this.html` + class SpecificStuff extends Stuff { + render () { + return this.html`
A header
${super.render()}
` + } } - } - const compName = `x-${uuid()}` - Tonic.add(SpecificStuff, compName) + const compName = `x-${uuid()}` + Tonic.add(SpecificStuff, compName) - document.body.innerHTML = ` + document.body.innerHTML = ` <${compName}> ` - const divs = document.querySelectorAll('div') - t.equal(divs.length, 2) + const divs = document.querySelectorAll('div') + t.equal(divs.length, 2) - const first = divs[0] - t.equal(first.childNodes.length, 5) - t.equal(first.childNodes[1].tagName, 'HEADER') - t.equal(first.childNodes[3].tagName, 'DIV') - t.equal(first.childNodes[3].textContent, 'nice stuff') + const first = divs[0] + t.equal(first.childNodes.length, 5) + t.equal(first.childNodes[1].tagName, 'HEADER') + t.equal(first.childNodes[3].tagName, 'DIV') + t.equal(first.childNodes[3].textContent, 'nice stuff') }) test('construct from api', async t => { - document.body.innerHTML = '' + document.body.innerHTML = '' - class ComponentD extends Tonic { - render () { - return this.html`
` + class ComponentD extends Tonic { + render () { + return this.html`
` + } } - } - Tonic.add(ComponentD) - const d = new ComponentD() - document.body.appendChild(d) + Tonic.add(ComponentD) + const d = new ComponentD() + document.body.appendChild(d) - d.reRender({ number: 3 }) + d.reRender({ number: 3 }) - await sleep(1) - const div1 = document.body.querySelector('div') - t.equal(div1.getAttribute('number'), '3', 'attribute was set in component') + await sleep(1) + const div1 = document.body.querySelector('div') + t.equal(div1.getAttribute('number'), '3', 'attribute was set in component') - d.reRender({ number: 6 }) + d.reRender({ number: 6 }) - await sleep(1) - const div2 = document.body.querySelector('div') - t.equal(div2.getAttribute('number'), '6', 'attribute was set in component') + await sleep(1) + const div2 = document.body.querySelector('div') + t.equal(div2.getAttribute('number'), '6', 'attribute was set in component') }) test('stylesheets and inline styles', async t => { - document.body.innerHTML = ` + document.body.innerHTML = ` ` - class ComponentF extends Tonic { - stylesheet () { - return 'component-f div { color: red; }' - } + class ComponentF extends Tonic { + stylesheet () { + return 'component-f div { color: red; }' + } - styles () { - return { - foo: { - color: 'red' - }, - bar: { - backgroundColor: 'red' + styles () { + return { + foo: { + color: 'red' + }, + bar: { + backgroundColor: 'red' + } + } } - } - } - render () { - return this.html`
` + render () { + return this.html`
` + } } - } - Tonic.add(ComponentF) + Tonic.add(ComponentF) - const expected = 'component-f div { color: red; }' - const style = document.querySelector('component-f style') - t.equal(style.textContent, expected, 'style was prefixed') - const div = document.querySelector('component-f div') - const computed = window.getComputedStyle(div) - t.equal(computed.color, 'rgb(255, 0, 0)', 'inline style was set') - t.equal(computed.backgroundColor, 'rgb(255, 0, 0)', 'inline style was set') + const expected = 'component-f div { color: red; }' + const style = document.querySelector('component-f style') + t.equal(style.textContent, expected, 'style was prefixed') + const div = document.querySelector('component-f div') + const computed = window.getComputedStyle(div) + t.equal(computed.color, 'rgb(255, 0, 0)', 'inline style was set') + t.equal(computed.backgroundColor, 'rgb(255, 0, 0)', 'inline style was set') }) test('static stylesheet', async t => { - document.body.innerHTML = ` + document.body.innerHTML = ` ` - class ComponentStaticStyles extends Tonic { - static stylesheet () { - return 'component-static-styles div { color: red; }' - } + class ComponentStaticStyles extends Tonic { + static stylesheet () { + return 'component-static-styles div { color: red; }' + } - render () { - return this.html`
RED
` + render () { + return this.html`
RED
` + } } - } - Tonic.add(ComponentStaticStyles) + Tonic.add(ComponentStaticStyles) - const style = document.head.querySelector('style') - t.ok(style, 'has a style tag') - const div = document.querySelector('component-static-styles div') - const computed = window.getComputedStyle(div) - t.equal(computed.color, 'rgb(255, 0, 0)', 'inline style was set') + const style = document.head.querySelector('style') + t.ok(style, 'has a style tag') + const div = document.querySelector('component-static-styles div') + const computed = window.getComputedStyle(div) + t.equal(computed.color, 'rgb(255, 0, 0)', 'inline style was set') }) test('component composition', async t => { - document.body.innerHTML = ` + document.body.innerHTML = ` A Few Noisy @@ -511,199 +511,199 @@ test('component composition', async t => { Text Nodes ` - class XFoo extends Tonic { - render () { - return this.html`
` + class XFoo extends Tonic { + render () { + return this.html`
` + } } - } - class XBar extends Tonic { - render () { - return this.html` + class XBar extends Tonic { + render () { + return this.html`
` + } } - } - Tonic.add(XFoo) - Tonic.add(XBar) + Tonic.add(XFoo) + Tonic.add(XBar) - t.equal(document.body.querySelectorAll('.bar').length, 2, 'two bar divs') - t.equal(document.body.querySelectorAll('.foo').length, 4, 'four foo divs') + t.equal(document.body.querySelectorAll('.bar').length, 2, 'two bar divs') + t.equal(document.body.querySelectorAll('.foo').length, 4, 'four foo divs') }) test('sync lifecycle events', async t => { - document.body.innerHTML = '' - let calledBazzCtor - let disconnectedBazz - let calledQuxxCtor - - class XBazz extends Tonic { - constructor (p) { - super(p) - calledBazzCtor = true - } + document.body.innerHTML = '' + let calledBazzCtor + let disconnectedBazz + let calledQuxxCtor + + class XBazz extends Tonic { + constructor (p) { + super(p) + calledBazzCtor = true + } - disconnected () { - disconnectedBazz = true - } + disconnected () { + disconnectedBazz = true + } - render () { - return this.html`
` + render () { + return this.html`
` + } } - } - class XQuxx extends Tonic { - constructor (p) { - super(p) - calledQuxxCtor = true - } + class XQuxx extends Tonic { + constructor (p) { + super(p) + calledQuxxCtor = true + } - willConnect () { - const expectedRE = /<\/x-quxx>/ - t.ok(true, 'willConnect event fired') - t.ok(expectedRE.test(document.body.innerHTML), 'nothing added yet') - } + willConnect () { + const expectedRE = /<\/x-quxx>/ + t.ok(true, 'willConnect event fired') + t.ok(expectedRE.test(document.body.innerHTML), 'nothing added yet') + } - connected () { - t.ok(true, 'connected event fired') - const expectedRE = /
<\/div><\/x-bazz><\/div><\/x-quxx>/ - t.ok(expectedRE.test(document.body.innerHTML), 'rendered') - } + connected () { + t.ok(true, 'connected event fired') + const expectedRE = /
<\/div><\/x-bazz><\/div><\/x-quxx>/ + t.ok(expectedRE.test(document.body.innerHTML), 'rendered') + } - render () { - t.ok(true, 'render event fired') - return this.html`
` + render () { + t.ok(true, 'render event fired') + return this.html`
` + } } - } - Tonic.add(XBazz) - Tonic.add(XQuxx) - const q = document.querySelector('x-quxx') - q.reRender({}) - const refsLength = Tonic._refIds.length + Tonic.add(XBazz) + Tonic.add(XQuxx) + const q = document.querySelector('x-quxx') + q.reRender({}) + const refsLength = Tonic._refIds.length - // once again to overwrite the old instances - q.reRender({}) - t.equal(Tonic._refIds.length, refsLength, 'Cleanup, refs correct count') + // once again to overwrite the old instances + q.reRender({}) + t.equal(Tonic._refIds.length, refsLength, 'Cleanup, refs correct count') - // once again to check that the refs length is the same - q.reRender({}) - t.equal(Tonic._refIds.length, refsLength, 'Cleanup, refs still correct count') + // once again to check that the refs length is the same + q.reRender({}) + t.equal(Tonic._refIds.length, refsLength, 'Cleanup, refs still correct count') - await sleep(0) + await sleep(0) - t.ok(calledBazzCtor, 'calling bazz ctor') - t.ok(calledQuxxCtor, 'calling quxx ctor') - t.ok(disconnectedBazz, 'disconnected event fired') + t.ok(calledBazzCtor, 'calling bazz ctor') + t.ok(calledQuxxCtor, 'calling quxx ctor') + t.ok(disconnectedBazz, 'disconnected event fired') }) test('async lifecycle events', async t => { - let bar - document.body.innerHTML = '' + let bar + document.body.innerHTML = '' - class AsyncF extends Tonic { - connected () { - bar = this.querySelector('.bar') - } + class AsyncF extends Tonic { + connected () { + bar = this.querySelector('.bar') + } - async render () { - return this.html`
` + async render () { + return this.html`
` + } } - } - Tonic.add(AsyncF) + Tonic.add(AsyncF) - await sleep(10) - t.ok(bar, 'body was ready') + await sleep(10) + t.ok(bar, 'body was ready') }) test('async-generator lifecycle events', async t => { - let bar - document.body.innerHTML = '' + let bar + document.body.innerHTML = '' - class AsyncG extends Tonic { - connected () { - bar = this.querySelector('.bar') - } + class AsyncG extends Tonic { + connected () { + bar = this.querySelector('.bar') + } - async * render () { - yield 'loading...' - yield 'something else....' - return this.html`
` + async * render () { + yield 'loading...' + yield 'something else....' + return this.html`
` + } } - } - Tonic.add(AsyncG) + Tonic.add(AsyncG) - await sleep(10) - t.ok(bar, 'body was ready') + await sleep(10) + t.ok(bar, 'body was ready') }) test('compose sugar (this.children)', async t => { - class ComponentG extends Tonic { - render () { - return this.html`
${this.children}
` + class ComponentG extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComponentH extends Tonic { - render () { - return this.html`
${this.props.value}
` + class ComponentH extends Tonic { + render () { + return this.html`
${this.props.value}
` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(ComponentG) - Tonic.add(ComponentH) + Tonic.add(ComponentG) + Tonic.add(ComponentH) - const g = document.querySelector('component-g') - const children = g.querySelectorAll('.child') - t.equal(children.length, 1, 'child element was added') - t.equal(children[0].innerHTML, 'x') + const g = document.querySelector('component-g') + const children = g.querySelectorAll('.child') + t.equal(children.length, 1, 'child element was added') + t.equal(children[0].innerHTML, 'x') - const h = document.querySelector('component-h') + const h = document.querySelector('component-h') - h.reRender({ - value: 'y' - }) + h.reRender({ + value: 'y' + }) - await sleep(1) - const childrenAfterSetProps = g.querySelectorAll('.child') - t.equal(childrenAfterSetProps.length, 1, 'child element was replaced') - t.equal(childrenAfterSetProps[0].innerHTML, 'y') + await sleep(1) + const childrenAfterSetProps = g.querySelectorAll('.child') + t.equal(childrenAfterSetProps.length, 1, 'child element was replaced') + t.equal(childrenAfterSetProps[0].innerHTML, 'y') }) test('ensure registration order does not affect rendering', async t => { - class ComposeA extends Tonic { - render () { - return this.html` + class ComposeA extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComposeB extends Tonic { - render () { - return this.html` + class ComposeB extends Tonic { + render () { + return this.html` ` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` @@ -713,105 +713,105 @@ test('ensure registration order does not affect rendering', async t => { ` - Tonic.add(ComposeB) - Tonic.add(ComposeA) + Tonic.add(ComposeB) + Tonic.add(ComposeA) - const select = document.querySelectorAll('.a select') - t.equal(select.length, 1, 'there is only one select') - t.equal(select[0].children.length, 3, 'there are 3 options') + const select = document.querySelectorAll('.a select') + t.equal(select.length, 1, 'there is only one select') + t.equal(select[0].children.length, 3, 'there are 3 options') }) test('check that composed elements use (and re-use) their initial innerHTML correctly', async t => { - class ComponentI extends Tonic { - render () { - return this.html`
+ class ComponentI extends Tonic { + render () { + return this.html`
` + } } - } - class ComponentJ extends Tonic { - render () { - return this.html`
${this.children}
` + class ComponentJ extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComponentK extends Tonic { - render () { - return this.html`
${this.props.value}
` + class ComponentK extends Tonic { + render () { + return this.html`
${this.props.value}
` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` ` - Tonic.add(ComponentJ) - Tonic.add(ComponentK) - Tonic.add(ComponentI) + Tonic.add(ComponentJ) + Tonic.add(ComponentK) + Tonic.add(ComponentI) - t.comment('Uses init() instead of ') + t.comment('Uses init() instead of ') - const i = document.querySelector('component-i') - const kTags = i.getElementsByTagName('component-k') - t.equal(kTags.length, 1) + const i = document.querySelector('component-i') + const kTags = i.getElementsByTagName('component-k') + t.equal(kTags.length, 1) - const kClasses = i.querySelectorAll('.k') - t.equal(kClasses.length, 1) + const kClasses = i.querySelectorAll('.k') + t.equal(kClasses.length, 1) - const kText = kClasses[0].textContent - t.equal(kText, 'x', 'The text of the inner-most child was rendered correctly') + const kText = kClasses[0].textContent + t.equal(kText, 'x', 'The text of the inner-most child was rendered correctly') - i.reRender({ - value: 1 - }) + i.reRender({ + value: 1 + }) - await sleep(1) - const kTagsAfterSetProps = i.getElementsByTagName('component-k') - t.equal(kTagsAfterSetProps.length, 1, 'correct number of components rendered') + await sleep(1) + const kTagsAfterSetProps = i.getElementsByTagName('component-k') + t.equal(kTagsAfterSetProps.length, 1, 'correct number of components rendered') - const kClassesAfterSetProps = i.querySelectorAll('.k') - t.equal(kClassesAfterSetProps.length, 1, 'correct number of elements rendered') - const kTextAfterSetProps = kClassesAfterSetProps[0].textContent - t.equal(kTextAfterSetProps, '1', 'The text of the inner-most child was rendered correctly') + const kClassesAfterSetProps = i.querySelectorAll('.k') + t.equal(kClassesAfterSetProps.length, 1, 'correct number of elements rendered') + const kTextAfterSetProps = kClassesAfterSetProps[0].textContent + t.equal(kTextAfterSetProps, '1', 'The text of the inner-most child was rendered correctly') }) test('mixed order declaration', async t => { - class AppXx extends Tonic { - render () { - return this.html`
${this.children}
` + class AppXx extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComponentAx extends Tonic { - render () { - return this.html`
A
` + class ComponentAx extends Tonic { + render () { + return this.html`
A
` + } } - } - class ComponentBx extends Tonic { - render () { - return this.html`
${this.children}
` + class ComponentBx extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComponentCx extends Tonic { - render () { - return this.html`
${this.children}
` + class ComponentCx extends Tonic { + render () { + return this.html`
${this.children}
` + } } - } - class ComponentDx extends Tonic { - render () { - return this.html`
D
` + class ComponentDx extends Tonic { + render () { + return this.html`
D
` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = ` @@ -825,58 +825,58 @@ test('mixed order declaration', async t => { ` - Tonic.add(ComponentDx) - Tonic.add(ComponentAx) - Tonic.add(ComponentCx) - Tonic.add(AppXx) - Tonic.add(ComponentBx) + Tonic.add(ComponentDx) + Tonic.add(ComponentAx) + Tonic.add(ComponentCx) + Tonic.add(AppXx) + Tonic.add(ComponentBx) - { - const div = document.querySelector('.app') - t.ok(div, 'a div was created and attached') - } + { + const div = document.querySelector('.app') + t.ok(div, 'a div was created and attached') + } - { - const div = document.querySelector('body .app .a') - t.ok(div, 'a div was created and attached') - } + { + const div = document.querySelector('body .app .a') + t.ok(div, 'a div was created and attached') + } - { - const div = document.querySelector('body .app .b') - t.ok(div, 'a div was created and attached') - } + { + const div = document.querySelector('body .app .b') + t.ok(div, 'a div was created and attached') + } - { - const div = document.querySelector('body .app .b .c') - t.ok(div, 'a div was created and attached') - } + { + const div = document.querySelector('body .app .b .c') + t.ok(div, 'a div was created and attached') + } - { - const div = document.querySelector('body .app .b .c .d') - t.ok(div, 'a div was created and attached') - } + { + const div = document.querySelector('body .app .b .c .d') + t.ok(div, 'a div was created and attached') + } }) test('spread props', async t => { - class SpreadComponent extends Tonic { - render () { - return this.html` + class SpreadComponent extends Tonic { + render () { + return this.html`
` + } } - } - class AppContainer extends Tonic { - render () { - const o = { - a: 'testing', - b: 2.2, - FooBar: '"ok"' - } + class AppContainer extends Tonic { + render () { + const o = { + a: 'testing', + b: 2.2, + FooBar: '"ok"' + } - const el = document.querySelector('#el').attributes + const el = document.querySelector('#el').attributes - return this.html` + return this.html` @@ -885,423 +885,423 @@ test('spread props', async t => { ` + } } - } - document.body.innerHTML = ` + document.body.innerHTML = `
` - Tonic.add(AppContainer) - Tonic.add(SpreadComponent) - - const component = document.querySelector('spread-component') - t.equal(component.getAttribute('a'), 'testing') - t.equal(component.getAttribute('b'), '2.2') - t.equal(component.getAttribute('foo-bar'), '"ok"') - const div = document.querySelector('div:first-of-type') - const span = document.querySelector('span:first-of-type') - t.equal(div.attributes.length, 3, 'div also got expanded attributes') - t.equal(span.attributes.length, 4, 'span got all attributes from div#el') + Tonic.add(AppContainer) + Tonic.add(SpreadComponent) + + const component = document.querySelector('spread-component') + t.equal(component.getAttribute('a'), 'testing') + t.equal(component.getAttribute('b'), '2.2') + t.equal(component.getAttribute('foo-bar'), '"ok"') + const div = document.querySelector('div:first-of-type') + const span = document.querySelector('span:first-of-type') + t.equal(div.attributes.length, 3, 'div also got expanded attributes') + t.equal(span.attributes.length, 4, 'span got all attributes from div#el') }) test('async render', async t => { - class AsyncRender extends Tonic { - async getSomeData () { - await sleep(100) - return 'Some Data' - } + class AsyncRender extends Tonic { + async getSomeData () { + await sleep(100) + return 'Some Data' + } - async render () { - const value = await this.getSomeData() - return this.html` + async render () { + const value = await this.getSomeData() + return this.html`

${value}

` + } } - } - Tonic.add(AsyncRender) + Tonic.add(AsyncRender) - document.body.innerHTML = ` + document.body.innerHTML = ` ` - let ar = document.body.querySelector('async-render') - t.equal(ar.innerHTML, '') + let ar = document.body.querySelector('async-render') + t.equal(ar.innerHTML, '') - await sleep(200) + await sleep(200) - ar = document.body.querySelector('async-render') - t.equal(ar.innerHTML.trim(), '

Some Data

') + ar = document.body.querySelector('async-render') + t.equal(ar.innerHTML.trim(), '

Some Data

') }) test('async generator render', async t => { - class AsyncGeneratorRender extends Tonic { - async * render () { - yield 'X' + class AsyncGeneratorRender extends Tonic { + async * render () { + yield 'X' - await sleep(100) + await sleep(100) - return 'Y' + return 'Y' + } } - } - Tonic.add(AsyncGeneratorRender) + Tonic.add(AsyncGeneratorRender) - document.body.innerHTML = ` + document.body.innerHTML = ` ` - await sleep(10) + await sleep(10) - let ar = document.body.querySelector('async-generator-render') - t.equal(ar.innerHTML, 'X') + let ar = document.body.querySelector('async-generator-render') + t.equal(ar.innerHTML, 'X') - await sleep(200) + await sleep(200) - ar = document.body.querySelector('async-generator-render') - t.equal(ar.innerHTML, 'Y') + ar = document.body.querySelector('async-generator-render') + t.equal(ar.innerHTML, 'Y') }) test('pass in references to children', async t => { - const cName = `x-${uuid()}` - const dName = `x-${uuid()}` + const cName = `x-${uuid()}` + const dName = `x-${uuid()}` - class DividerComponent extends Tonic { - willConnect () { - this.left = this.querySelector('.left') - this.right = this.querySelector('.right') - } + class DividerComponent extends Tonic { + willConnect () { + this.left = this.querySelector('.left') + this.right = this.querySelector('.right') + } - render () { - return this.html` + render () { + return this.html` ${this.left}
${this.right} ` + } } - } - Tonic.add(DividerComponent, cName) + Tonic.add(DividerComponent, cName) - class TextComp extends Tonic { - render () { - return this.html`${this.props.text}` + class TextComp extends Tonic { + render () { + return this.html`${this.props.text}` + } } - } - Tonic.add(TextComp, dName) + Tonic.add(TextComp, dName) - document.body.innerHTML = ` + document.body.innerHTML = ` <${cName}>
left
<${dName} class="right" text="right"> ` - const pElem = document.querySelector(cName) + const pElem = document.querySelector(cName) - const first = pElem.children[0] - t.ok(first) - t.equal(first.tagName, 'DIV') - t.equal(first.className, 'left') - t.equal(first.innerHTML, 'left') + const first = pElem.children[0] + t.ok(first) + t.equal(first.tagName, 'DIV') + t.equal(first.className, 'left') + t.equal(first.innerHTML, 'left') - const second = pElem.children[1] - t.ok(second) - t.equal(second.tagName, 'BR') + const second = pElem.children[1] + t.ok(second) + t.equal(second.tagName, 'BR') - const third = pElem.children[2] - t.ok(third) - t.equal(third.tagName, dName.toUpperCase()) - t.equal(third.className, 'right') - t.equal(third.innerHTML, 'right') + const third = pElem.children[2] + t.ok(third) + t.equal(third.tagName, dName.toUpperCase()) + t.equal(third.className, 'right') + t.equal(third.innerHTML, 'right') }) test('pass comp as ref in props', async t => { - const pName = `x-${uuid()}` - const cName = `x-${uuid()}` + const pName = `x-${uuid()}` + const cName = `x-${uuid()}` - class ParentComponent extends Tonic { - constructor (o) { - super(o) + class ParentComponent extends Tonic { + constructor (o) { + super(o) - this.name = 'hello' - } + this.name = 'hello' + } - render () { - return this.html` + render () { + return this.html`
<${cName} ref=${this}>
` + } } - } - class ChildComponent extends Tonic { - render () { - return this.html` + class ChildComponent extends Tonic { + render () { + return this.html`
${this.props.ref.name}
` + } } - } - Tonic.add(ParentComponent, pName) - Tonic.add(ChildComponent, cName) + Tonic.add(ParentComponent, pName) + Tonic.add(ChildComponent, cName) - document.body.innerHTML = `<${pName}>hello
') + t.equal(cElem.innerHTML.trim(), '
hello
') }) test('default props', async t => { - class InstanceProps extends Tonic { - constructor () { - super() - this.props = { num: 100 } - } + class InstanceProps extends Tonic { + constructor () { + super() + this.props = { num: 100 } + } - render () { - return this.html`
${JSON.stringify(this.props)}
` + render () { + return this.html`
${JSON.stringify(this.props)}
` + } } - } - Tonic.add(InstanceProps) + Tonic.add(InstanceProps) - document.body.innerHTML = ` + document.body.innerHTML = ` ` - const actual = document.body.innerHTML.trim() + const actual = document.body.innerHTML.trim() - const expectedRE = /
{"num":100,"str":"0x"}<\/div><\/instance-props>/ + const expectedRE = /
{"num":100,"str":"0x"}<\/div><\/instance-props>/ - t.ok(expectedRE.test(actual), 'elements match') + t.ok(expectedRE.test(actual), 'elements match') }) test('Tonic comp with null prop', async t => { - class InnerComp extends Tonic { - render () { - return this.html`
${String(this.props.foo)}
` + class InnerComp extends Tonic { + render () { + return this.html`
${String(this.props.foo)}
` + } } - } - const innerName = `x-${uuid()}` - Tonic.add(InnerComp, innerName) + const innerName = `x-${uuid()}` + Tonic.add(InnerComp, innerName) - class OuterComp extends Tonic { - render () { - return this.html`<${innerName} foo=${null}>` + class OuterComp extends Tonic { + render () { + return this.html`<${innerName} foo=${null}>` + } } - } - const outerName = `x-${uuid()}` - Tonic.add(OuterComp, outerName) + const outerName = `x-${uuid()}` + Tonic.add(OuterComp, outerName) - document.body.innerHTML = `<${outerName}>` + document.body.innerHTML = `<${outerName}>` - const div = document.body.querySelector('div') - t.ok(div) + const div = document.body.querySelector('div') + t.ok(div) - t.equal(div.textContent, 'null') + t.equal(div.textContent, 'null') }) test('re-render nested component', async t => { - const pName = `x-${uuid()}` - const cName = `x-${uuid()}` - class ParentComponent extends Tonic { - render () { - const message = this.props.message - return this.html` + const pName = `x-${uuid()}` + const cName = `x-${uuid()}` + class ParentComponent extends Tonic { + render () { + const message = this.props.message + return this.html`
<${cName} id="persist" message="${message}">
` + } } - } - class ChildStateComponent extends Tonic { - updateText (newText) { - this.state.text = newText - this.reRender() - } + class ChildStateComponent extends Tonic { + updateText (newText) { + this.state.text = newText + this.reRender() + } - render () { - const message = this.props.message - const text = this.state.text || '' + render () { + const message = this.props.message + const text = this.state.text || '' - return this.html` + return this.html`
` + } } - } - Tonic.add(ParentComponent, pName) - Tonic.add(ChildStateComponent, cName) + Tonic.add(ParentComponent, pName) + Tonic.add(ChildStateComponent, cName) - document.body.innerHTML = ` + document.body.innerHTML = ` <${pName} message="initial"> ` - const pElem = document.querySelector(pName) - t.ok(pElem) + const pElem = document.querySelector(pName) + t.ok(pElem) - const label = pElem.querySelector('label') - t.equal(label.textContent, 'initial') - - const input = pElem.querySelector('input') - t.equal(input.value, '') - - const cElem = pElem.querySelector(cName) - cElem.updateText('new text') - - async function onUpdate () { const label = pElem.querySelector('label') t.equal(label.textContent, 'initial') const input = pElem.querySelector('input') - t.equal(input.value, 'new text') + t.equal(input.value, '') - pElem.reRender({ - message: 'new message' - }) - } + const cElem = pElem.querySelector(cName) + cElem.updateText('new text') - function onReRender () { - const label = pElem.querySelector('label') - t.equal(label.textContent, 'new message') + async function onUpdate () { + const label = pElem.querySelector('label') + t.equal(label.textContent, 'initial') - const input = pElem.querySelector('input') - t.equal(input.value, 'new text') - } + const input = pElem.querySelector('input') + t.equal(input.value, 'new text') + + pElem.reRender({ + message: 'new message' + }) + } + + function onReRender () { + const label = pElem.querySelector('label') + t.equal(label.textContent, 'new message') - await sleep(1) - await onUpdate() - await sleep(1) - await onReRender() + const input = pElem.querySelector('input') + t.equal(input.value, 'new text') + } + + await sleep(1) + await onUpdate() + await sleep(1) + await onReRender() }) test('async rendering component', async t => { - const cName = `x-${uuid()}` - class AsyncComponent extends Tonic { - async render () { - await sleep(100) + const cName = `x-${uuid()}` + class AsyncComponent extends Tonic { + async render () { + await sleep(100) - return this.html`
${this.props.text}
` + return this.html`
${this.props.text}
` + } } - } - Tonic.add(AsyncComponent, cName) - document.body.innerHTML = `<${cName}>` + Tonic.add(AsyncComponent, cName) + document.body.innerHTML = `<${cName}>` - const cElem = document.querySelector(cName) - t.ok(cElem) - t.equal(cElem.textContent, '') + const cElem = document.querySelector(cName) + t.ok(cElem) + t.equal(cElem.textContent, '') - cElem.reRender({ text: 'new text' }) - t.equal(cElem.textContent, '') + cElem.reRender({ text: 'new text' }) + t.equal(cElem.textContent, '') - await cElem.reRender({ text: 'new text2' }) - t.equal(cElem.textContent, 'new text2') + await cElem.reRender({ text: 'new text2' }) + t.equal(cElem.textContent, 'new text2') }) test('alternating component', async t => { - const cName = `x-${uuid()}` - const pName = `x-${uuid()}` + const cName = `x-${uuid()}` + const pName = `x-${uuid()}` - class ParentComponent extends Tonic { - render () { - return this.html` + class ParentComponent extends Tonic { + render () { + return this.html` <${cName} id="alternating">
Child Text
Span Text Raw Text Node ` + } } - } - Tonic.add(ParentComponent, pName) + Tonic.add(ParentComponent, pName) - class AlternatingComponent extends Tonic { - constructor () { - super() + class AlternatingComponent extends Tonic { + constructor () { + super() - this.state = { - renderCount: 0, - ...this.state - } - } + this.state = { + renderCount: 0, + ...this.state + } + } - render () { - this.state.renderCount++ - if (this.state.renderCount % 2) { - return this.html` + render () { + this.state.renderCount++ + if (this.state.renderCount % 2) { + return this.html`
New content
` - } else { - return this.html`${this.nodes}` - } + } else { + return this.html`${this.nodes}` + } + } } - } - Tonic.add(AlternatingComponent, cName) + Tonic.add(AlternatingComponent, cName) - document.body.innerHTML = `<${pName}>` + document.body.innerHTML = `<${pName}>` - const pElem = document.querySelector(pName) - t.ok(pElem) + const pElem = document.querySelector(pName) + t.ok(pElem) - let cElem = document.querySelector(cName) - t.ok(cElem) + let cElem = document.querySelector(cName) + t.ok(cElem) - t.equal(cElem.children.length, 1) - t.equal(cElem.children[0].textContent, 'New content') + t.equal(cElem.children.length, 1) + t.equal(cElem.children[0].textContent, 'New content') - await pElem.reRender() + await pElem.reRender() - cElem = document.querySelector(cName) - t.ok(cElem) + cElem = document.querySelector(cName) + t.ok(cElem) - t.equal(cElem.children.length, 2) - t.equal(cElem.children[0].textContent, 'Child Text') - t.equal(cElem.children[1].textContent, 'Span Text') + t.equal(cElem.children.length, 2) + t.equal(cElem.children[0].textContent, 'Child Text') + t.equal(cElem.children[1].textContent, 'Span Text') - t.equal(cElem.childNodes.length, 5) - t.equal(cElem.childNodes[4].data.trim(), 'Raw Text Node') + t.equal(cElem.childNodes.length, 5) + t.equal(cElem.childNodes[4].data.trim(), 'Raw Text Node') - await pElem.reRender() + await pElem.reRender() - cElem = document.querySelector(cName) - t.equal(cElem.children.length, 1) - t.equal(cElem.children[0].textContent, 'New content') + cElem = document.querySelector(cName) + t.equal(cElem.children.length, 1) + t.equal(cElem.children[0].textContent, 'New content') - await pElem.reRender() + await pElem.reRender() - cElem = document.querySelector(cName) - t.equal(cElem.children.length, 2) - t.equal(cElem.children[0].textContent, 'Child Text') - t.equal(cElem.children[1].textContent, 'Span Text') + cElem = document.querySelector(cName) + t.equal(cElem.children.length, 2) + t.equal(cElem.children[0].textContent, 'Child Text') + t.equal(cElem.children[1].textContent, 'Span Text') - const child1Ref = cElem.children[0] - const child2Ref = cElem.children[1] + const child1Ref = cElem.children[0] + const child2Ref = cElem.children[1] - await cElem.reRender() - t.equal(cElem.textContent.trim(), 'New content') - await cElem.reRender() - t.equal( - cElem.textContent.trim().replace(/\s+/g, ' '), - 'Child Text Span Text Raw Text Node' - ) + await cElem.reRender() + t.equal(cElem.textContent.trim(), 'New content') + await cElem.reRender() + t.equal( + cElem.textContent.trim().replace(/\s+/g, ' '), + 'Child Text Span Text Raw Text Node' + ) - t.equal(cElem.children[0], child1Ref) - t.equal(cElem.children[1], child2Ref) + t.equal(cElem.children[0], child1Ref) + t.equal(cElem.children[1], child2Ref) }) test('cleanup, ensure exist', async t => { - document.body.classList.add('finished') + document.body.classList.add('finished') })