From 8ec8788a466d03c6a4ae29873add67496377ee79 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Wed, 20 Nov 2024 01:16:15 -0800 Subject: [PATCH] =?UTF-8?q?Final=20=E2=80=9C2.x=E2=80=9D=20interface.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This mainly deletes things we deprecated in “1.x” which enables a smooth transition from “1.x” >> “2.x”. --- test/test-template-engine.js | 1142 +++------------------------------- x-element.js | 372 +---------- 2 files changed, 96 insertions(+), 1418 deletions(-) diff --git a/test/test-template-engine.js b/test/test-template-engine.js index 6becbfc..c298b9c 100644 --- a/test/test-template-engine.js +++ b/test/test-template-engine.js @@ -1,32 +1,8 @@ import XElement from '../x-element.js'; import { assert, describe, it } from './x-test.js'; -// Long-term interface. const { render, html, svg, map } = XElement.templateEngine; -// Deprecated interface. We will eventually delete these. -const { ifDefined, nullish, repeat, live, unsafeHTML, unsafeSVG } = XElement.templateEngine; - -// Overwrite console warn for testing so we don’t get spammed with our own -// deprecation warnings. -const seen = new Set(); -const warn = console.warn; // eslint-disable-line no-console -const localMessages = [ - 'Deprecated "ifDefined" from default templating engine interface.', - 'Deprecated "nullish" from default templating engine interface.', - 'Deprecated "live" from default templating engine interface.', - 'Deprecated "unsafeHTML" from default templating engine interface.', - 'Deprecated "unsafeSVG" from default templating engine interface.', - 'Deprecated "repeat" from default templating engine interface.', -]; -console.warn = (...args) => { // eslint-disable-line no-console - if (!localMessages.includes(args[0]?.message)) { - warn(...args); - } else { - seen.add(args[0].message); - } -}; - describe('html rendering', () => { it('renders basic string', () => { const getTemplate = () => { @@ -543,202 +519,6 @@ describe('html rendering', () => { }); describe('html updaters', () => { - // This is mainly for backwards compat, "nullish" is likely a better match. - it('ifDefined', () => { - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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`
`; - }; - 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('live', () => { - const getTemplate = ({ alive, dead }) => { - return html`
`; - }; - 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('unsafeHTML', () => { - const getTemplate = ({ content }) => { - return html`
${unsafeHTML(content)}
`; - }; - const container = document.createElement('div'); - document.body.append(container); - render(container, getTemplate({ content: '
oh hai
' })); - assert(!!container.querySelector('#injected')); - render(container, getTemplate({ content: '
oh hai, again
' })); - assert(!!container.querySelector('#booster')); - 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` -
- ${repeat(items, item => item.id, item => { - return html`
${item.id}
`; - })} -
- `; - }; - 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('repeat works when called with omitted lookup', () => { - const getTemplate = ({ items }) => { - return html` -
- ${repeat(items, item => { - return html`
${item.id}
`; - })} -
- `; - }; - 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(); - }); - - it('repeat re-runs each time', () => { - const getTemplate = ({ items, lookup }) => { - return html` -
- -
- `; - }; - const container = document.createElement('div'); - document.body.append(container); - const items = [{ id: 'a' }, { id: 'b'}, { id: 'c' }]; - let lookup = { a: 'foo', b: 'bar', c: 'baz' }; - render(container, getTemplate({ items, lookup })); - assert(container.querySelector('#target').childElementCount === 3); - assert(container.querySelector('#a').textContent === 'foo'); - assert(container.querySelector('#b').textContent === 'bar'); - assert(container.querySelector('#c').textContent === 'baz'); - lookup = { a: 'fizzle', b: 'bop', c: 'fuzz' }; - render(container, getTemplate({ items, lookup })); - assert(container.querySelector('#target').childElementCount === 3); - assert(container.querySelector('#a').textContent === 'fizzle'); - assert(container.querySelector('#b').textContent === 'bop'); - assert(container.querySelector('#c').textContent === 'fuzz'); - container.remove(); - }); - it('map', () => { const getTemplate = ({ items }) => { return html` @@ -1055,33 +835,6 @@ describe('svg rendering', () => { }); }); -describe('svg updaters', () => { - it('unsafeSVG', () => { - const getTemplate = ({ content }) => { - return html` - - ${unsafeSVG(content)} - - `; - }; - const container = document.createElement('div'); - document.body.append(container); - render(container, getTemplate({ content: '' })); - assert(!!container.querySelector('#injected')); - assert(container.querySelector('#injected').getBoundingClientRect().height = 20); - assert(container.querySelector('#injected').getBoundingClientRect().width = 20); - render(container, getTemplate({ content: '' })); - assert(!!container.querySelector('#injected')); - assert(container.querySelector('#injected').getBoundingClientRect().height = 10); - assert(container.querySelector('#injected').getBoundingClientRect().width = 10); - container.remove(); - }); -}); - describe('rendering errors', () => { describe('templating', () => { it('throws when attempting to interpolate within a style tag', () => { @@ -1251,17 +1004,17 @@ describe('rendering errors', () => { }); }); - describe('ifDefined', () => { - it('throws if used on a "boolean"', () => { - const expected = 'The ifDefined update must be used on an attribute, not on a boolean attribute.'; + 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`
`; + return html`
`; }; const container = document.createElement('div'); document.body.append(container); let actual; try { - render(container, getTemplate({ maybe: 'yes' })); + render(container, getTemplate({ maybe: ['yes'] })); } catch (error) { actual = error.message; } @@ -1270,16 +1023,16 @@ describe('rendering errors', () => { container.remove(); }); - it('throws if used on a "property"', () => { - const expected = 'The ifDefined update must be used on an attribute, not on a property.'; + it('throws if callback is not a function', () => { + const expected = 'Unexpected map callback "undefined" provided, expected a function.'; const getTemplate = ({ maybe }) => { - return html`
`; + return html`
`; }; const container = document.createElement('div'); document.body.append(container); let actual; try { - render(container, getTemplate({ maybe: 'yes' })); + render(container, getTemplate({ maybe: ['yes'] })); } catch (error) { actual = error.message; } @@ -1288,833 +1041,108 @@ describe('rendering errors', () => { 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`
${ifDefined(maybe)}
`; + it('throws for duplicate identify responses on initial render', () => { + const getTemplate = ({ array }) => { + return html` +
+ ${map(array, () => 'foo', () => html``)} +
+ `; }; const container = document.createElement('div'); document.body.append(container); - let actual; + let error; try { - render(container, getTemplate({ maybe: 'yes' })); - } catch (error) { - actual = error.message; + render(container, getTemplate({ array: [1, 2, 3] })); + } catch (e) { + error = e; } - assert(!!actual, 'No error was thrown.'); - assert(actual === expected, actual); + assert(error?.message === 'Unexpected duplicate value returned from identify callback "foo".', error?.message); container.remove(); }); - it('throws if used with "text"', () => { - const expected = 'The ifDefined update must be used on an attribute, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; + it('throws for duplicate identify responses on subsequent render', () => { + const getTemplate = ({ array }) => { + return html` +
+ ${map(array, item => item, () => html``)} +
+ `; }; const container = document.createElement('div'); document.body.append(container); - let actual; + let error; + render(container, getTemplate({ array: [1, 2, 3] })); try { - render(container, getTemplate({ maybe: 'yes' })); - } catch (error) { - actual = error.message; + render(container, getTemplate({ array: [1, 2, 3, 4, 4] })); + } catch (e) { + error = e; } - assert(!!actual, 'No error was thrown.'); - assert(actual === expected, actual); + assert(error?.message === 'Unexpected duplicate value returned from identify callback "4".', error?.message); container.remove(); }); - }); - describe('nullish', () => { - it('throws if used on a "boolean"', () => { - const expected = 'The nullish update must be used on an attribute, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; + it('throws for non-array value', () => { + const getTemplate = ({ array }) => { + return html` +
+ ${map(array, () => {}, () => html``)} +
+ `; }; const container = document.createElement('div'); document.body.append(container); - let actual; + let error; try { - render(container, getTemplate({ maybe: 'yes' })); - } catch (error) { - actual = error.message; + render(container, getTemplate({ array: 5 })); + } catch (e) { + error = e; } - assert(!!actual, 'No error was thrown.'); - assert(actual === expected, actual); + assert(error?.message === 'Unexpected map items "5" provided, expected an array.', error?.message); 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`
`; + it('throws for non-template callback value', () => { + const getTemplate = ({ array }) => { + return html` +
+ ${map(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} +
+ `; }; const container = document.createElement('div'); document.body.append(container); - let actual; + let error; try { - render(container, getTemplate({ maybe: 'yes' })); - } catch (error) { - actual = error.message; + render(container, getTemplate({ array: [{ id: 'foo', value: null }] })); + } catch (e) { + error = e; } - assert(!!actual, 'No error was thrown.'); - assert(actual === expected, actual); + assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message); 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`
${nullish(maybe)}
`; + it('throws for non-template callback value (on re-render)', () => { + const getTemplate = ({ array }) => { + return html` +
+ ${map(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} +
+ `; }; const container = document.createElement('div'); document.body.append(container); - let actual; + render(container, getTemplate({ array: [{ id: 'foo', value: 'oh hai' }] })); + let error; try { - render(container, getTemplate({ maybe: 'yes' })); - } catch (error) { - actual = error.message; + render(container, getTemplate({ array: [{ id: 'foo', value: null }] })); + } catch (e) { + error = e; } - assert(!!actual, 'No error was thrown.'); - assert(actual === expected, actual); + assert(error?.message === 'Unexpected map value "null" provided by callback.', error?.message); container.remove(); }); - - it('throws if used with "text"', () => { - const expected = 'The nullish update must be used on an attribute, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The live update must be used on a property, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 "defined"', () => { - const expected = 'The live update must be used on a property, not on a defined attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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`
${live(maybe)}
`; - }; - 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"', () => { - const expected = 'The live update must be used on a property, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The unsafeHTML update must be used on content, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 "defined"', () => { - const expected = 'The unsafeHTML update must be used on content, not on a defined attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The unsafeHTML update must be used on content, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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` -
- ${unsafeHTML(content)} -
- `; - }; - 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(); - }); - }); - - 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`
`; - }; - 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"', () => { - const expected = 'The unsafeSVG update must be used on content, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 "defined"', () => { - const expected = 'The unsafeSVG update must be used on content, not on a defined attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The unsafeSVG update must be used on content, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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` - - ${unsafeSVG(content)} - - `; - }; - 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(); - }); - }); - - 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`
`; - }; - 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`
`; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The map update must be used on content, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 "defined"', () => { - const expected = 'The map update must be used on content, not on a defined attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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`
`; - }; - 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"', () => { - const expected = 'The map update must be used on content, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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 duplicate identify responses on initial render', () => { - const getTemplate = ({ array }) => { - return html` -
- ${map(array, () => 'foo', () => html``)} -
- `; - }; - const container = document.createElement('div'); - document.body.append(container); - let error; - try { - render(container, getTemplate({ array: [1, 2, 3] })); - } catch (e) { - error = e; - } - assert(error?.message === 'Unexpected duplicate value returned from identify callback "foo".', error?.message); - container.remove(); - }); - - it('throws for duplicate identify responses on subsequent render', () => { - const getTemplate = ({ array }) => { - return html` -
- ${map(array, item => item, () => html``)} -
- `; - }; - const container = document.createElement('div'); - document.body.append(container); - let error; - render(container, getTemplate({ array: [1, 2, 3] })); - try { - render(container, getTemplate({ array: [1, 2, 3, 4, 4] })); - } catch (e) { - error = e; - } - assert(error?.message === 'Unexpected duplicate value returned from identify callback "4".', error?.message); - container.remove(); - }); - - it('throws for non-array value', () => { - const getTemplate = ({ array }) => { - return html` -
- ${map(array, () => {}, () => html``)} -
- `; - }; - 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 items "5" provided, expected an array.', error?.message); - container.remove(); - }); - - it('throws for non-template callback value', () => { - const getTemplate = ({ array }) => { - return html` -
- ${map(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} -
- `; - }; - 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 for non-template callback value (on re-render)', () => { - const getTemplate = ({ array }) => { - return html` -
- ${map(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} -
- `; - }; - 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(); - }); - }); - - 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`
`; - }; - 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 (2)', () => { - const expected = 'Unexpected repeat callback "5" provided, expected a function.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 repeat update must be used on content, not on an attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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"', () => { - const expected = 'The repeat update must be used on content, not on a boolean attribute.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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 repeat update must be used on content, not on a property.'; - const getTemplate = ({ maybe }) => { - return html`
`; - }; - 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"', () => { - const expected = 'The repeat update must be used on content, not on text content.'; - const getTemplate = ({ maybe }) => { - return html``; - }; - 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` -
- ${repeat(array, () => {}, () => html``)} -
- `; - }; - 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 items "5" provided, expected an array.', error?.message); - container.remove(); - }); - - it('throws for non-template callback value', () => { - const getTemplate = ({ array }) => { - return html` -
- ${repeat(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} -
- `; - }; - 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 for non-template callback value (on re-render)', () => { - const getTemplate = ({ array }) => { - return html` -
- ${repeat(array, item => item.id, item => item.value ? html`
${item.value}
` : null)} -
- `; - }; - 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(); - }); - }); + }); describe('native array', () => { it('throws for non-template value', () => { @@ -2159,29 +1187,3 @@ describe('rendering errors', () => { }); }); }); - -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); - }); - } -}); - -it('confirm that deprecation warnings are still necessary', () => { - for (const message of localMessages) { - assert(seen.has(message), `Unused deprecation warning: ${message}`); - } -}); diff --git a/x-element.js b/x-element.js index 4e738fc..9efc17f 100644 --- a/x-element.js +++ b/x-element.js @@ -1041,38 +1041,18 @@ class TemplateEngine { // Mapping of opaque references to internal result objects. static #symbolToResult = new WeakMap(); - // Mapping of opaque references to internal update objects. - static #symbolToUpdate = new WeakMap(); + // Mapping of opaque references to internal mapping objects. + static #symbolToMapping = new WeakMap(); /** * Default template engine interface — what you get inside “template”. * @type {{[key: string]: Function}} */ static interface = Object.freeze({ - // Long-term interface. render: TemplateEngine.render, html: TemplateEngine.html, svg: TemplateEngine.svg, map: TemplateEngine.map, - - // Deprecated interface. - live: TemplateEngine.#interfaceDeprecated('live', TemplateEngine.live), - unsafeHTML: TemplateEngine.#interfaceDeprecated('unsafeHTML', TemplateEngine.unsafeHTML), - unsafeSVG: TemplateEngine.#interfaceDeprecated('unsafeSVG', TemplateEngine.unsafeSVG), - ifDefined: TemplateEngine.#interfaceDeprecated('ifDefined', TemplateEngine.ifDefined), - nullish: TemplateEngine.#interfaceDeprecated('nullish', TemplateEngine.nullish), - repeat: TemplateEngine.#interfaceDeprecated('repeat', TemplateEngine.repeat), - - // Removed interface. - asyncAppend: TemplateEngine.#interfaceRemoved('asyncAppend'), - asyncReplace: TemplateEngine.#interfaceRemoved('asyncReplace'), - cache: TemplateEngine.#interfaceRemoved('cache'), - classMap: TemplateEngine.#interfaceRemoved('classMap'), - directive: TemplateEngine.#interfaceRemoved('directive'), - guard: TemplateEngine.#interfaceRemoved('guard'), - styleMap: TemplateEngine.#interfaceRemoved('styleMap'), - templateContent: TemplateEngine.#interfaceRemoved('templateContent'), - until: TemplateEngine.#interfaceRemoved('until'), }); /** @@ -1130,106 +1110,6 @@ class TemplateEngine { } } - /** - * Updater to manage an attribute which may be undefined. - * In the following example, the "ifDefined" updater will remove the - * attribute if it's undefined. Else, it sets the key-value pair. - * ```js - * html``; - * ``` - * @deprecated - * @param {any} value - * @returns {any} - */ - static ifDefined(value) { - const symbol = Object.create(null); - const updater = TemplateEngine.#ifDefined; - TemplateEngine.#symbolToUpdate.set(symbol, { updater, value }); - return symbol; - } - - /** - * Updater to manage an attribute which may not exist. - * In the following example, the "nullish" updater will remove the - * attribute if it's nullish. Else, it sets the key-value pair. - * ```js - * html``; - * ``` - * @deprecated - * @param {any} value - * @returns {any} - */ - static nullish(value) { - const symbol = Object.create(null); - const updater = TemplateEngine.#nullish; - const update = { updater, value }; - TemplateEngine.#symbolToUpdate.set(symbol, update); - return symbol; - } - - /** - * Updater to manage a property which may change outside the template engine. - * Typically, properties are declaratively managed from state and efficient - * value checking is used (i.e., "value !== lastValue"). However, if DOM state - * is expected to change, the "live" updater can be used to essentially change - * this check to "value !== node[property]". - * ```js - * html``; - * ``` - * @deprecated - * @param {any} value - * @returns {any} - */ - static live(value) { - const symbol = Object.create(null); - const updater = TemplateEngine.#live; - const update = { updater, value }; - TemplateEngine.#symbolToUpdate.set(symbol, update); - return symbol; - } - - /** - * Updater to inject trusted HTML into the DOM. - * Use with caution. The "unsafeHTML" updater allows arbitrary input to be - * parsed as HTML and injected into the DOM. - * ```js - * html`
${unsafeHTML(obj.trustedMarkup)}
`; - * ``` - * @deprecated - * @param {any} value - * @returns {any} - */ - static unsafeHTML(value) { - const symbol = Object.create(null); - const updater = TemplateEngine.#unsafeHTML; - const update = { updater, value }; - TemplateEngine.#symbolToUpdate.set(symbol, update); - return symbol; - } - - /** - * Updater to inject trusted SVG into the DOM. - * Use with caution. The "unsafeSVG" updater allows arbitrary input to be - * parsed as SVG and injected into the DOM. - * ```js - * html` - * - * ${unsafeSVG(obj.trustedMarkup)} - * - * `; - * ``` - * @deprecated - * @param {any} value - * @returns {any} - */ - static unsafeSVG(value) { - const symbol = Object.create(null); - const updater = TemplateEngine.#unsafeSVG; - const update = { updater, value }; - TemplateEngine.#symbolToUpdate.set(symbol, update); - return symbol; - } - /** * Updater to manage a keyed array of templates (allows for DOM reuse). * ```js @@ -1256,103 +1136,15 @@ class TemplateEngine { } const symbol = Object.create(null); const value = items; - const updater = TemplateEngine.#map; - const update = { updater, value, identify, callback }; - TemplateEngine.#symbolToUpdate.set(symbol, update); - return symbol; - } - - /** - * Shim for prior "repeat" function. Use "map". - * @deprecated - * @param {any[]} items - * @param {Function} identify - * @param {Function} [callback] - * @returns {any} - */ - static repeat(items, identify, callback) { - if (arguments.length === 2) { - callback = identify; - identify = null; - } - if (!Array.isArray(items)) { - throw new Error(`Unexpected repeat items "${items}" provided, expected an array.`); - } - if (arguments.length !== 2 && typeof identify !== 'function') { - throw new Error(`Unexpected repeat identify "${identify}" provided, expected a function.`); - } else if (typeof callback !== 'function') { - throw new Error(`Unexpected repeat callback "${callback}" provided, expected a function.`); - } - const symbol = Object.create(null); - const value = items; - const updater = TemplateEngine.#repeat; - const update = { updater, value, identify, callback }; - TemplateEngine.#symbolToUpdate.set(symbol, update); + const mapping = { value, identify, callback }; + TemplateEngine.#symbolToMapping.set(symbol, mapping); return symbol; } - // Deprecated. Will remove in future release. - static #ifDefined(node, name, value, lastValue) { - if (value !== lastValue) { - value === undefined || value === null - ? node.removeAttribute(name) - : node.setAttribute(name, value); - } - } - - // Deprecated. Will remove in future release. - static #nullish(node, name, value, lastValue) { - if (value !== lastValue) { - value === undefined || value === null - ? node.removeAttribute(name) - : node.setAttribute(name, value); - } - } - - // Deprecated. Will remove in future release. - static #live(node, name, value) { - if (node[name] !== value) { - node[name] = value; - } - } - - // Deprecated. Will remove in future release. - static #unsafeHTML(node, startNode, value, lastValue) { - if (value !== lastValue) { - if (typeof value === 'string') { - const template = document.createElement('template'); - template.innerHTML = value; - TemplateEngine.#removeBetween(startNode, node); - TemplateEngine.#insertAllBefore(node.parentNode, node, template.content.childNodes); - } else { - throw new Error(`Unexpected unsafeHTML value "${value}".`); - } - } - } - - // Deprecated. Will remove in future release. - static #unsafeSVG(node, startNode, value, lastValue) { - if (value !== lastValue) { - if (typeof value === 'string') { - const template = document.createElement('template'); - template.innerHTML = `${value}`; - TemplateEngine.#removeBetween(startNode, node); - TemplateEngine.#insertAllBefore(node.parentNode, node, template.content.firstChild.childNodes); - } else { - throw new Error(`Unexpected unsafeSVG value "${value}".`); - } - } - } - static #map(node, startNode, value, identify, callback) { TemplateEngine.#mapInputs(node, startNode, identify, callback, value, 'map'); } - // Deprecated. Will remove in future release. - static #repeat(node, startNode, value, identify, callback) { - TemplateEngine.#mapInputs(node, startNode, identify, callback, value, 'repeat'); - } - // Walk through each string from our tagged template function “strings” array // in a stateful way so that we know what kind of bindings are implied at // each interpolated value. @@ -1681,66 +1473,28 @@ class TemplateEngine { } static #commitAttribute(node, name, value, lastValue) { - const update = TemplateEngine.#symbolToUpdate.get(value); - const lastUpdate = TemplateEngine.#symbolToUpdate.get(lastValue); - if (update) { - switch (update.updater) { - case TemplateEngine.#ifDefined: - TemplateEngine.#ifDefined(node, name, update.value, lastUpdate?.value); - break; - case TemplateEngine.#nullish: - TemplateEngine.#nullish(node, name, update.value, lastUpdate?.value); - break; - default: - TemplateEngine.#throwUpdaterError(update.updater, 'attribute'); - break; - } - } else { - if (value !== lastValue) { - node.setAttribute(name, value); - } + if (value !== lastValue) { + node.setAttribute(name, value); } } static #commitBoolean(node, name, value, lastValue) { - const update = TemplateEngine.#symbolToUpdate.get(value); - if (update) { - TemplateEngine.#throwUpdaterError(update.updater, 'boolean'); - } else { - if (value !== lastValue) { - value ? node.setAttribute(name, '') : node.removeAttribute(name); - } + if (value !== lastValue) { + value ? node.setAttribute(name, '') : node.removeAttribute(name); } } static #commitDefined(node, name, value, lastValue) { - const update = TemplateEngine.#symbolToUpdate.get(value); - if (update) { - TemplateEngine.#throwUpdaterError(update.updater, 'defined'); - } else { - if (value !== lastValue) { - value === undefined || value === null - ? node.removeAttribute(name) - : node.setAttribute(name, value); - } + if (value !== lastValue) { + value === undefined || value === null + ? node.removeAttribute(name) + : node.setAttribute(name, value); } } static #commitProperty(node, name, value, lastValue) { - const update = TemplateEngine.#symbolToUpdate.get(value); - if (update) { - switch (update.updater) { - case TemplateEngine.#live: - TemplateEngine.#live(node, name, update.value); - break; - default: - TemplateEngine.#throwUpdaterError(update.updater, 'property'); - break; - } - } else { - if (value !== lastValue) { - node[name] = value; - } + if (value !== lastValue) { + node[name] = value; } } @@ -1748,10 +1502,8 @@ class TemplateEngine { const introspection = TemplateEngine.#getValueIntrospection(value); const lastIntrospection = TemplateEngine.#getValueIntrospection(lastValue); if ( - lastValue !== TemplateEngine.#UNSET && ( - introspection?.category !== lastIntrospection?.category || - introspection?.update?.updater !== lastIntrospection?.update?.updater - ) + lastValue !== TemplateEngine.#UNSET && + introspection?.category !== lastIntrospection?.category ) { // Reset content under certain conditions. E.g., `map(…)` >> `null`. const state = TemplateEngine.#setIfMissing(TemplateEngine.#nodeToState, node, () => ({})); @@ -1760,26 +1512,9 @@ class TemplateEngine { TemplateEngine.#clearObject(state); TemplateEngine.#clearObject(arrayState); } - if (introspection?.category === 'update') { - const { update } = introspection; - const lastUpdate = lastIntrospection?.update; - switch (update.updater) { - case TemplateEngine.#map: - TemplateEngine.#map(node, startNode, update.value, update.identify, update.callback); - break; - case TemplateEngine.#repeat: - TemplateEngine.#repeat(node, startNode, update.value, update.identify, update.callback); - break; - case TemplateEngine.#unsafeHTML: - TemplateEngine.#unsafeHTML(node, startNode, update.value, lastUpdate?.value); - break; - case TemplateEngine.#unsafeSVG: - TemplateEngine.#unsafeSVG(node, startNode, update.value, lastUpdate?.value); - break; - default: - TemplateEngine.#throwUpdaterError(update.updater, 'content'); - break; - } + if (introspection?.category === 'mapping') { + const { mapping } = introspection; + TemplateEngine.#map(node, startNode, mapping.value, mapping.identify, mapping.callback); } else { if (value !== lastValue) { if (introspection?.category === 'result') { @@ -1823,13 +1558,8 @@ class TemplateEngine { } static #commitText(node, value, lastValue) { - const update = TemplateEngine.#symbolToUpdate.get(value); - if (update) { - TemplateEngine.#throwUpdaterError(update.updater, 'text'); - } else { - if (value !== lastValue) { - node.textContent = value; - } + if (value !== lastValue) { + node.textContent = value; } } @@ -1897,9 +1627,6 @@ class TemplateEngine { TemplateEngine.#commit(result); } - // TODO: Revisit this concept when we delete deprecated interfaces. Once that - // happens, the _only_ updater available for content is `map`, and we may be - // able to make this more performant. static #getValueIntrospection(value) { if (Array.isArray(value)) { return { category: 'array' }; @@ -1910,35 +1637,14 @@ class TemplateEngine { if (result) { return { category: 'result', result }; } else { - const update = TemplateEngine.#symbolToUpdate.get(value); - if (update) { - return { category: 'update', update }; + const mapping = TemplateEngine.#symbolToMapping.get(value); + if (mapping) { + return { category: 'mapping', mapping }; } } } } - static #throwUpdaterError(updater, type) { - switch (updater) { - case TemplateEngine.#map: - throw new Error(`The map update must be used on ${TemplateEngine.#getTypeText('content')}, not on ${TemplateEngine.#getTypeText(type)}.`); - - // We’ll delete these updaters later. - case TemplateEngine.#live: - throw new Error(`The live update must be used on ${TemplateEngine.#getTypeText('property')}, not on ${TemplateEngine.#getTypeText(type)}.`); - case TemplateEngine.#unsafeHTML: - throw new Error(`The unsafeHTML update must be used on ${TemplateEngine.#getTypeText('content')}, not on ${TemplateEngine.#getTypeText(type)}.`); - case TemplateEngine.#unsafeSVG: - throw new Error(`The unsafeSVG update must be used on ${TemplateEngine.#getTypeText('content')}, not on ${TemplateEngine.#getTypeText(type)}.`); - case TemplateEngine.#ifDefined: - throw new Error(`The ifDefined update must be used on ${TemplateEngine.#getTypeText('attribute')}, not on ${TemplateEngine.#getTypeText(type)}.`); - case TemplateEngine.#nullish: - throw new Error(`The nullish update must be used on ${TemplateEngine.#getTypeText('attribute')}, not on ${TemplateEngine.#getTypeText(type)}.`); - case TemplateEngine.#repeat: - throw new Error(`The repeat update must be used on ${TemplateEngine.#getTypeText('content')}, not on ${TemplateEngine.#getTypeText(type)}.`); - } - } - static #cannotReuseResult(result, newResult) { return ( result?.type !== newResult.type || result?.strings !== newResult.strings @@ -1997,34 +1703,4 @@ class TemplateEngine { } return value; } - - static #getTypeText(type) { - switch (type) { - case 'attribute': return 'an attribute'; - case 'boolean': return 'a boolean attribute'; - case 'defined': return 'a defined attribute'; - case 'property': return 'a property'; - case 'content': return 'content'; - case 'text': return 'text content'; - } - } - - static #interfaceDeprecatedStacks = new Set(); - static #interfaceDeprecated(name, callback) { - return (...args) => { - const error = new Error(`Deprecated "${name}" from default templating engine interface.`); - const stack = error.stack; - if (!this.#interfaceDeprecatedStacks.has(stack)) { - this.#interfaceDeprecatedStacks.add(stack); - console.warn(error); // eslint-disable-line no-console - } - return callback(...args); - }; - } - - static #interfaceRemoved(name) { - return () => { - throw new Error(`Removed "${name}" from default templating engine interface. Import and plug-in "lit-html" as your element's templating engine if you want this functionality.`); - }; - } }