lit-html
Advanced tools
Comparing version 0.1.0 to 0.2.0
{ | ||
"name": "lit-html", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "HTML template literals in JavaScript", | ||
@@ -22,3 +22,7 @@ "license": "BSD-3-Clause", | ||
"typescript": "^2.4.1" | ||
} | ||
}, | ||
"dependencies": { | ||
"@types/mocha": "^2.2.41" | ||
}, | ||
"typings": "./lib/lit-html.d.ts" | ||
} |
@@ -236,4 +236,2 @@ # lit-html | ||
Thunks are trampolined so they can return other thunks. | ||
### Arrays/Iterables | ||
@@ -277,3 +275,3 @@ | ||
* Event handlers: Specially named attributes can install event handlers. | ||
* HTML values: `lit-html` sets `textContent` by default. Extensions could allow setting `innerHTML` or injecting existing DOM nodes. | ||
* HTML values: `lit-html` creates `Text` nodes by default. Extensions could allow setting `innerHTML`. | ||
@@ -314,8 +312,10 @@ ### Small Size | ||
### Interface `Part` | ||
### Abstract Class `Part` | ||
* Property `type: string` | ||
A `Part` is a dynamic section of a `TemplateInstance`. It's value can be set to update the section. | ||
* Method `update(instance: TemplateInstance, node: Node, values: Iterator<any>): void` | ||
Specially support value types are `Node`, `Function`, and `TemplateResult`. | ||
* Method `setValue(value: any): void` | ||
## Future Work | ||
@@ -322,0 +322,0 @@ |
@@ -55,3 +55,5 @@ /** | ||
container.__templateInstance = instance; | ||
instance.appendTo(container, this.values); | ||
const fragment = instance._clone(); | ||
instance.update(this.values); | ||
container.appendChild(fragment); | ||
} else { | ||
@@ -82,72 +84,12 @@ instance.update(this.values); | ||
*/ | ||
export interface TemplatePart { | ||
type: string; | ||
index: number; | ||
update(instance: TemplateInstance, node: Node, values: Iterator<any>): void; | ||
} | ||
export class AttributePart implements TemplatePart { | ||
type: 'attribute'; | ||
index: number; | ||
name: string; | ||
rawName: string; | ||
strings: string[]; | ||
constructor(index: number, name: string, rawName: string, strings: string[]) { | ||
this.index = index; | ||
this.name = name; | ||
this.rawName = rawName; | ||
this.strings = strings; | ||
export class TemplatePart { | ||
constructor( | ||
public type: string, | ||
public index: number, | ||
public name?: string, | ||
public rawName?: string, | ||
public strings?: string[]) { | ||
} | ||
update(_instance: TemplateInstance, node: Node, values: Iterator<any>) { | ||
console.assert(node.nodeType === Node.ELEMENT_NODE); | ||
const strings = this.strings; | ||
let text = ''; | ||
for (let i = 0; i < strings.length; i++) { | ||
text += strings[i]; | ||
if (i < strings.length - 1) { | ||
const v = values.next().value; | ||
if (v && typeof v !== 'string' && v[Symbol.iterator]) { | ||
for (const t of v) { | ||
// TODO: we need to recursively call getValue into iterables... | ||
text += t; | ||
} | ||
} else { | ||
text += v; | ||
} | ||
} | ||
} | ||
(node as Element).setAttribute(this.name, text); | ||
} | ||
} | ||
export class NodePart implements TemplatePart { | ||
type: 'node'; | ||
index: number; | ||
constructor(index: number) { | ||
this.index = index; | ||
} | ||
update(instance: TemplateInstance, node: Node, values: Iterator<any>): void { | ||
console.assert(node.nodeType === Node.TEXT_NODE); | ||
const value = values.next().value; | ||
if (value && typeof value !== 'string' && value[Symbol.iterator]) { | ||
const fragment = document.createDocumentFragment(); | ||
for (const item of value) { | ||
const marker = new Text(); | ||
fragment.appendChild(marker); | ||
instance.renderValue(item, marker); | ||
} | ||
instance.renderValue(fragment, node); | ||
} else { | ||
instance.renderValue(value, node); | ||
} | ||
} | ||
} | ||
export class Template { | ||
@@ -186,3 +128,3 @@ private _strings: TemplateStringsArray; | ||
const rawName = match![1]; | ||
this.parts.push(new AttributePart(index, attribute.name, rawName, strings)); | ||
this.parts.push(new TemplatePart('attribute', index, attribute.name, rawName, strings)); | ||
attributesToRemove.push(attribute); | ||
@@ -194,3 +136,4 @@ } | ||
if (strings.length > 1) { | ||
// Generate a new text node for each literal and part | ||
// Generate a new text node for each literal and two for each part, | ||
// a start and end | ||
partIndex += strings.length - 1; | ||
@@ -203,5 +146,6 @@ for (let i = 0; i < strings.length; i++) { | ||
if (i < strings.length - 1) { | ||
const partNode = new Text(); | ||
node.parentNode!.insertBefore(partNode, node); | ||
this.parts.push(new NodePart(index)); | ||
node.parentNode!.insertBefore(new Text(), node); | ||
node.parentNode!.insertBefore(new Text(), node); | ||
this.parts.push(new TemplatePart('node', index)); | ||
index++; | ||
} | ||
@@ -236,5 +180,181 @@ } | ||
export abstract class Part { | ||
size?: number; | ||
abstract setValue(value: any): void; | ||
protected _getValue(value: any) { | ||
if (typeof value === 'function') { | ||
try { | ||
value = value(this); | ||
} catch (e) { | ||
console.error(e); | ||
return; | ||
} | ||
} | ||
if (value === null) { | ||
// `null` as the value of Text node will render the string 'null' | ||
return undefined; | ||
} | ||
return value; | ||
} | ||
} | ||
export class AttributePart extends Part { | ||
element: Element; | ||
name: string; | ||
strings: string[]; | ||
constructor(element: Element, name: string, strings: string[]) { | ||
super(); | ||
console.assert(element.nodeType === Node.ELEMENT_NODE); | ||
this.element = element; | ||
this.name = name; | ||
this.strings = strings; | ||
} | ||
setValue(values: any[]): void { | ||
const strings = this.strings; | ||
let text = ''; | ||
for (let i = 0; i < strings.length; i++) { | ||
text += strings[i]; | ||
if (i < strings.length - 1) { | ||
const v = this._getValue(values[i]); | ||
if (v && typeof v !== 'string' && v[Symbol.iterator]) { | ||
for (const t of v) { | ||
// TODO: we need to recursively call getValue into iterables... | ||
text += t; | ||
} | ||
} else { | ||
text += v; | ||
} | ||
} | ||
} | ||
this.element.setAttribute(this.name, text); | ||
} | ||
get size(): number { | ||
return this.strings.length - 1; | ||
} | ||
} | ||
export class NodePart extends Part { | ||
startNode: Node; | ||
endNode: Node; | ||
private _previousValue: any; | ||
constructor(startNode: Node, endNode: Node) { | ||
super(); | ||
this.startNode = startNode; | ||
this.endNode = endNode; | ||
} | ||
setValue(value: any): void { | ||
let node: Node|undefined = undefined; | ||
value = this._getValue(value); | ||
if (value instanceof Node) { | ||
this.clear(); | ||
node = value; | ||
} else if (value instanceof TemplateResult) { | ||
let instance: TemplateInstance; | ||
if (this._previousValue && this._previousValue._template === value.template) { | ||
instance = this._previousValue; | ||
} else { | ||
this.clear(); | ||
instance = new TemplateInstance(value.template); | ||
node = instance._clone(); | ||
} | ||
instance.update(value.values); | ||
this._previousValue = instance; | ||
} else if (value && typeof value !== 'string' && value[Symbol.iterator]) { | ||
// For an Iterable, we create a new InstancePart per item, then set its | ||
// value to the item. This is a little bit of overhead for every item in | ||
// an Iterable, but it lets us recurse easily and update Arrays of | ||
// TemplateResults that will be commonly returned from expressions like: | ||
// array.map((i) => html`${i}`) | ||
// We reuse this parts startNode as the first part's startNode, and this | ||
// parts endNode as the last part's endNode. | ||
let itemStart = this.startNode; | ||
let itemEnd; | ||
const values = value[Symbol.iterator]() as Iterator<any>; | ||
const previousParts = Array.isArray(this._previousValue) ? this._previousValue : undefined; | ||
let previousPartsIndex = 0; | ||
const itemParts = []; | ||
let current = values.next(); | ||
let next = values.next(); | ||
while (!current.done) { | ||
if (next.done) { | ||
// on the last item, reuse this part's endNode | ||
itemEnd = this.endNode; | ||
} else { | ||
itemEnd = new Text(); | ||
this.endNode.parentNode!.insertBefore(itemEnd, this.endNode); | ||
} | ||
// Reuse a part if we can, otherwise create a new one | ||
let itemPart; | ||
if (previousParts !== undefined && previousPartsIndex < previousParts.length) { | ||
itemPart = previousParts[previousPartsIndex++]; | ||
} else { | ||
itemPart = new NodePart(itemStart, itemEnd); | ||
} | ||
itemPart.setValue(current.value); | ||
itemParts.push(itemPart); | ||
current = next; | ||
next = values.next(); | ||
itemStart = itemEnd; | ||
} | ||
this._previousValue = itemParts; | ||
// If the new list is shorter than the old list, clean up: | ||
if (previousParts !== undefined && previousPartsIndex < previousParts.length) { | ||
const clearStart = previousParts[previousPartsIndex].startNode; | ||
const clearEnd = previousParts[previousParts.length - 1].endNode; | ||
const clearRange = document.createRange(); | ||
if (previousPartsIndex === 0) { | ||
clearRange.setStartBefore(clearStart); | ||
} else { | ||
clearRange.setStartAfter(clearStart); | ||
} | ||
clearRange.setEndAfter(clearEnd); | ||
clearRange.deleteContents(); | ||
clearRange.detach(); // is this neccessary? | ||
} | ||
} else { | ||
this.clear(); | ||
node = new Text(value); | ||
} | ||
if (node !== undefined) { | ||
this.endNode.parentNode!.insertBefore(node, this.endNode); | ||
} | ||
} | ||
clear() { | ||
this._previousValue = undefined; | ||
let node: Node = this.startNode; | ||
let next: Node|null = node.nextSibling; | ||
while (next !== null && next !== this.endNode) { | ||
node = next; | ||
next = next.nextSibling; | ||
node.parentNode!.removeChild(node); | ||
} | ||
} | ||
// detach(): DocumentFragment ? | ||
} | ||
export class TemplateInstance { | ||
private _template: Template; | ||
private _parts: {part: TemplatePart, node: Node}[] = []; | ||
_template: Template; | ||
_parts: Part[] = []; | ||
startNode: Node; | ||
@@ -247,23 +367,15 @@ endNode: Node; | ||
appendTo(container: Element|DocumentFragment, values: any[]) { | ||
const fragment = this._clone(); | ||
this.update(values); | ||
container.appendChild(fragment); | ||
} | ||
update(values: any[]) { | ||
const valuesIterator = this._getValues(values); | ||
for (const {part, node} of this._parts) { | ||
part.update(this, node, valuesIterator); | ||
let valueIndex = 0; | ||
for (const part of this._parts) { | ||
if (part.size === undefined) { | ||
part.setValue(values[valueIndex++]); | ||
} else { | ||
part.setValue(values.slice(valueIndex, valueIndex + part.size)); | ||
valueIndex += part.size; | ||
} | ||
} | ||
} | ||
private _getFragment() { | ||
const fragment = this._clone(); | ||
this.startNode = fragment.insertBefore(new Text(), fragment.firstChild); | ||
this.endNode = fragment.appendChild(new Text()); | ||
return fragment; | ||
} | ||
private _clone(): DocumentFragment { | ||
_clone(): DocumentFragment { | ||
const fragment = document.importNode(this._template.element.content, true); | ||
@@ -276,12 +388,13 @@ | ||
const parts = this._template.parts; | ||
let index = -1; | ||
let index = 0; | ||
let partIndex = 0; | ||
let part = parts[0]; | ||
while (walker.nextNode() && partIndex < parts.length) { | ||
index++; | ||
if (index === part.index) { | ||
const node = walker.currentNode; | ||
this._parts.push({part, node}); | ||
part = parts[++partIndex]; | ||
let templatePart = parts[0]; | ||
let node = walker.nextNode(); | ||
while (node != null && partIndex < parts.length) { | ||
if (index === templatePart.index) { | ||
this._parts.push(this._createPart(templatePart, node)); | ||
templatePart = parts[++partIndex]; | ||
} else { | ||
index++; | ||
node = walker.nextNode(); | ||
} | ||
@@ -293,65 +406,12 @@ } | ||
/** | ||
* Converts a raw values array passed to a template tag into an iterator so | ||
* that TemplateParts can consume it while updating. | ||
* | ||
* Contains a trampoline to evaluate thunks until they return a non-function value. | ||
*/ | ||
private * _getValues(values: any[]) { | ||
for (let value of values) { | ||
while (typeof value === 'function') { | ||
try { | ||
value = value(); | ||
} catch (e) { | ||
console.error(e); | ||
yield; | ||
} | ||
} | ||
yield value; | ||
} | ||
} | ||
renderValue(value: any, node: Node) { | ||
let templateInstance = node.__templateInstance as TemplateInstance; | ||
if (templateInstance !== undefined && (!(value instanceof TemplateResult) || templateInstance._template !== value.template)) { | ||
this._cleanup(node); | ||
} | ||
if (value instanceof DocumentFragment) { | ||
node.__templateInstance = { | ||
startNode: value.firstChild!, | ||
endNode: value.lastChild!, | ||
}; | ||
node.parentNode!.insertBefore(value, node.nextSibling); | ||
} else if (value instanceof TemplateResult) { | ||
if (templateInstance === undefined || value.template !== templateInstance._template) { | ||
// We haven't stamped this template to this location, so create | ||
// a new instance and insert it. | ||
// TODO: Add keys and check for key equality also | ||
node.textContent = ''; | ||
templateInstance = node.__templateInstance = new TemplateInstance(value.template); | ||
const fragment = templateInstance._getFragment(); | ||
node.parentNode!.insertBefore(fragment, node.nextSibling); | ||
} | ||
templateInstance.update(value.values); | ||
_createPart(templatePart: TemplatePart, node: Node): Part { | ||
if (templatePart.type === 'attribute') { | ||
return new AttributePart(node as Element, templatePart.name!, templatePart.strings!); | ||
} else if (templatePart.type === 'node') { | ||
return new NodePart(node, node.nextSibling!); | ||
} else { | ||
node.textContent = value; | ||
throw new Error(`unknown part type: ${templatePart.type}`); | ||
} | ||
} | ||
private _cleanup(node: Node) { | ||
const instance = node.__templateInstance!; | ||
// We had a previous template instance here, but don't now: clean up | ||
let cleanupNode: Node|null = instance.startNode; | ||
while (cleanupNode !== null) { | ||
const n = cleanupNode; | ||
cleanupNode = cleanupNode.nextSibling; | ||
n.parentNode!.removeChild(n); | ||
if (n === instance.endNode) { | ||
break; | ||
} | ||
} | ||
node.__templateInstance = undefined; | ||
} | ||
} | ||
@@ -358,0 +418,0 @@ |
@@ -15,8 +15,6 @@ /** | ||
import {html, TemplateResult, AttributePart, TemplatePart, TemplateInstance} from '../lit-html.js'; | ||
/// <reference path="../../node_modules/@types/mocha/index.d.ts" /> | ||
/// <reference path="../../node_modules/@types/chai/index.d.ts" /> | ||
declare const chai: any; | ||
declare const mocha: any; | ||
declare const suite: (title: string, fn: Function) => void; | ||
declare const test: (title: string, fn: Function) => void; | ||
import {html, TemplateResult, TemplatePart, TemplateInstance, NodePart, Part, AttributePart} from '../lit-html.js'; | ||
@@ -64,4 +62,4 @@ const assert = chai.assert; | ||
const parts = result.template.parts; | ||
const names = parts.map((p: AttributePart) => p.name); | ||
const rawNames = parts.map((p: AttributePart) => p.rawName); | ||
const names = parts.map((p: TemplatePart) => p.name); | ||
const rawNames = parts.map((p: TemplatePart) => p.rawName); | ||
assert.deepEqual(names, ['someprop', 'a-nother', 'multiparts', undefined, 'athing']); | ||
@@ -71,2 +69,11 @@ assert.deepEqual(rawNames, ['someProp', 'a-nother', 'multiParts', undefined, 'aThing']); | ||
test('parses expressions for two attributes of one element', () => { | ||
const result = html`<div a="${1}" b="${2}"></div>`; | ||
const parts = result.template.parts; | ||
assert.equal(parts.length, 2); | ||
const instance = new TemplateInstance(result.template); | ||
instance._clone(); | ||
assert.equal(instance._parts.length, 2); | ||
}) | ||
}); | ||
@@ -108,8 +115,2 @@ | ||
test('renders chained thunks', () => { | ||
const container = document.createElement('div'); | ||
html`<div>${(_:any)=>(_:any)=>123}</div>`.renderTo(container); | ||
assert.equal(container.innerHTML, '<div>123</div>'); | ||
}); | ||
test('renders thunks that throw as empty text', () => { | ||
@@ -134,2 +135,9 @@ const container = document.createElement('div'); | ||
// test('renders multiple nested templates', () => { | ||
// const container = document.createElement('div'); | ||
// const partial = html`<h1>${'foo'}</h1>`; | ||
// html`${partial}${'bar'}${partial}${'baz'}qux`.renderTo(container); | ||
// assert.equal(container.innerHTML, '<h1>foo</h1>bar<h1>foo</h1>bazqux'); | ||
// }); | ||
test('renders arrays of nested templates', () => { | ||
@@ -141,2 +149,20 @@ const container = document.createElement('div'); | ||
test('renders an element', () => { | ||
const container = document.createElement('div'); | ||
const child = document.createElement('p'); | ||
html`<div>${child}</div>`.renderTo(container); | ||
assert.equal(container.innerHTML, '<div><p></p></div>'); | ||
}); | ||
test('renders an array of elements', () => { | ||
const container = document.createElement('div'); | ||
const children = [ | ||
document.createElement('p'), | ||
document.createElement('a'), | ||
document.createElement('span') | ||
]; | ||
html`<div>${children}</div>`.renderTo(container); | ||
assert.equal(container.innerHTML, '<div><p></p><a></a><span></span></div>'); | ||
}); | ||
test('renders to an attribute', () => { | ||
@@ -148,3 +174,3 @@ const container = document.createElement('div'); | ||
test('renders to an attribute wihtout quotes', () => { | ||
test('renders to an attribute without quotes', () => { | ||
const container = document.createElement('div'); | ||
@@ -173,2 +199,8 @@ html`<div foo=${'bar'}></div>`.renderTo(container); | ||
test('renders to an attribute and node', () => { | ||
const container = document.createElement('div'); | ||
html`<div foo="${'bar'}">${'baz'}</div>`.renderTo(container); | ||
assert.equal(container.innerHTML, '<div foo="bar">baz</div>'); | ||
}); | ||
test('renders a combination of stuff', () => { | ||
@@ -282,2 +314,38 @@ const container = document.createElement('div'); | ||
test('updates an element', () => { | ||
const container = document.createElement('div'); | ||
let child: any = document.createElement('p'); | ||
const t = () => html`<div>${child}<div></div></div>`; | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div><p></p><div></div></div>'); | ||
child = undefined; | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div><div></div></div>'); | ||
child = new Text('foo'); | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div>foo<div></div></div>'); | ||
}); | ||
test('updates an array of elements', () => { | ||
const container = document.createElement('div'); | ||
let children: any = [ | ||
document.createElement('p'), | ||
document.createElement('a'), | ||
document.createElement('span') | ||
]; | ||
const t = () => html`<div>${children}</div>` | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div><p></p><a></a><span></span></div>'); | ||
children = null; | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div></div>'); | ||
children = new Text('foo'); | ||
t().renderTo(container); | ||
assert.equal(container.innerHTML, '<div>foo</div>'); | ||
}); | ||
}); | ||
@@ -297,22 +365,20 @@ | ||
class PropertyPart implements TemplatePart { | ||
type: 'property'; | ||
index: number; | ||
name: string; | ||
strings: string[]; | ||
constructor(index: number, name: string, strings: string[]) { | ||
this.index = index; | ||
this.name = name; | ||
this.strings = strings; | ||
class PropertySettingTemplateInstance extends TemplateInstance { | ||
_createPart(templatePart: TemplatePart, node: Node): Part { | ||
if (templatePart.type === 'attribute') { | ||
return new PropertyPart(node as Element, templatePart.rawName!, templatePart.strings!); | ||
} | ||
return super._createPart(templatePart, node); | ||
} | ||
update(_instance: TemplateInstance, node: Node, values: Iterator<any>) { | ||
console.assert(node.nodeType === Node.ELEMENT_NODE); | ||
} | ||
class PropertyPart extends AttributePart { | ||
setValue(values: any[]): void { | ||
const s = this.strings; | ||
if (s[0] === '' && s[s.length - 1] === '') { | ||
if (s.length === 2 && s[0] === '' && s[s.length - 1] === '') { | ||
// An expression that occupies the whole attribute value will leave | ||
// leading and trailing empty strings. | ||
(node as any)[this.name] = values.next().value; | ||
(this.element as any)[this.name] = values[0]; | ||
} else { | ||
@@ -324,6 +390,6 @@ // Interpolation, so interpolate | ||
if (i < s.length - 1) { | ||
text += values.next().value; | ||
text += values[i]; | ||
} | ||
} | ||
(node as any)[this.name] = text; | ||
(this.element as any)[this.name] = text; | ||
} | ||
@@ -335,5 +401,6 @@ } | ||
const t = html`<div someProp="${123}"></div>`; | ||
const part = t.template.parts[0] as AttributePart; | ||
t.template.parts[0] = new PropertyPart(part.index, part.rawName, part.strings); | ||
t.renderTo(container); | ||
const instance = new PropertySettingTemplateInstance(t.template); | ||
const fragment = instance._clone(); | ||
instance.update(t.values); | ||
container.appendChild(fragment); | ||
assert.equal(container.innerHTML, '<div></div>'); | ||
@@ -347,5 +414,148 @@ assert.strictEqual((container.firstElementChild as any).someProp, 123); | ||
}); | ||
suite('NodePart', () => { | ||
let container: HTMLElement; | ||
let startNode: Node; | ||
let endNode: Node; | ||
let part: NodePart; | ||
mocha.run(); | ||
setup(() => { | ||
container = document.createElement('div'); | ||
startNode = new Text(); | ||
endNode = new Text(); | ||
container.appendChild(startNode); | ||
container.appendChild(endNode); | ||
part = new NodePart(startNode, endNode); | ||
}); | ||
suite('setValue', () => { | ||
test('accepts a string', () => { | ||
part.setValue('foo'); | ||
assert.equal(container.innerHTML, 'foo'); | ||
}); | ||
test('accepts a number', () => { | ||
part.setValue(123); | ||
assert.equal(container.innerHTML, '123'); | ||
}); | ||
test('accepts undefined', () => { | ||
part.setValue(undefined); | ||
assert.equal(container.innerHTML, ''); | ||
}); | ||
test('accepts null', () => { | ||
part.setValue(null); | ||
assert.equal(container.innerHTML, ''); | ||
}); | ||
test('accepts a thunk', () => { | ||
part.setValue((_:any)=>123); | ||
assert.equal(container.innerHTML, '123'); | ||
}); | ||
test('accepts thunks that throw as empty text', () => { | ||
part.setValue((_:any)=>{throw new Error('e')}); | ||
assert.equal(container.innerHTML, ''); | ||
}); | ||
test('accepts an element', () => { | ||
part.setValue(document.createElement('p')); | ||
assert.equal(container.innerHTML, '<p></p>'); | ||
}); | ||
test('accepts arrays', () => { | ||
part.setValue([1,2,3]); | ||
assert.equal(container.innerHTML, '123'); | ||
}); | ||
test('accepts nested templates', () => { | ||
part.setValue(html`<h1>${'foo'}</h1>`); | ||
assert.equal(container.innerHTML, '<h1>foo</h1>'); | ||
}); | ||
test('accepts arrays of nested templates', () => { | ||
part.setValue([1,2,3].map((i)=>html`${i}`)); | ||
assert.equal(container.innerHTML, '123'); | ||
}); | ||
test('accepts an array of elements', () => { | ||
const children = [ | ||
document.createElement('p'), | ||
document.createElement('a'), | ||
document.createElement('span') | ||
]; | ||
part.setValue(children); | ||
assert.equal(container.innerHTML, '<p></p><a></a><span></span>'); | ||
}); | ||
test('updates when called multiple times with simple values', () => { | ||
part.setValue('abc'); | ||
assert.equal(container.innerHTML, 'abc'); | ||
part.setValue('def'); | ||
assert.equal(container.innerHTML, 'def'); | ||
}); | ||
test('updates when called multiple times with arrays', () => { | ||
part.setValue([1, 2, 3]); | ||
assert.equal(container.innerHTML, '123'); | ||
part.setValue([4, 5]); | ||
assert.equal(container.innerHTML, '45'); | ||
// check that we're not leaving orphaned marker nodes around | ||
assert.deepEqual(['', '4', '', '5', ''], Array.from(container.childNodes).map((n) => n.nodeValue)); | ||
part.setValue([]); | ||
assert.equal(container.innerHTML, ''); | ||
assert.deepEqual([], Array.from(container.childNodes).map((n) => n.nodeValue)); | ||
}); | ||
test('updates are stable when called multiple times with templates', () => { | ||
let value = 'foo'; | ||
const r = () => html`<h1>${value}</h1>`; | ||
part.setValue(r); | ||
assert.equal(container.innerHTML, '<h1>foo</h1>'); | ||
const originalH1 = container.querySelector('h1'); | ||
value = 'bar'; | ||
part.setValue(r); | ||
assert.equal(container.innerHTML, '<h1>bar</h1>'); | ||
const newH1 = container.querySelector('h1'); | ||
assert.isTrue(newH1 === originalH1); | ||
}); | ||
test('updates are stable when called multiple times with arrays of templates', () => { | ||
let items = [1, 2, 3]; | ||
const r = () => items.map((i)=>html`<li>${i}</li>`); | ||
part.setValue(r); | ||
assert.equal(container.innerHTML, '<li>1</li><li>2</li><li>3</li>'); | ||
const originalLIs = Array.from(container.querySelectorAll('li')); | ||
items = [3, 2, 1]; | ||
part.setValue(r); | ||
assert.equal(container.innerHTML, '<li>3</li><li>2</li><li>1</li>'); | ||
const newLIs = Array.from(container.querySelectorAll('li')); | ||
assert.deepEqual(newLIs, originalLIs); | ||
}); | ||
}); | ||
suite('clear', () => { | ||
test('is a no-op on an already empty range', () => { | ||
part.clear(); | ||
assert.deepEqual(Array.from(container.childNodes), [startNode, endNode]); | ||
}); | ||
test('clears a range', () => { | ||
container.insertBefore(new Text('foo'), endNode); | ||
part.clear(); | ||
assert.deepEqual(Array.from(container.childNodes), [startNode, endNode]); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64978
9
1182
1
+ Added@types/mocha@^2.2.41
+ Added@types/mocha@2.2.48(transitive)