diff --git a/src/lib/unsafe-html.ts b/src/lib/unsafe-html.ts index 3ef7700ce8..d4c3e5668e 100644 --- a/src/lib/unsafe-html.ts +++ b/src/lib/unsafe-html.ts @@ -12,7 +12,7 @@ * http://polymer.github.io/PATENTS.txt */ -import {directive, DirectiveFn, NodePart} from '../lit-html.js'; +import {directive, DirectiveFn, NodePart, _isPrimitiveValue} from '../lit-html.js'; /** * Renders the result as HTML, rather than text. @@ -23,7 +23,11 @@ import {directive, DirectiveFn, NodePart} from '../lit-html.js'; */ export const unsafeHTML = (value: any): DirectiveFn => directive((part: NodePart): void => { + if (part._previousValue === value && _isPrimitiveValue(value)) { + return; + } const tmp = document.createElement('template'); tmp.innerHTML = value; part.setValue(document.importNode(tmp.content, true)); + part._previousValue = value; }); diff --git a/src/lit-html.ts b/src/lit-html.ts index a74f76124f..7626da35ad 100644 --- a/src/lit-html.ts +++ b/src/lit-html.ts @@ -468,7 +468,7 @@ export const noChange = {}; */ export { noChange as directiveValue }; -const isPrimitiveValue = (value: any) => value === null || +export const _isPrimitiveValue = (value: any) => value === null || !(typeof value === 'object' || typeof value === 'function'); export interface Part { @@ -531,7 +531,7 @@ export class AttributePart implements MultiPart { } for (let i = startIndex; i < startIndex + this.size; i++) { if (this._previousValues[i] !== values[i] || - !isPrimitiveValue(values[i])) { + !_isPrimitiveValue(values[i])) { return false; } } @@ -579,7 +579,7 @@ export class NodePart implements SinglePart { if (value === noChange) { return; } - if (isPrimitiveValue(value)) { + if (_isPrimitiveValue(value)) { // Handle primitive values // If the value didn't change, do nothing if (value === this._previousValue) { diff --git a/src/test/lib/unsafe-html_test.ts b/src/test/lib/unsafe-html_test.ts index dd65ab163f..05c668dcf8 100644 --- a/src/test/lib/unsafe-html_test.ts +++ b/src/test/lib/unsafe-html_test.ts @@ -24,8 +24,13 @@ const assert = chai.assert; suite('unsafeHTML', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + }); + test('renders HTML', () => { - const container = document.createElement('div'); render( html`
before${unsafeHTML('innerafter
')}`, container); @@ -33,4 +38,41 @@ suite('unsafeHTML', () => { stripExpressionDelimeters(container.innerHTML), '
beforeinnerafter
'); }); + test('dirty checks primitive values', () => { + const value = 'aaa'; + const t = () => html`
${unsafeHTML(value)}
`; + + // Initial render + render(t(), container); + assert.equal(stripExpressionDelimeters(container.innerHTML), '
aaa
'); + + // Modify instance directly. Since lit-html doesn't dirty check against + // actual DOM, but again previous part values, this modification should + // persist through the next render if dirty checking works. + const text = container.firstChild!.childNodes[1] as Text; + text.textContent = 'bbb'; + assert.equal(stripExpressionDelimeters(container.innerHTML), '
bbb
'); + + // Re-render with the same value + render(t(), container); + + assert.equal(stripExpressionDelimeters(container.innerHTML), '
bbb
'); + const text2 = container.firstChild!.childNodes[1] as Text; + assert.strictEqual(text, text2); + }); + + test('does not dirty check complex values', () => { + const value = ['aaa']; + const t = () => html`
${unsafeHTML(value)}
`; + + // Initial render + render(t(), container); + assert.equal(stripExpressionDelimeters(container.innerHTML), '
aaa
'); + + // Re-render with the same value, but a different deep property + value[0] = 'bbb'; + render(t(), container); + assert.equal(stripExpressionDelimeters(container.innerHTML), '
bbb
'); + }); + });