Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@netflix/x-element

Package Overview
Dependencies
Maintainers
10
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@netflix/x-element - npm Package Compare versions

Comparing version 1.0.0-rc.53 to 1.0.0-rc.54

6

package.json

@@ -5,3 +5,3 @@ {

"description": "Custom Element base class.",
"version": "1.0.0-rc.53",
"version": "1.0.0-rc.54",
"license": "SEE LICENSE IN LICENSE",

@@ -32,5 +32,5 @@ "repository": "https://github.com/Netflix/x-element",

"http-server": "^14.1.1",
"puppeteer": "^13.4.0",
"tap-parser": "^10.0.1"
"puppeteer": "^19.7.5",
"tap-parser": "^12.0.1"
}
}

@@ -1,2 +0,2 @@

import { test, cover } from './x-test.js';
import { test, coverage } from './x-test.js';

@@ -7,3 +7,3 @@ // We import this here so we can see code coverage.

// Set a high bar for code coverage!
cover(new URL('../x-element.js', import.meta.url).href, 100);
coverage(new URL('../x-element.js', import.meta.url).href, 100);

@@ -10,0 +10,0 @@ test('./test-analysis-errors.html');

import XElement from '../x-element.js';
import { assert, it, skip } from './x-test.js';
import { assert, it } from './x-test.js';

