From 7455cb048686a28a5b4cd945460b0a1366b807af Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Wed, 23 Mar 2022 10:19:16 +0000 Subject: [PATCH] move all functions to their own files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kristján Oddsson --- src/document-fragment.ts | 10 ++ src/html.ts | 111 +---------------- src/index.ts | 5 +- src/iterable.ts | 30 +++++ src/processor.ts | 20 ++++ src/render.ts | 7 ++ src/sub-template.ts | 12 ++ src/template-result.ts | 41 +++++++ src/until.ts | 5 +- test/html.ts | 251 +-------------------------------------- test/render.ts | 250 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 381 insertions(+), 361 deletions(-) create mode 100644 src/document-fragment.ts create mode 100644 src/iterable.ts create mode 100644 src/processor.ts create mode 100644 src/render.ts create mode 100644 src/sub-template.ts create mode 100644 src/template-result.ts create mode 100644 test/render.ts diff --git a/src/document-fragment.ts b/src/document-fragment.ts new file mode 100644 index 0000000..ee70cce --- /dev/null +++ b/src/document-fragment.ts @@ -0,0 +1,10 @@ +import {NodeTemplatePart} from '@github/template-parts' +import type {TemplatePart} from '@github/template-parts' + +export function processDocumentFragment(part: TemplatePart, value: unknown): boolean { + if (value instanceof DocumentFragment && part instanceof NodeTemplatePart) { + if (value.childNodes.length) part.replace(...value.childNodes) + return true + } + return false +} diff --git a/src/html.ts b/src/html.ts index 5ff2dbd..b0140de 100644 --- a/src/html.ts +++ b/src/html.ts @@ -1,111 +1,6 @@ -import { - TemplateInstance, - NodeTemplatePart, - createProcessor, - processPropertyIdentity, - processBooleanAttribute -} from '@github/template-parts' -import {processDirective} from './directive.js' -import {processEvent} from './events.js' -import type {TemplatePart, TemplateTypeInit} from '@github/template-parts' +import {processor} from './processor.js' +import {TemplateResult} from './template-result.js' -function processSubTemplate(part: TemplatePart, value: unknown): boolean { - if (value instanceof TemplateResult && part instanceof NodeTemplatePart) { - value.renderInto(part) - return true - } - return false -} - -function processDocumentFragment(part: TemplatePart, value: unknown): boolean { - if (value instanceof DocumentFragment && part instanceof NodeTemplatePart) { - if (value.childNodes.length) part.replace(...value.childNodes) - return true - } - return false -} - -function isIterable(value: unknown): value is Iterable { - return typeof value === 'object' && Symbol.iterator in ((value as unknown) as Record) -} - -function processIterable(part: TemplatePart, value: unknown): boolean { - if (!isIterable(value)) return false - if (part instanceof NodeTemplatePart) { - const nodes = [] - for (const item of value) { - if (item instanceof TemplateResult) { - const fragment = document.createDocumentFragment() - item.renderInto(fragment) - nodes.push(...fragment.childNodes) - } else if (item instanceof DocumentFragment) { - nodes.push(...item.childNodes) - } else { - nodes.push(String(item)) - } - } - if (nodes.length) part.replace(...nodes) - return true - } else { - part.value = Array.from(value).join(' ') - return true - } -} - -export function processPart(part: TemplatePart, value: unknown): void { - processDirective(part, value) || - processBooleanAttribute(part, value) || - processEvent(part, value) || - processSubTemplate(part, value) || - processDocumentFragment(part, value) || - processIterable(part, value) || - processPropertyIdentity(part, value) -} - -const templates = new WeakMap() -const renderedTemplates = new WeakMap() -const renderedTemplateInstances = new WeakMap() -export class TemplateResult { - constructor( - public readonly strings: TemplateStringsArray, - public readonly values: unknown[], - public readonly processor: TemplateTypeInit - ) {} - - get template(): HTMLTemplateElement { - if (templates.has(this.strings)) { - return templates.get(this.strings)! - } else { - const template = document.createElement('template') - const end = this.strings.length - 1 - template.innerHTML = this.strings.reduce((str, cur, i) => str + cur + (i < end ? `{{ ${i} }}` : ''), '') - templates.set(this.strings, template) - return template - } - } - - renderInto(element: Node | NodeTemplatePart): void { - const template = this.template - if (renderedTemplates.get(element) !== template) { - renderedTemplates.set(element, template) - const instance = new TemplateInstance(template, this.values, this.processor) - renderedTemplateInstances.set(element, instance) - if (element instanceof NodeTemplatePart) { - element.replace(...instance.children) - } else { - element.appendChild(instance) - } - return - } - renderedTemplateInstances.get(element)!.update((this.values as unknown) as Record) - } -} - -const defaultProcessor = createProcessor(processPart) export function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult { - return new TemplateResult(strings, values, defaultProcessor) -} - -export function render(result: TemplateResult, element: Node | NodeTemplatePart): void { - result.renderInto(element) + return new TemplateResult(strings, values, processor) } diff --git a/src/index.ts b/src/index.ts index cd98a35..4eabd47 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ -export {TemplateResult, html, render} from './html.js' +export {TemplateResult} from './template-result.js' +export {render} from './render.js' +export {processor} from './processor.js' +export {html} from './html.js' export {isDirective, directive} from './directive.js' export {until} from './until.js' export {unsafeHTML} from './unsafe-html.js' diff --git a/src/iterable.ts b/src/iterable.ts new file mode 100644 index 0000000..a3457aa --- /dev/null +++ b/src/iterable.ts @@ -0,0 +1,30 @@ +import {TemplateResult} from './template-result.js' +import {NodeTemplatePart} from '@github/template-parts' +import type {TemplatePart} from '@github/template-parts' + +function isIterable(value: unknown): value is Iterable { + return typeof value === 'object' && Symbol.iterator in ((value as unknown) as Record) +} + +export function processIterable(part: TemplatePart, value: unknown): boolean { + if (!isIterable(value)) return false + if (part instanceof NodeTemplatePart) { + const nodes = [] + for (const item of value) { + if (item instanceof TemplateResult) { + const fragment = document.createDocumentFragment() + item.renderInto(fragment) + nodes.push(...fragment.childNodes) + } else if (item instanceof DocumentFragment) { + nodes.push(...item.childNodes) + } else { + nodes.push(String(item)) + } + } + if (nodes.length) part.replace(...nodes) + return true + } else { + part.value = Array.from(value).join(' ') + return true + } +} diff --git a/src/processor.ts b/src/processor.ts new file mode 100644 index 0000000..ae4bcf8 --- /dev/null +++ b/src/processor.ts @@ -0,0 +1,20 @@ +import {createProcessor, processPropertyIdentity, processBooleanAttribute} from '@github/template-parts' +import type {TemplatePart} from '@github/template-parts' + +import {processDirective} from './directive.js' +import {processEvent} from './events.js' +import {processIterable} from './iterable.js' +import {processDocumentFragment} from './document-fragment.js' +import {processSubTemplate} from './sub-template.js' + +export function processPart(part: TemplatePart, value: unknown): void { + processDirective(part, value) || + processBooleanAttribute(part, value) || + processEvent(part, value) || + processSubTemplate(part, value) || + processDocumentFragment(part, value) || + processIterable(part, value) || + processPropertyIdentity(part, value) +} + +export const processor = createProcessor(processPart) diff --git a/src/render.ts b/src/render.ts new file mode 100644 index 0000000..15ba566 --- /dev/null +++ b/src/render.ts @@ -0,0 +1,7 @@ +import type {NodeTemplatePart} from '@github/template-parts' + +import {TemplateResult} from './template-result.js' + +export function render(result: TemplateResult, element: Node | NodeTemplatePart): void { + result.renderInto(element) +} diff --git a/src/sub-template.ts b/src/sub-template.ts new file mode 100644 index 0000000..7549733 --- /dev/null +++ b/src/sub-template.ts @@ -0,0 +1,12 @@ +import {NodeTemplatePart} from '@github/template-parts' +import type {TemplatePart} from '@github/template-parts' + +import {TemplateResult} from './template-result.js' + +export function processSubTemplate(part: TemplatePart, value: unknown): boolean { + if (value instanceof TemplateResult && part instanceof NodeTemplatePart) { + value.renderInto(part) + return true + } + return false +} diff --git a/src/template-result.ts b/src/template-result.ts new file mode 100644 index 0000000..bb1adc9 --- /dev/null +++ b/src/template-result.ts @@ -0,0 +1,41 @@ +import {TemplateInstance, NodeTemplatePart} from '@github/template-parts' +import type {TemplateTypeInit} from '@github/template-parts' + +const templates = new WeakMap() +const renderedTemplates = new WeakMap() +const renderedTemplateInstances = new WeakMap() +export class TemplateResult { + constructor( + public readonly strings: TemplateStringsArray, + public readonly values: unknown[], + public readonly processor: TemplateTypeInit + ) {} + + get template(): HTMLTemplateElement { + if (templates.has(this.strings)) { + return templates.get(this.strings)! + } else { + const template = document.createElement('template') + const end = this.strings.length - 1 + template.innerHTML = this.strings.reduce((str, cur, i) => str + cur + (i < end ? `{{ ${i} }}` : ''), '') + templates.set(this.strings, template) + return template + } + } + + renderInto(element: Node | NodeTemplatePart): void { + const template = this.template + if (renderedTemplates.get(element) !== template) { + renderedTemplates.set(element, template) + const instance = new TemplateInstance(template, this.values, this.processor) + renderedTemplateInstances.set(element, instance) + if (element instanceof NodeTemplatePart) { + element.replace(...instance.children) + } else { + element.appendChild(instance) + } + return + } + renderedTemplateInstances.get(element)!.update((this.values as unknown) as Record) + } +} diff --git a/src/until.ts b/src/until.ts index f7818cd..3a3ca88 100644 --- a/src/until.ts +++ b/src/until.ts @@ -1,7 +1,8 @@ -import {processPart} from './html.js' -import {directive} from './directive.js' import type {TemplatePart} from '@github/template-parts' +import {processPart} from './processor.js' +import {directive} from './directive.js' + const untils: WeakMap = new WeakMap() export const until = directive((...promises: unknown[]) => (part: TemplatePart) => { if (!untils.has(part)) untils.set(part, {i: promises.length}) diff --git a/test/html.ts b/test/html.ts index c922c2e..0e1aacb 100644 --- a/test/html.ts +++ b/test/html.ts @@ -1,4 +1,4 @@ -import {html, render, directive} from '../lib/index.js' +import {html} from '../lib/index.js' describe('html', () => { it('creates new TemplateResults with each call', () => { @@ -9,252 +9,3 @@ describe('html', () => { expect(other()).to.not.equal(other()) }) }) - -describe('render', () => { - let surface - beforeEach(() => { - surface = document.createElement('section') - }) - - it('calls `createCallback` on first render', () => { - const main = (x = null) => html`
` - let called = false - const instance = main() - instance.processor = { - processCallback() { - throw new Error('Expected processCallback to not be called') - }, - createCallback() { - called = true - } - } - instance.renderInto(surface) - expect(called).to.equal(true) - }) - - it('memoizes by TemplateResult#template, updating old templates with new values', () => { - const main = (x = null) => html`
` - render(main('foo'), surface) - expect(surface.innerHTML).to.equal('
') - render(main('bar'), surface) - expect(surface.innerHTML).to.equal('
') - }) - - describe('nesting', () => { - it('supports nested html calls', () => { - const main = child => html`
${child}
` - const child = message => html`${message}` - render(main(child('Hello')), surface) - expect(surface.innerHTML).to.equal('
Hello
') - }) - - it('updates nodes from repeat calls', () => { - const main = child => html`
${child}
` - const child = message => html`${message}` - render(main(child('Hello')), surface) - expect(surface.innerHTML).to.equal('
Hello
') - render(main(child('Goodbye')), surface) - expect(surface.innerHTML).to.equal('
Goodbye
') - }) - - it('can nest document fragments and text nodes', () => { - const main = frag => html`${frag}` - const fragment = document.createDocumentFragment() - fragment.append(new Text('Hello World')) - render(main(fragment), surface) - expect(surface.innerHTML).to.equal('Hello World') - fragment.append(document.createTextNode('Hello Universe!')) - render(main(fragment), surface) - expect(surface.innerHTML).to.equal('Hello Universe!') - }) - - it('renders DocumentFragments nested in sub templates nested in arrays', () => { - const sub = () => { - const frag = document.createDocumentFragment() - frag.appendChild(document.createElement('div')) - return html`${frag}` - } - const main = () => html`
${[sub(), sub()]}
` - render(main(), surface) - expect(surface.innerHTML).to.contain('
') - }) - }) - - describe('iterables', () => { - it('supports arrays of strings in nodes', () => { - const main = list => html`
${list}
` - render(main(['one', 'two', 'three']), surface) - expect(surface.innerHTML).to.equal('
onetwothree
') - render(main(['four', 'five', 'six']), surface) - expect(surface.innerHTML).to.equal('
fourfivesix
') - }) - - it('supports iterables of Sub Templates with text nodes', () => { - const main = list => html`
${list}
` - let fragments = ['one', 'two', 'three'].map(text => html`${text}`) - render(main(fragments), surface) - expect(surface.innerHTML).to.equal('
onetwothree
') - fragments = ['four', 'five', 'six'].map(text => html`${text}`) - render(main(fragments), surface) - expect(surface.innerHTML).to.equal('
fourfivesix
') - }) - - it('supports iterables of fragments with text nodes', () => { - const main = list => html`
${list}
` - let fragments = ['one', 'two', 'three'].map(text => { - const fragment = document.createDocumentFragment() - fragment.append(new Text(text)) - return fragment - }) - render(main(fragments), surface) - expect(surface.innerHTML).to.equal('
onetwothree
') - fragments = ['four', 'five', 'six'].map(text => { - const fragment = document.createDocumentFragment() - fragment.append(new Text(text)) - return fragment - }) - render(main(fragments), surface) - expect(surface.innerHTML).to.equal('
fourfivesix
') - }) - - it('supports other strings iterables in nodes', () => { - const main = list => html`
${list}
` - render(main(new Set(['one', 'two', 'three'])), surface) - expect(surface.innerHTML).to.equal('
onetwothree
') - render( - main( - new Map([ - [4, 'four'], - [5, 'five'], - [6, 'six'] - ]).values() - ), - surface - ) - expect(surface.innerHTML).to.equal('
fourfivesix
') - }) - - it('supports iterables of strings in attributes', () => { - const main = list => html`
` - render(main(['one', 'two', 'three']), surface) - expect(surface.innerHTML).to.equal('
') - render(main(new Set(['four', 'five', 'six'])), surface) - expect(surface.innerHTML).to.equal('
') - }) - - it('supports nested iterables of document fragments', () => { - // prettier-ignore - const main = list => html`
    ${list}
` - render( - main( - ['One', 'Two'].map(text => { - const f = document.createDocumentFragment() - const li = document.createElement('li') - li.textContent = text - f.append(li) - return f - }) - ), - surface - ) - expect(surface.innerHTML).to.equal('
  • One
  • Two
') - }) - - it('supports nested iterables of templates', () => { - const child = item => html`
  • ${item.name}
  • ` - // prettier-ignore - const main = list => html`
      ${list.map(child)}
    ` - render(main([{name: 'One'}, {name: 'Two'}, {name: 'Three'}]), surface) - expect(surface.innerHTML).to.equal('
    • One
    • Two
    • Three
    ') - render(main([{name: 'Two'}, {name: 'Three'}, {name: 'Four'}]), surface) - expect(surface.innerHTML).to.equal('
    • Two
    • Three
    • Four
    ') - }) - }) - - describe('directives', () => { - it('handles directives differently', () => { - const setAsFoo = directive(() => part => { - part.value = 'foo' - }) - const main = () => html`
    ` - render(main(), surface) - expect(surface.innerHTML).to.equal('
    ') - }) - }) - - describe('event listeners', () => { - it('handles event listeners properly', () => { - let i = 0 - const main = () => html`
    ` - render(main(), surface) - expect(surface.innerHTML).to.equal('
    ') - expect(i).to.equal(0) - surface.children[0].click() - expect(i).to.equal(1) - surface.children[0].dispatchEvent(new CustomEvent('click')) - expect(i).to.equal(2) - }) - - it('does not rebind event listeners multiple times', () => { - let i = 0 - const main = () => html`
    ` - render(main(), surface) - render(main(), surface) - render(main(), surface) - expect(surface.innerHTML).to.equal('
    ') - expect(i).to.equal(0) - surface.children[0].click() - expect(i).to.equal(1) - surface.children[0].dispatchEvent(new CustomEvent('click')) - expect(i).to.equal(2) - }) - - it('allows events to be driven by params', () => { - let i = 0 - const main = amt => html`
    ` - render(main(1), surface) - expect(surface.innerHTML).to.equal('
    ') - expect(i).to.equal(0) - surface.children[0].click() - expect(i).to.equal(1) - render(main(4), surface) - surface.children[0].dispatchEvent(new CustomEvent('click')) - expect(i).to.equal(5) - }) - - it('will unbind event listeners by passing null', () => { - let i = 0 - const main = listener => html`
    ` - render( - main(() => (i += 1)), - surface - ) - expect(surface.innerHTML).to.equal('
    ') - expect(i).to.equal(0) - surface.children[0].click() - expect(i).to.equal(1) - render(main(null), surface) - surface.children[0].click() - surface.children[0].click() - surface.children[0].click() - expect(i).to.equal(1) - }) - - it('binds event handler objects', () => { - const handler = { - i: 0, - handleEvent() { - this.i += 1 - } - } - const main = () => html`
    ` - render(main(), surface) - expect(surface.innerHTML).to.equal('
    ') - expect(handler.i).to.equal(0) - surface.children[0].click() - expect(handler.i).to.equal(1) - surface.children[0].dispatchEvent(new CustomEvent('click')) - expect(handler.i).to.equal(2) - }) - }) -}) diff --git a/test/render.ts b/test/render.ts new file mode 100644 index 0000000..cc03386 --- /dev/null +++ b/test/render.ts @@ -0,0 +1,250 @@ +import {html, render, directive} from '../lib/index.js' + +describe('render', () => { + let surface + beforeEach(() => { + surface = document.createElement('section') + }) + + it('calls `createCallback` on first render', () => { + const main = (x = null) => html`
    ` + let called = false + const instance = main() + instance.processor = { + processCallback() { + throw new Error('Expected processCallback to not be called') + }, + createCallback() { + called = true + } + } + instance.renderInto(surface) + expect(called).to.equal(true) + }) + + it('memoizes by TemplateResult#template, updating old templates with new values', () => { + const main = (x = null) => html`
    ` + render(main('foo'), surface) + expect(surface.innerHTML).to.equal('
    ') + render(main('bar'), surface) + expect(surface.innerHTML).to.equal('
    ') + }) + + describe('nesting', () => { + it('supports nested html calls', () => { + const main = child => html`
    ${child}
    ` + const child = message => html`${message}` + render(main(child('Hello')), surface) + expect(surface.innerHTML).to.equal('
    Hello
    ') + }) + + it('updates nodes from repeat calls', () => { + const main = child => html`
    ${child}
    ` + const child = message => html`${message}` + render(main(child('Hello')), surface) + expect(surface.innerHTML).to.equal('
    Hello
    ') + render(main(child('Goodbye')), surface) + expect(surface.innerHTML).to.equal('
    Goodbye
    ') + }) + + it('can nest document fragments and text nodes', () => { + const main = frag => html`${frag}` + const fragment = document.createDocumentFragment() + fragment.append(new Text('Hello World')) + render(main(fragment), surface) + expect(surface.innerHTML).to.equal('Hello World') + fragment.append(document.createTextNode('Hello Universe!')) + render(main(fragment), surface) + expect(surface.innerHTML).to.equal('Hello Universe!') + }) + + it('renders DocumentFragments nested in sub templates nested in arrays', () => { + const sub = () => { + const frag = document.createDocumentFragment() + frag.appendChild(document.createElement('div')) + return html`${frag}` + } + const main = () => html`
    ${[sub(), sub()]}
    ` + render(main(), surface) + expect(surface.innerHTML).to.contain('
    ') + }) + }) + + describe('iterables', () => { + it('supports arrays of strings in nodes', () => { + const main = list => html`
    ${list}
    ` + render(main(['one', 'two', 'three']), surface) + expect(surface.innerHTML).to.equal('
    onetwothree
    ') + render(main(['four', 'five', 'six']), surface) + expect(surface.innerHTML).to.equal('
    fourfivesix
    ') + }) + + it('supports iterables of Sub Templates with text nodes', () => { + const main = list => html`
    ${list}
    ` + let fragments = ['one', 'two', 'three'].map(text => html`${text}`) + render(main(fragments), surface) + expect(surface.innerHTML).to.equal('
    onetwothree
    ') + fragments = ['four', 'five', 'six'].map(text => html`${text}`) + render(main(fragments), surface) + expect(surface.innerHTML).to.equal('
    fourfivesix
    ') + }) + + it('supports iterables of fragments with text nodes', () => { + const main = list => html`
    ${list}
    ` + let fragments = ['one', 'two', 'three'].map(text => { + const fragment = document.createDocumentFragment() + fragment.append(new Text(text)) + return fragment + }) + render(main(fragments), surface) + expect(surface.innerHTML).to.equal('
    onetwothree
    ') + fragments = ['four', 'five', 'six'].map(text => { + const fragment = document.createDocumentFragment() + fragment.append(new Text(text)) + return fragment + }) + render(main(fragments), surface) + expect(surface.innerHTML).to.equal('
    fourfivesix
    ') + }) + + it('supports other strings iterables in nodes', () => { + const main = list => html`
    ${list}
    ` + render(main(new Set(['one', 'two', 'three'])), surface) + expect(surface.innerHTML).to.equal('
    onetwothree
    ') + render( + main( + new Map([ + [4, 'four'], + [5, 'five'], + [6, 'six'] + ]).values() + ), + surface + ) + expect(surface.innerHTML).to.equal('
    fourfivesix
    ') + }) + + it('supports iterables of strings in attributes', () => { + const main = list => html`
    ` + render(main(['one', 'two', 'three']), surface) + expect(surface.innerHTML).to.equal('
    ') + render(main(new Set(['four', 'five', 'six'])), surface) + expect(surface.innerHTML).to.equal('
    ') + }) + + it('supports nested iterables of document fragments', () => { + // prettier-ignore + const main = list => html`
      ${list}
    ` + render( + main( + ['One', 'Two'].map(text => { + const f = document.createDocumentFragment() + const li = document.createElement('li') + li.textContent = text + f.append(li) + return f + }) + ), + surface + ) + expect(surface.innerHTML).to.equal('
    • One
    • Two
    ') + }) + + it('supports nested iterables of templates', () => { + const child = item => html`
  • ${item.name}
  • ` + // prettier-ignore + const main = list => html`
      ${list.map(child)}
    ` + render(main([{name: 'One'}, {name: 'Two'}, {name: 'Three'}]), surface) + expect(surface.innerHTML).to.equal('
    • One
    • Two
    • Three
    ') + render(main([{name: 'Two'}, {name: 'Three'}, {name: 'Four'}]), surface) + expect(surface.innerHTML).to.equal('
    • Two
    • Three
    • Four
    ') + }) + }) + + describe('directives', () => { + it('handles directives differently', () => { + const setAsFoo = directive(() => part => { + part.value = 'foo' + }) + const main = () => html`
    ` + render(main(), surface) + expect(surface.innerHTML).to.equal('
    ') + }) + }) + + describe('event listeners', () => { + it('handles event listeners properly', () => { + let i = 0 + const main = () => html`
    ` + render(main(), surface) + expect(surface.innerHTML).to.equal('
    ') + expect(i).to.equal(0) + surface.children[0].click() + expect(i).to.equal(1) + surface.children[0].dispatchEvent(new CustomEvent('click')) + expect(i).to.equal(2) + }) + + it('does not rebind event listeners multiple times', () => { + let i = 0 + const main = () => html`
    ` + render(main(), surface) + render(main(), surface) + render(main(), surface) + expect(surface.innerHTML).to.equal('
    ') + expect(i).to.equal(0) + surface.children[0].click() + expect(i).to.equal(1) + surface.children[0].dispatchEvent(new CustomEvent('click')) + expect(i).to.equal(2) + }) + + it('allows events to be driven by params', () => { + let i = 0 + const main = amt => html`
    ` + render(main(1), surface) + expect(surface.innerHTML).to.equal('
    ') + expect(i).to.equal(0) + surface.children[0].click() + expect(i).to.equal(1) + render(main(4), surface) + surface.children[0].dispatchEvent(new CustomEvent('click')) + expect(i).to.equal(5) + }) + + it('will unbind event listeners by passing null', () => { + let i = 0 + const main = listener => html`
    ` + render( + main(() => (i += 1)), + surface + ) + expect(surface.innerHTML).to.equal('
    ') + expect(i).to.equal(0) + surface.children[0].click() + expect(i).to.equal(1) + render(main(null), surface) + surface.children[0].click() + surface.children[0].click() + surface.children[0].click() + expect(i).to.equal(1) + }) + + it('binds event handler objects', () => { + const handler = { + i: 0, + handleEvent() { + this.i += 1 + } + } + const main = () => html`
    ` + render(main(), surface) + expect(surface.innerHTML).to.equal('
    ') + expect(handler.i).to.equal(0) + surface.children[0].click() + expect(handler.i).to.equal(1) + surface.children[0].dispatchEvent(new CustomEvent('click')) + expect(handler.i).to.equal(2) + }) + }) +})