@@ -223,3 +223,3 @@ class TestElement extends XElement {

// seems like a bug in the browser, but we should look into it.
(navigator.userAgent.includes('Firefox') ? skip : it)('test adoptedCallback', () => {
(navigator.userAgent.includes('Firefox') ? it.skip : it)('test adoptedCallback', () => {
const el = document.createElement('test-element');

@@ -226,0 +226,0 @@ el.prop1 = 'adopt me!';

import XElement from '../x-element.js';
import { assert, it } from './x-test.js';
import { assert, describe, it } from './x-test.js';

@@ -10,1512 +10,1570 @@ // Long-term interface.

it('html: renders basic string', () => {
const getTemplate = () => {
return html`<div id="target">No interpolation.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').textContent === 'No interpolation.');
container.remove();
});
describe('html rendering', () => {
it('renders basic string', () => {
const getTemplate = () => {
return html`<div id="target">No interpolation.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').textContent === 'No interpolation.');
container.remove();
});
it('html: refuses to interpolate within a style tag', () => {
const getTemplate = ({ color }) => {
return html`
<style id="target">
#target {
background-color: ${color};
}
</style>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ color: 'url(evil-url)' }));
const textContent = container.querySelector('#target').textContent;
assert(textContent === '/* x-element: Interpolation is not allowed here. */', textContent);
container.remove();
});
it('refuses to interpolate within a style tag', () => {
const getTemplate = ({ color }) => {
return html`
<style id="target">
#target {
background-color: ${color};
}
</style>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ color: 'url(evil-url)' }));
const textContent = container.querySelector('#target').textContent;
assert(textContent === '/* x-element: Interpolation is not allowed here. */', textContent);
container.remove();
});
it('html: refuses to interpolate within a script tag', () => {
const getTemplate = ({ message }) => {
return html`
<script id="target">
console.log('${message}');
</script>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ message: '\' + prompt(\'evil\') + \'' }));
const textContent = container.querySelector('#target').textContent;
assert(textContent === '/* x-element: Interpolation is not allowed here. */', textContent);
container.remove();
});
it('refuses to interpolate within a script tag', () => {
const getTemplate = ({ message }) => {
return html`
<script id="target">
console.log('${message}');
</script>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ message: '\' + prompt(\'evil\') + \'' }));
const textContent = container.querySelector('#target').textContent;
assert(textContent === '/* x-element: Interpolation is not allowed here. */', textContent);
container.remove();
});
it('html: renders interpolated content without parsing', () => {
const userContent = '<a href="https://evil.com">Click Me!</a>';
const getTemplate = () => {
return html`<div id="target">${userContent}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').textContent === userContent);
container.remove();
});
it('renders interpolated content without parsing', () => {
const userContent = '<a href="https://evil.com">Click Me!</a>';
const getTemplate = () => {
return html`<div id="target">${userContent}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').textContent === userContent);
container.remove();
});
it('html: does not recognize single-quoted attributes', () => {
const getTemplate = () => {
return html`<div id="target" ignore-me='${'foo'}'>Gotta double-quote those.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').getAttribute('ignore-me') !== 'foo');
container.remove();
});
it('does not recognize single-quoted attributes', () => {
const getTemplate = () => {
return html`<div id="target" ignore-me='${'foo'}'>Gotta double-quote those.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').getAttribute('ignore-me') !== 'foo');
container.remove();
});
it('html: does not recognize single-quoted properties', () => {
const getTemplate = () => {
return html`<div id="target" .ignoreMe='${'foo'}'>Gotta double-quote those.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').ignoreMe !== 'foo');
container.remove();
});
it('does not recognize single-quoted properties', () => {
const getTemplate = () => {
return html`<div id="target" .ignoreMe='${'foo'}'>Gotta double-quote those.</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(container.querySelector('#target').ignoreMe !== 'foo');
container.remove();
});
it('html: refuses to reuse a template', () => {
const templateResultReference = html`<div id="target"></div>`;
const container = document.createElement('div');
document.body.append(container);
render(container, templateResultReference);
assert(!!container.querySelector('#target'));
render(container, null);
assert(!container.querySelector('#target'));
let error;
try {
it('refuses to reuse a template', () => {
const templateResultReference = html`<div id="target"></div>`;
const container = document.createElement('div');
document.body.append(container);
render(container, templateResultReference);
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected re-injection of template result.', error.message);
container.remove();
});
assert(!!container.querySelector('#target'));
render(container, null);
assert(!container.querySelector('#target'));
let error;
try {
render(container, templateResultReference);
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected re-injection of template result.', error.message);
container.remove();
});
it('html: renders nullish templates', () => {
const getTemplate = () => {
return html`<div></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(!!container.childNodes.length);
render(container, null);
assert(!container.childNodes.length);
render(container, getTemplate());
assert(!!container.childNodes.length);
render(container, undefined);
assert(!container.childNodes.length);
container.remove();
});
it('renders nullish templates', () => {
const getTemplate = () => {
return html`<div></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate());
assert(!!container.childNodes.length);
render(container, null);
assert(!container.childNodes.length);
render(container, getTemplate());
assert(!!container.childNodes.length);
render(container, undefined);
assert(!container.childNodes.length);
container.remove();
});
it('html: renders interpolated content', () => {
const getTemplate = ({ content }) => {
return html`<div id="target">${content}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'Interpolated.' }));
assert(container.querySelector('#target').textContent === 'Interpolated.');
render(container, getTemplate({ content: 'Updated.' }));
assert(container.querySelector('#target').textContent === 'Updated.');
container.remove();
});
it('renders interpolated content', () => {
const getTemplate = ({ content }) => {
return html`<div id="target">${content}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'Interpolated.' }));
assert(container.querySelector('#target').textContent === 'Interpolated.');
render(container, getTemplate({ content: 'Updated.' }));
assert(container.querySelector('#target').textContent === 'Updated.');
container.remove();
});
it('html: renders multiple, interpolated content', () => {
const getTemplate = ({ one, two }) => {
return html`
<div id="target">one: ${one} / two: ${two}</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ one: 'ONE', two: 'TWO' }));
assert(container.querySelector('#target').textContent === 'one: ONE / two: TWO');
container.remove();
});
it('renders multiple, interpolated content', () => {
const getTemplate = ({ one, two }) => {
return html`
<div id="target">one: ${one} / two: ${two}</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ one: 'ONE', two: 'TWO' }));
assert(container.querySelector('#target').textContent === 'one: ONE / two: TWO');
container.remove();
});
it('html: renders nested content', () => {
const getTemplate = ({ show, nestedContent }) => {
return html`
<div id="target">
${show ? html`<span id="conditional">${nestedContent}</span>` : null}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ show: true, nestedContent: 'oh hai' }));
assert(!!container.querySelector('#conditional'));
render(container, getTemplate({ show: false, nestedContent: 'oh hai' }));
assert(!container.querySelector('#conditional'));
render(container, getTemplate({ show: true, nestedContent: 'oh hai' }));
assert(container.querySelector('#conditional').textContent === 'oh hai');
render(container, getTemplate({ show: true, nestedContent: 'k bye' }));
assert(container.querySelector('#conditional').textContent === 'k bye');
container.remove();
});
it('renders nested content', () => {
const getTemplate = ({ show, nestedContent }) => {
return html`
<div id="target">
${show ? html`<span id="conditional">${nestedContent}</span>` : null}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ show: true, nestedContent: 'oh hai' }));
assert(!!container.querySelector('#conditional'));
render(container, getTemplate({ show: false, nestedContent: 'oh hai' }));
assert(!container.querySelector('#conditional'));
render(container, getTemplate({ show: true, nestedContent: 'oh hai' }));
assert(container.querySelector('#conditional').textContent === 'oh hai');
render(container, getTemplate({ show: true, nestedContent: 'k bye' }));
assert(container.querySelector('#conditional').textContent === 'k bye');
container.remove();
});
it('html: renders attributes', () => {
const getTemplate = ({ attr }) => {
return html`<div id="target" attr="${attr}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ attr: 'foo' }));
assert(container.querySelector('#target').getAttribute('attr') === 'foo');
render(container, getTemplate({ attr: 'bar' }));
assert(container.querySelector('#target').getAttribute('attr') === 'bar');
container.remove();
});
it('renders attributes', () => {
const getTemplate = ({ attr }) => {
return html`<div id="target" attr="${attr}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ attr: 'foo' }));
assert(container.querySelector('#target').getAttribute('attr') === 'foo');
render(container, getTemplate({ attr: 'bar' }));
assert(container.querySelector('#target').getAttribute('attr') === 'bar');
container.remove();
});
it('html: renders boolean attributes', () => {
const getTemplate = ({ attr }) => {
return html`<div id="target" ?attr="${attr}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ attr: 'foo' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: '' }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: 'bar' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: undefined }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: 'baz' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: false }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: true }));
assert(container.querySelector('#target').getAttribute('attr') === '');
container.remove();
});
it('renders boolean attributes', () => {
const getTemplate = ({ attr }) => {
return html`<div id="target" ?attr="${attr}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ attr: 'foo' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: '' }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: 'bar' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: undefined }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: 'baz' }));
assert(container.querySelector('#target').getAttribute('attr') === '');
render(container, getTemplate({ attr: false }));
assert(container.querySelector('#target').getAttribute('attr') === null);
render(container, getTemplate({ attr: true }));
assert(container.querySelector('#target').getAttribute('attr') === '');
container.remove();
});
it('html: renders properties', () => {
const getTemplate = ({ prop }) => {
return html`<div id="target" .prop="${prop}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ prop: 'foo' }));
assert(container.querySelector('#target').prop === 'foo');
render(container, getTemplate({ prop: 'bar' }));
assert(container.querySelector('#target').prop === 'bar');
container.remove();
});
it('renders properties', () => {
const getTemplate = ({ prop }) => {
return html`<div id="target" .prop="${prop}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ prop: 'foo' }));
assert(container.querySelector('#target').prop === 'foo');
render(container, getTemplate({ prop: 'bar' }));
assert(container.querySelector('#target').prop === 'bar');
container.remove();
});
it('html: maintains DOM nodes', () => {
const getTemplate = ({ content }) => {
return html`<div>${content}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'foo' }));
container.querySelector('div').classList.add('state');
assert(container.querySelector('div').textContent === 'foo');
assert(container.querySelector('div').classList.contains('state'));
render(container, getTemplate({ content: 'bar' }));
assert(container.querySelector('div').textContent === 'bar');
assert(container.querySelector('div').classList.contains('state'));
container.remove();
});
it('maintains DOM nodes', () => {
const getTemplate = ({ content }) => {
return html`<div>${content}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'foo' }));
container.querySelector('div').classList.add('state');
assert(container.querySelector('div').textContent === 'foo');
assert(container.querySelector('div').classList.contains('state'));
render(container, getTemplate({ content: 'bar' }));
assert(container.querySelector('div').textContent === 'bar');
assert(container.querySelector('div').classList.contains('state'));
container.remove();
});
it('html: renders lists', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => html`<div>${item}</div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo', 'bar', 'baz'] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0].textContent === 'foo');
assert(container.querySelector('#target').children[1].textContent === 'bar');
assert(container.querySelector('#target').children[2].textContent === 'baz');
container.remove();
});
it('renders lists', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => html`<div>${item}</div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo', 'bar', 'baz'] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0].textContent === 'foo');
assert(container.querySelector('#target').children[1].textContent === 'bar');
assert(container.querySelector('#target').children[2].textContent === 'baz');
container.remove();
});
it('html: renders lists with multiple elements', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => html`<div>${item}-</div><div>${item}</div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo', 'bar', 'baz'] }));
assert(container.querySelector('#target').childElementCount === 6);
assert(container.querySelector('#target').children[0].textContent === 'foo-');
assert(container.querySelector('#target').children[1].textContent === 'foo');
assert(container.querySelector('#target').children[2].textContent === 'bar-');
assert(container.querySelector('#target').children[3].textContent === 'bar');
assert(container.querySelector('#target').children[4].textContent === 'baz-');
assert(container.querySelector('#target').children[5].textContent === 'baz');
container.remove();
});
it('renders lists with multiple elements', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => html`<div>${item}-</div><div>${item}</div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo', 'bar', 'baz'] }));
assert(container.querySelector('#target').childElementCount === 6);
assert(container.querySelector('#target').children[0].textContent === 'foo-');
assert(container.querySelector('#target').children[1].textContent === 'foo');
assert(container.querySelector('#target').children[2].textContent === 'bar-');
assert(container.querySelector('#target').children[3].textContent === 'bar');
assert(container.querySelector('#target').children[4].textContent === 'baz-');
assert(container.querySelector('#target').children[5].textContent === 'baz');
container.remove();
});
it('html: renders lists with changing templates', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => item ? html`<div class="true"></div>` : html`<div class="false"></div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [true, true, true] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0].classList.contains('true'));
assert(container.querySelector('#target').children[1].classList.contains('true'));
assert(container.querySelector('#target').children[2].classList.contains('true'));
render(container, getTemplate({ items: [true, false, true] }));
assert(container.querySelector('#target').children[0].classList.contains('true'));
assert(container.querySelector('#target').children[1].classList.contains('false'));
assert(container.querySelector('#target').children[2].classList.contains('true'));
container.remove();
});
it('renders lists with changing templates', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(item => item ? html`<div class="true"></div>` : html`<div class="false"></div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [true, true, true] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0].classList.contains('true'));
assert(container.querySelector('#target').children[1].classList.contains('true'));
assert(container.querySelector('#target').children[2].classList.contains('true'));
render(container, getTemplate({ items: [true, false, true] }));
assert(container.querySelector('#target').children[0].classList.contains('true'));
assert(container.querySelector('#target').children[1].classList.contains('false'));
assert(container.querySelector('#target').children[2].classList.contains('true'));
container.remove();
});
it('html: renders lists with changing length', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(() => html`<div class="item"></div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [1, 2, 3] }));
assert(container.querySelector('#target').childElementCount === 3);
render(container, getTemplate({ items: [1, 2, 3, 4, 5] }));
assert(container.querySelector('#target').childElementCount === 5);
render(container, getTemplate({ items: [1, 2] }));
assert(container.querySelector('#target').childElementCount === 2);
container.remove();
});
it('renders lists with changing length', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${items.map(() => html`<div class="item"></div>`)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [1, 2, 3] }));
assert(container.querySelector('#target').childElementCount === 3);
render(container, getTemplate({ items: [1, 2, 3, 4, 5] }));
assert(container.querySelector('#target').childElementCount === 5);
render(container, getTemplate({ items: [1, 2] }));
assert(container.querySelector('#target').childElementCount === 2);
container.remove();
});
it('html: renders multiple templates', () => {
const getTemplate = ({ content }) => {
if (content) {
return html`<div id="content">${content}</div>`;
} else {
return html`<div id="empty"></div>`;
}
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'oh hai' }));
assert(!!container.querySelector('#content'));
assert(!container.querySelector('#empty'));
render(container, getTemplate({ content: '' }));
assert(!container.querySelector('#content'));
assert(!!container.querySelector('#empty'));
container.remove();
});
it('renders multiple templates', () => {
const getTemplate = ({ content }) => {
if (content) {
return html`<div id="content">${content}</div>`;
} else {
return html`<div id="empty"></div>`;
}
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'oh hai' }));
assert(!!container.querySelector('#content'));
assert(!container.querySelector('#empty'));
render(container, getTemplate({ content: '' }));
assert(!container.querySelector('#content'));
assert(!!container.querySelector('#empty'));
container.remove();
});
it('html: renders multiple templates (as content)', () => {
const getTemplate = ({ content }) => {
return html`
<div id="target">
${content ? html`<div id="content">${content}</div>` : html`<div id="empty"></div>`}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'oh hai' }));
assert(!!container.querySelector('#content'));
assert(!container.querySelector('#empty'));
render(container, getTemplate({ content: '' }));
assert(!container.querySelector('#content'));
assert(!!container.querySelector('#empty'));
container.remove();
});
it('renders multiple templates (as content)', () => {
const getTemplate = ({ content }) => {
return html`
<div id="target">
${content ? html`<div id="content">${content}</div>` : html`<div id="empty"></div>`}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: 'oh hai' }));
assert(!!container.querySelector('#content'));
assert(!container.querySelector('#empty'));
render(container, getTemplate({ content: '' }));
assert(!container.querySelector('#content'));
assert(!!container.querySelector('#empty'));
container.remove();
});
it('html: renders elements with special characters in attributes', () => {
// Note the "/", "<", ">", and "&quot;" characters.
const getTemplate = ({ width, height }) => {
return html`\
<svg
id="svg"
class="<><>&quot;&quot;</></>"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="${ifDefined(width)}"
height="${ifDefined(height)}">
<circle id="circle" r="${width / 2}" cx="${width / 2}" cy="${height / 2}"></circle>
</svg>`;
};
const container = document.createElement('div');
document.body.append(container);
const width = 24;
const height = 24;
render(container, getTemplate({ width, height }));
const svgBox = container.querySelector('#svg').getBoundingClientRect();
assert(svgBox.width === width);
assert(svgBox.height === height);
const circleBox = container.querySelector('#circle').getBoundingClientRect();
assert(circleBox.width === width);
assert(circleBox.height === height);
container.remove();
});
it('renders elements with "/" characters in attributes', () => {
// Note the "/" character.
const getTemplate = ({ width, height }) => {
return html`\
<svg
id="svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="${ifDefined(width)}"
height="${ifDefined(height)}">
<circle id="circle" r="${width / 2}" cx="${width / 2}" cy="${height / 2}"></circle>
</svg>`;
};
const container = document.createElement('div');
document.body.append(container);
const width = 24;
const height = 24;
render(container, getTemplate({ width, height }));
const svgBox = container.querySelector('#svg').getBoundingClientRect();
assert(svgBox.width === width);
assert(svgBox.height === height);
const circleBox = container.querySelector('#circle').getBoundingClientRect();
assert(circleBox.width === width);
assert(circleBox.height === height);
container.remove();
});
it('html: self-closing tags work', () => {
const getTemplate = ({ type }) => {
return html`<input type="${nullish(type)}"/>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ type: null }));
assert(container.querySelector('input').type === 'text');
render(container, getTemplate({ type: 'checkbox' }));
assert(container.querySelector('input').type === 'checkbox');
container.remove();
});
// TODO: trying to find escaped values in strings is possible via a regex, but
// making that performant is nuanced. I.e., it's easy to cause catastrophic
// backtracking.
it.todo('renders elements with "<" or ">" characters in attributes', () => {
// Note the "/", "<", and ">" characters.
const getTemplate = ({ width, height }) => {
return html`\
<svg
id="svg"
class="<><></></>"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="${ifDefined(width)}"
height="${ifDefined(height)}">
<circle id="circle" r="${width / 2}" cx="${width / 2}" cy="${height / 2}"></circle>
</svg>`;
};
const container = document.createElement('div');
document.body.append(container);
const width = 24;
const height = 24;
render(container, getTemplate({ width, height }));
const svgBox = container.querySelector('#svg').getBoundingClientRect();
assert(svgBox.width === width);
assert(svgBox.height === height);
const circleBox = container.querySelector('#circle').getBoundingClientRect();
assert(circleBox.width === width);
assert(circleBox.height === height);
container.remove();
});
it('html: textarea element content', () => {
const getTemplate = ({ defaultValue }) => {
return html`<textarea>${defaultValue}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ defaultValue: 'foo' }));
assert(container.querySelector('textarea').value === 'foo');
container.remove();
it('self-closing tags work', () => {
const getTemplate = ({ type }) => {
return html`<input type="${nullish(type)}"/>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ type: null }));
assert(container.querySelector('input').type === 'text');
render(container, getTemplate({ type: 'checkbox' }));
assert(container.querySelector('input').type === 'checkbox');
container.remove();
});
it('textarea element content', () => {
const getTemplate = ({ defaultValue }) => {
return html`<textarea>${defaultValue}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ defaultValue: 'foo' }));
assert(container.querySelector('textarea').value === 'foo');
container.remove();
});
});
// This is mainly for backwards compat, "nullish" is likely a better match.
it('html: updaters: ifDefined', () => {
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ maybe: 'yes' }));
assert(container.querySelector('#target').getAttribute('maybe') === 'yes');
render(container, getTemplate({ maybe: undefined }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
render(container, getTemplate({ maybe: false }));
assert(container.querySelector('#target').getAttribute('maybe') === 'false');
describe('html updaters', () => {
// This is mainly for backwards compat, "nullish" is likely a better match.
it('ifDefined', () => {
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ maybe: 'yes' }));
assert(container.querySelector('#target').getAttribute('maybe') === 'yes');
render(container, getTemplate({ maybe: undefined }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
render(container, getTemplate({ maybe: false }));
assert(container.querySelector('#target').getAttribute('maybe') === 'false');
// This is correct, but perhaps unexpected.
render(container, getTemplate({ maybe: null }));
assert(container.querySelector('#target').getAttribute('maybe') === 'null');
container.remove();
});
// This is correct, but perhaps unexpected.
render(container, getTemplate({ maybe: null }));
assert(container.querySelector('#target').getAttribute('maybe') === 'null');
container.remove();
});
it('html: updaters: nullish', () => {
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ maybe: 'yes' }));
assert(container.querySelector('#target').getAttribute('maybe') === 'yes');
render(container, getTemplate({ maybe: undefined }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
render(container, getTemplate({ maybe: false }));
assert(container.querySelector('#target').getAttribute('maybe') === 'false');
render(container, getTemplate({ maybe: null }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
container.remove();
});
it('nullish', () => {
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ maybe: 'yes' }));
assert(container.querySelector('#target').getAttribute('maybe') === 'yes');
render(container, getTemplate({ maybe: undefined }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
render(container, getTemplate({ maybe: false }));
assert(container.querySelector('#target').getAttribute('maybe') === 'false');
render(container, getTemplate({ maybe: null }));
assert(container.querySelector('#target').getAttribute('maybe') === null);
container.remove();
});
it('html: updaters: live', () => {
const getTemplate = ({ alive, dead }) => {
return html`<div id="target" .alive="${live(alive)}" .dead="${dead}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ alive: 'lively', dead: 'deadly' }));
assert(container.querySelector('#target').alive === 'lively');
assert(container.querySelector('#target').dead === 'deadly');
container.querySelector('#target').alive = 'changed';
container.querySelector('#target').dead = 'changed';
assert(container.querySelector('#target').alive === 'changed');
assert(container.querySelector('#target').dead === 'changed');
render(container, getTemplate({ alive: 'lively', dead: 'deadly' }));
assert(container.querySelector('#target').alive === 'lively');
assert(container.querySelector('#target').dead === 'changed');
container.remove();
});
it('live', () => {
const getTemplate = ({ alive, dead }) => {
return html`<div id="target" .alive="${live(alive)}" .dead="${dead}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ alive: 'lively', dead: 'deadly' }));
assert(container.querySelector('#target').alive === 'lively');
assert(container.querySelector('#target').dead === 'deadly');
container.querySelector('#target').alive = 'changed';
container.querySelector('#target').dead = 'changed';
assert(container.querySelector('#target').alive === 'changed');
assert(container.querySelector('#target').dead === 'changed');
render(container, getTemplate({ alive: 'lively', dead: 'deadly' }));
assert(container.querySelector('#target').alive === 'lively');
assert(container.querySelector('#target').dead === 'changed');
container.remove();
});
it('html: updaters: unsafeHTML', () => {
const getTemplate = ({ content }) => {
return html`<div id="target">${unsafeHTML(content)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: '<div id="injected">oh hai</div>' }));
assert(!!container.querySelector('#injected'));
render(container, getTemplate({ content: '<div id="booster">oh hai, again</div>' }));
assert(!!container.querySelector('#booster'));
container.remove();
});
it('unsafeHTML', () => {
const getTemplate = ({ content }) => {
return html`<div id="target">${unsafeHTML(content)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: '<div id="injected">oh hai</div>' }));
assert(!!container.querySelector('#injected'));
render(container, getTemplate({ content: '<div id="booster">oh hai, again</div>' }));
assert(!!container.querySelector('#booster'));
container.remove();
});
// This is mainly for backwards compat, TBD if we deprecate or not.
it('html: updaters: repeat works when called with all arguments', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${repeat(items, item => item.id, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === baz);
assert(container.querySelector('#target').children[1] === foo);
assert(container.querySelector('#target').children[2] === bar);
render(container, getTemplate({ items: [{ id: 'bar'}, { id: 'baz' }, { id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === bar);
assert(container.querySelector('#target').children[1] === baz);
assert(container.querySelector('#target').children[2] === foo);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 2);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
render(container, getTemplate({ items: [{ id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [] }));
assert(container.querySelector('#target').childElementCount === 0);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== foo);
assert(container.querySelector('#target').children[1] !== bar);
assert(container.querySelector('#target').children[2] !== baz);
container.remove();
});
// This is mainly for backwards compat, TBD if we deprecate or not.
it('repeat works when called with all arguments', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${repeat(items, item => item.id, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === baz);
assert(container.querySelector('#target').children[1] === foo);
assert(container.querySelector('#target').children[2] === bar);
render(container, getTemplate({ items: [{ id: 'bar'}, { id: 'baz' }, { id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === bar);
assert(container.querySelector('#target').children[1] === baz);
assert(container.querySelector('#target').children[2] === foo);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 2);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
render(container, getTemplate({ items: [{ id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [] }));
assert(container.querySelector('#target').childElementCount === 0);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== foo);
assert(container.querySelector('#target').children[1] !== bar);
assert(container.querySelector('#target').children[2] !== baz);
container.remove();
});
it('html: updaters: repeat works when called with omitted lookup', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${repeat(items, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
it('repeat works when called with omitted lookup', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${repeat(items, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
// Because "lookup" is omitted, we don't expect DOM nodes to remain after a shift.
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== baz);
assert(container.querySelector('#target').children[1] !== foo);
assert(container.querySelector('#target').children[2] !== bar);
container.remove();
});
// Because "lookup" is omitted, we don't expect DOM nodes to remain after a shift.
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== baz);
assert(container.querySelector('#target').children[1] !== foo);
assert(container.querySelector('#target').children[2] !== bar);
container.remove();
});
it('html: updaters: map', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${map(items, item => item.id, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === baz);
assert(container.querySelector('#target').children[1] === foo);
assert(container.querySelector('#target').children[2] === bar);
render(container, getTemplate({ items: [{ id: 'bar'}, { id: 'baz' }, { id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === bar);
assert(container.querySelector('#target').children[1] === baz);
assert(container.querySelector('#target').children[2] === foo);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 2);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
render(container, getTemplate({ items: [{ id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [] }));
assert(container.querySelector('#target').childElementCount === 0);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== foo);
assert(container.querySelector('#target').children[1] !== bar);
assert(container.querySelector('#target').children[2] !== baz);
container.remove();
});
it('map', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${map(items, item => item.id, item => {
return html`<div id="${item.id}" class="item">${item.id}</div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
const foo = container.querySelector('#foo');
const bar = container.querySelector('#bar');
const baz = container.querySelector('#baz');
assert(container.querySelector('#target').childElementCount === 3);
assert(!!foo);
assert(!!bar);
assert(!!baz);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'baz' }, { id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === baz);
assert(container.querySelector('#target').children[1] === foo);
assert(container.querySelector('#target').children[2] === bar);
render(container, getTemplate({ items: [{ id: 'bar'}, { id: 'baz' }, { id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === bar);
assert(container.querySelector('#target').children[1] === baz);
assert(container.querySelector('#target').children[2] === foo);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
assert(container.querySelector('#target').children[2] === baz);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}] }));
assert(container.querySelector('#target').childElementCount === 2);
assert(container.querySelector('#target').children[0] === foo);
assert(container.querySelector('#target').children[1] === bar);
render(container, getTemplate({ items: [{ id: 'foo' }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [] }));
assert(container.querySelector('#target').childElementCount === 0);
render(container, getTemplate({ items: [{ id: 'foo' }, { id: 'bar'}, { id: 'baz' }] }));
assert(container.querySelector('#target').childElementCount === 3);
assert(container.querySelector('#target').children[0] !== foo);
assert(container.querySelector('#target').children[1] !== bar);
assert(container.querySelector('#target').children[2] !== baz);
container.remove();
});
it('html: updaters: map: template changes', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${map(items, item => item.id, item => {
return item.show ? html`<div id="${item.id}" class="item">${item.id}</div>` : html`<div id="${item.id}" class="item"></div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo', show: true }] }));
const foo = container.querySelector('#foo');
assert(container.querySelector('#target').childElementCount === 1);
assert(!!foo);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [{ id: 'foo', show: false }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(!!container.querySelector('#foo'));
assert(container.querySelector('#target').children[0] !== foo);
container.remove();
it('map: template changes', () => {
const getTemplate = ({ items }) => {
return html`
<div id="target">
${map(items, item => item.id, item => {
return item.show ? html`<div id="${item.id}" class="item">${item.id}</div>` : html`<div id="${item.id}" class="item"></div>`;
})}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ id: 'foo', show: true }] }));
const foo = container.querySelector('#foo');
assert(container.querySelector('#target').childElementCount === 1);
assert(!!foo);
assert(container.querySelector('#target').children[0] === foo);
render(container, getTemplate({ items: [{ id: 'foo', show: false }] }));
assert(container.querySelector('#target').childElementCount === 1);
assert(!!container.querySelector('#foo'));
assert(container.querySelector('#target').children[0] !== foo);
container.remove();
});
});
it('svg: renders a basic string', () => {
const getTemplate = ({ r, cx, cy }) => {
return svg`<circle id="target" r="${r}" cx="${cx}" cy="${cy}"/>`;
};
const container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
container.setAttribute('viewBox', '0 0 100 100');
container.setAttribute('style', 'height: 100px; width: 100px;');
document.body.append(container);
render(container, getTemplate({ r: 10, cx: 50, cy: 50 }));
assert(container.querySelector('#target').getBoundingClientRect().width === 20);
assert(container.querySelector('#target').getBoundingClientRect().height === 20);
render(container, getTemplate({ r: 5, cx: 50, cy: 50 }));
assert(container.querySelector('#target').getBoundingClientRect().width === 10);
assert(container.querySelector('#target').getBoundingClientRect().height === 10);
container.remove();
});
describe('svg rendering', () => {
it('renders a basic string', () => {
const getTemplate = ({ r, cx, cy }) => {
return svg`<circle id="target" r="${r}" cx="${cx}" cy="${cy}"/>`;
};
const container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
container.setAttribute('viewBox', '0 0 100 100');
container.setAttribute('style', 'height: 100px; width: 100px;');
document.body.append(container);
render(container, getTemplate({ r: 10, cx: 50, cy: 50 }));
assert(container.querySelector('#target').getBoundingClientRect().width === 20);
assert(container.querySelector('#target').getBoundingClientRect().height === 20);
render(container, getTemplate({ r: 5, cx: 50, cy: 50 }));
assert(container.querySelector('#target').getBoundingClientRect().width === 10);
assert(container.querySelector('#target').getBoundingClientRect().height === 10);
container.remove();
});
it('svg: renders lists', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 10 100"
style="width: 10px; height: 100px;">
${items.map((item, index) => {
return svg`<circle class="dot" r="${item.r}" cx="5" cy="${10 * (index + 1)}"></circle>`;
})}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ r: 1 }, { r: 2 }, { r: 3 }, { r: 4 }, { r: 5 }] }));
assert(container.querySelector('#target').childElementCount === 5);
assert(container.querySelector('#target').children[0].getBoundingClientRect().height = 2);
assert(container.querySelector('#target').children[1].getBoundingClientRect().height = 4);
assert(container.querySelector('#target').children[2].getBoundingClientRect().height = 6);
assert(container.querySelector('#target').children[3].getBoundingClientRect().height = 8);
assert(container.querySelector('#target').children[4].getBoundingClientRect().height = 10);
container.remove();
it('renders lists', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 10 100"
style="width: 10px; height: 100px;">
${items.map((item, index) => {
return svg`<circle class="dot" r="${item.r}" cx="5" cy="${10 * (index + 1)}"></circle>`;
})}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: [{ r: 1 }, { r: 2 }, { r: 3 }, { r: 4 }, { r: 5 }] }));
assert(container.querySelector('#target').childElementCount === 5);
assert(container.querySelector('#target').children[0].getBoundingClientRect().height = 2);
assert(container.querySelector('#target').children[1].getBoundingClientRect().height = 4);
assert(container.querySelector('#target').children[2].getBoundingClientRect().height = 6);
assert(container.querySelector('#target').children[3].getBoundingClientRect().height = 8);
assert(container.querySelector('#target').children[4].getBoundingClientRect().height = 10);
container.remove();
});
});
it('svg: updaters: unsafeSVG', () => {
const getTemplate = ({ content }) => {
return html`
<svg
id="target"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
style="width: 100px; height: 100px;">
${unsafeSVG(content)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: '<circle id="injected" r="10" cx="50" cy="50"></circle>' }));
assert(!!container.querySelector('#injected'));
assert(container.querySelector('#injected').getBoundingClientRect().height = 20);
assert(container.querySelector('#injected').getBoundingClientRect().width = 20);
render(container, getTemplate({ content: '<circle id="injected" r="5" cx="50" cy="50"></circle>' }));
assert(!!container.querySelector('#injected'));
assert(container.querySelector('#injected').getBoundingClientRect().height = 10);
assert(container.querySelector('#injected').getBoundingClientRect().width = 10);
container.remove();
describe('svg updaters', () => {
it('unsafeSVG', () => {
const getTemplate = ({ content }) => {
return html`
<svg
id="target"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
style="width: 100px; height: 100px;">
${unsafeSVG(content)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ content: '<circle id="injected" r="10" cx="50" cy="50"></circle>' }));
assert(!!container.querySelector('#injected'));
assert(container.querySelector('#injected').getBoundingClientRect().height = 20);
assert(container.querySelector('#injected').getBoundingClientRect().width = 20);
render(container, getTemplate({ content: '<circle id="injected" r="5" cx="50" cy="50"></circle>' }));
assert(!!container.querySelector('#injected'));
assert(container.querySelector('#injected').getBoundingClientRect().height = 10);
assert(container.querySelector('#injected').getBoundingClientRect().width = 10);
container.remove();
});
});
it('render errors: ifDefined: throws if used on a "boolean-attribute"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('rendering errors', () => {
describe('ifDefined', () => {
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: ifDefined: throws if used on a "property"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "property"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${ifDefined(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: ifDefined: throws if used with "content"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${ifDefined(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "content"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${ifDefined(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: ifDefined: throws if used with "text-content"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${ifDefined(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The ifDefined update must be used on an attribute, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${ifDefined(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
});
it('render errors: nullish: throws if used on a "boolean-attribute"', () => {
const expected = 'The nullish update must be used on an attribute, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('nullish', () => {
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The nullish update must be used on an attribute, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: nullish: throws if used on a "property"', () => {
const expected = 'The nullish update must be used on an attribute, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "property"', () => {
const expected = 'The nullish update must be used on an attribute, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${nullish(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: nullish: throws if used with "content"', () => {
const expected = 'The nullish update must be used on an attribute, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${nullish(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "content"', () => {
const expected = 'The nullish update must be used on an attribute, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${nullish(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: nullish: throws if used with "text-content"', () => {
const expected = 'The nullish update must be used on an attribute, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${nullish(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The nullish update must be used on an attribute, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${nullish(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
});
it('render errors: live: throws if used on an "attribute"', () => {
const expected = 'The live update must be used on a property, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${live(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('live', () => {
it('throws if used on an "attribute"', () => {
const expected = 'The live update must be used on a property, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${live(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: live: throws if used on a "boolean-attribute"', () => {
const expected = 'The live update must be used on a property, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${live(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The live update must be used on a property, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${live(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: live: throws if used with "content"', () => {
const expected = 'The live update must be used on a property, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${live(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "content"', () => {
const expected = 'The live update must be used on a property, not on content.';
const getTemplate = ({ maybe }) => {
return html`<div id="target">${live(maybe)}</div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: live: throws if used with "text-content"', () => {
const expected = 'The live update must be used on a property, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${live(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The live update must be used on a property, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${live(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
});
it('render errors: unsafeHTML: throws if used on an "attribute"', () => {
const expected = 'The unsafeHTML update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('unsafeHTML', () => {
it('throws if used on an "attribute"', () => {
const expected = 'The unsafeHTML update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeHTML: throws if used on a "boolean-attribute"', () => {
const expected = 'The unsafeHTML update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The unsafeHTML update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeHTML: throws if used with a "property"', () => {
const expected = 'The unsafeHTML update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with a "property"', () => {
const expected = 'The unsafeHTML update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${unsafeHTML(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeHTML: throws if used with "text-content"', () => {
const expected = 'The unsafeHTML update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${unsafeHTML(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The unsafeHTML update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${unsafeHTML(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeSVG: throws if used on an "attribute"', () => {
const expected = 'The unsafeSVG update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws for non-string value', () => {
const getTemplate = ({ content }) => {
return html`
<div id="target">
${unsafeHTML(content)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ content: null }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected unsafeHTML value "null".', error?.message);
container.remove();
});
});
it('render errors: unsafeSVG: throws if used on a "boolean-attribute"', () => {
const expected = 'The unsafeSVG update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('unsafeSVG', () => {
it('throws if used on an "attribute"', () => {
const expected = 'The unsafeSVG update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeSVG: throws if used with a "property"', () => {
const expected = 'The unsafeSVG update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The unsafeSVG update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: unsafeSVG: throws if used with "text-content"', () => {
const expected = 'The unsafeSVG update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${unsafeSVG(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with a "property"', () => {
const expected = 'The unsafeSVG update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${unsafeSVG(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws if identify is not a function', () => {
const expected = 'Unexpected map identify "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The unsafeSVG update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${unsafeSVG(maybe)}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws if callback is not a function', () => {
const expected = 'Unexpected map callback "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws for non-string value', () => {
const getTemplate = ({ content }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${unsafeSVG(content)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ content: null }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected unsafeSVG value "null".', error?.message);
container.remove();
});
});
it('render errors: map: throws if used on an "attribute"', () => {
const expected = 'The map update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('map', () => {
it('throws if identify is not a function', () => {
const expected = 'Unexpected map identify "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws if used on a "boolean-attribute"', () => {
const expected = 'The map update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if callback is not a function', () => {
const expected = 'Unexpected map callback "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws if used with a "property"', () => {
const expected = 'The map update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on an "attribute"', () => {
const expected = 'The map update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws if used with "text-content"', () => {
const expected = 'The map update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${map(maybe, () => {}, () => {})}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The map update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws if callback is not a function (1)', () => {
const expected = 'Unexpected repeat identify "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with a "property"', () => {
const expected = 'The map update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${map(maybe, () => {}, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws if callback is not a function (2)', () => {
const expected = 'Unexpected repeat callback "5" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe, 5)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The map update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${map(maybe, () => {}, () => {})}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws if used on an "attribute"', () => {
const expected = 'The repeat update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws for non-array value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, () => {}, () => html``)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: 5 }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "5".', error?.message);
container.remove();
});
it('render errors: repeat: throws if used on a "boolean-attribute"', () => {
const expected = 'The repeat update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws for non-template callback value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message);
container.remove();
});
it('render errors: repeat: throws if used with a "property"', () => {
const expected = 'The repeat update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('throws for non-template callback value (on re-render)', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ array: [{ id: 'foo', value: 'oh hai' }] }));
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message);
container.remove();
});
});
it('render errors: repeat: throws if used with "text-content"', () => {
const expected = 'The repeat update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${repeat(maybe, () => {})}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
describe('repeat', () => {
it('throws if callback is not a function (1)', () => {
const expected = 'Unexpected repeat identify "undefined" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws for non-array value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, () => {}, () => html``)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: 5 }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "5".', error?.message);
container.remove();
});
it('throws if callback is not a function (2)', () => {
const expected = 'Unexpected repeat callback "5" provided, expected a function.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe, 5)}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws for non-template callback value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message);
container.remove();
});
it('throws if used on an "attribute"', () => {
const expected = 'The repeat update must be used on content, not on an attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: map: throws for non-template callback value (on re-render)', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${map(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ array: [{ id: 'foo', value: 'oh hai' }] }));
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message);
container.remove();
});
it('throws if used on a "boolean-attribute"', () => {
const expected = 'The repeat update must be used on content, not on a boolean-attribute.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" ?maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws for non-array value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, () => {}, () => html``)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: 5 }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "5".', error?.message);
container.remove();
});
it('throws if used with a "property"', () => {
const expected = 'The repeat update must be used on content, not on a property.';
const getTemplate = ({ maybe }) => {
return html`<div id="target" .maybe="${repeat(maybe, () => {})}"></div>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws for non-template callback value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "null" provided by callback.', error?.message);
container.remove();
});
it('throws if used with "text-content"', () => {
const expected = 'The repeat update must be used on content, not on text-content.';
const getTemplate = ({ maybe }) => {
return html`<textarea id="target">${repeat(maybe, () => {})}</textarea>`;
};
const container = document.createElement('div');
document.body.append(container);
let actual;
try {
render(container, getTemplate({ maybe: 'yes' }));
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
container.remove();
});
it('render errors: repeat: throws for non-template callback value (on re-render)', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ array: [{ id: 'foo', value: 'oh hai' }] }));
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "null" provided by callback.', error?.message);
container.remove();
});
it('throws for non-array value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, () => {}, () => html``)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: 5 }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "5".', error?.message);
container.remove();
});
it('render errors: unsafeHTML: throws for non-string value', () => {
const getTemplate = ({ content }) => {
return html`
<div id="target">
${unsafeHTML(content)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ content: null }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected unsafeHTML value "null".', error?.message);
container.remove();
});
it('throws for non-template callback value', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "null" provided by callback.', error?.message);
container.remove();
});
it('render errors: unsafeSVG: throws for non-string value', () => {
const getTemplate = ({ content }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${unsafeSVG(content)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ content: null }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected unsafeSVG value "null".', error?.message);
container.remove();
});
it('throws for non-template callback value (on re-render)', () => {
const getTemplate = ({ array }) => {
return html`
<div id="target">
${repeat(array, item => item.id, item => item.value ? html`<div>${item.value}</div>` : null)}
</div>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ array: [{ id: 'foo', value: 'oh hai' }] }));
let error;
try {
render(container, getTemplate({ array: [{ id: 'foo', value: null }] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected repeat value "null" provided by callback.', error?.message);
container.remove();
});
});
it('render errors: template array: throws for non-template value', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${items.map(item => item ? html`<div>${item}</div>` : null)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ items: [null] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected array value "null" provided by callback.', error?.message);
container.remove();
describe('native array', () => {
it('throws for non-template value', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${items.map(item => item ? html`<div>${item}</div>` : null)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
let error;
try {
render(container, getTemplate({ items: [null] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected array value "null" provided by callback.', error?.message);
container.remove();
});
it('throws for non-template value on re-render', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${items.map(item => item ? html`<div>${item}</div>` : null)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo'] }));
let error;
try {
render(container, getTemplate({ items: [null] }));
} catch (e) {
error = e;
}
assert(error?.message === 'Unexpected array value "null" provided by callback.', error?.message);
container.remove();
});
});
});
it('render errors: template array (on re-render): throws for non-template value', () => {
const getTemplate = ({ items }) => {
return html`
<svg id="target" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
${items.map(item => item ? html`<div>${item}</div>` : null)}
</svg>
`;
};
const container = document.createElement('div');
document.body.append(container);
render(container, getTemplate({ items: ['foo'] }));
let error;
try {
render(container, getTemplate({ items: [null] }));
} catch (e) {
error = e;
describe('interface migration errors', () => {
const removedInterfaceNames = [
'asyncAppend', 'asyncReplace', 'cache', 'classMap', 'directive', 'guard',
'styleMap', 'templateContent', 'until',
];
for (const name of removedInterfaceNames) {
it(`warns that "${name}" no longer exists.`, () => {
const expected = `Removed "${name}" from default templating engine interface. Import and plug-in "lit-html" as your element's templating engine if you want this functionality.`;
let actual;
try {
XElement.templateEngine[name]();
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
});
}
assert(error?.message === 'Unexpected array value "null" provided by callback.', error?.message);
container.remove();
});
const removedInterfaceNames = [
'asyncAppend', 'asyncReplace', 'cache', 'classMap', 'directive', 'guard',
'styleMap', 'templateContent', 'until',
];
for (const name of removedInterfaceNames) {
it(`interface migration errors: "${name}" no longer exists.`, () => {
const expected = `Removed "${name}" from default templating engine interface. Import and plug-in "lit-html" as your element's templating engine if you want this functionality.`;
let actual;
try {
XElement.templateEngine[name]();
} catch (error) {
actual = error.message;
}
assert(!!actual, 'No error was thrown.');
assert(actual === expected, actual);
});
}
// Simply import / export so that we pin this version in one spot.
import { test, it, skip, todo, waitFor, assert, cover } from 'https://unpkg.com/@netflix/x-test@1.0.0-rc.16/x-test.js';
export { test, it, skip, todo, waitFor, assert, cover };
import { test, describe, it, waitFor, assert, coverage } from 'https://unpkg.com/@netflix/x-test@1.0.0-rc.20/x-test.js';
export { test, describe, it, waitFor, assert, coverage };

@@ -1065,5 +1065,5 @@ /** Base element class for creating custom elements. */

static #updaters = new WeakMap();
static #ATTRIBUTE = /<[a-zA-Z0-9-]+(?:[^/<>]|"[^"]*")* ([a-z][a-z-]*)="$/;
static #BOOLEAN_ATTRIBUTE = /<[a-zA-Z0-9-]+(?:[^/<>]|"[^"]*")* \?([a-z][a-z-]*)="$/;
static #PROPERTY = /<[a-zA-Z0-9-]+(?:[^/<>]|"[^"]*")* \.([a-z][a-zA-Z0-9_]*)="$/;
static #ATTRIBUTE = /<[a-zA-Z0-9-]+[^>]* ([a-z][a-z-]*)="$/;
static #BOOLEAN_ATTRIBUTE = /<[a-zA-Z0-9-]+[^>]* \?([a-z][a-z-]*)="$/;
static #PROPERTY = /<[a-zA-Z0-9-]+[^>]* \.([a-z][a-zA-Z0-9_]*)="$/;

@@ -1070,0 +1070,0 @@ #type = null;

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc