diff --git a/packages/testing-helpers/package.json b/packages/testing-helpers/package.json index b44f3c54..d2c9d04d 100644 --- a/packages/testing-helpers/package.json +++ b/packages/testing-helpers/package.json @@ -29,8 +29,12 @@ "fixtures" ], "peerDependencies": { + "lit-element": "^2.2.1", "lit-html": "^1.0.0" }, + "dependencies": { + "@open-wc/scoped-elements": "^1.1.0" + }, "devDependencies": { "lit-html": "^1.0.0", "webpack-merge": "^4.1.5" diff --git a/packages/testing-helpers/src/fixture-no-side-effect.js b/packages/testing-helpers/src/fixture-no-side-effect.js index e51d2ad4..3dd8f0ea 100644 --- a/packages/testing-helpers/src/fixture-no-side-effect.js +++ b/packages/testing-helpers/src/fixture-no-side-effect.js @@ -5,6 +5,8 @@ import { isValidRenderArg } from './lib.js'; /** * @typedef {object} FixtureOptions * @property {Element} [parentNode] optional parent node to render the fixture's template to + * @property {import('@open-wc/scoped-elements').ScopedElementsMap} [scopedElements] optional scoped-elements + * definition map */ /** diff --git a/packages/testing-helpers/src/litFixture.js b/packages/testing-helpers/src/litFixture.js index 74b67274..c47ccc08 100644 --- a/packages/testing-helpers/src/litFixture.js +++ b/packages/testing-helpers/src/litFixture.js @@ -3,6 +3,7 @@ import { fixtureWrapper } from './fixtureWrapper.js'; import { render } from './lit-html.js'; import { elementUpdated } from './elementUpdated.js'; import { NODE_TYPES } from './lib.js'; +import { getScopedElementsTemplate } from './scopedElementsWrapper.js'; /** * @typedef { @@ -35,7 +36,12 @@ const isUsefulNode = ({ nodeType, textContent }) => { */ export function litFixtureSync(template, options = {}) { const wrapper = fixtureWrapper(options.parentNode); - render(template, wrapper); + + render( + options.scopedElements ? getScopedElementsTemplate(template, options.scopedElements) : template, + wrapper, + ); + if (template instanceof TemplateResult) { return /** @type {T} */ (wrapper.firstElementChild); } @@ -52,9 +58,17 @@ export function litFixtureSync(template, options = {}) { * @param {import('./fixture-no-side-effect.js').FixtureOptions} [options] * @returns {Promise} */ -export async function litFixture(template, options) { +export async function litFixture(template, options = {}) { const el = litFixtureSync(template, options); await elementUpdated(el); + + if (options.scopedElements) { + const [node] = Array.from(el.shadowRoot.childNodes).filter(isUsefulNode); + await elementUpdated(/** @type {T} */ (node)); + + return /** @type {T} */ (node); + } + // @ts-ignore return el; } diff --git a/packages/testing-helpers/src/scopedElementsWrapper.js b/packages/testing-helpers/src/scopedElementsWrapper.js new file mode 100644 index 00000000..12b7a007 --- /dev/null +++ b/packages/testing-helpers/src/scopedElementsWrapper.js @@ -0,0 +1,69 @@ +import { ScopedElementsMixin } from '@open-wc/scoped-elements'; +import { html, LitElement, TemplateResult } from 'lit-element'; +import { nothing } from 'lit-html'; +import { isIterable } from './lib.js'; + +/** @typedef {import('@open-wc/scoped-elements').ScopedElementsMap} ScopedElementsMap */ + +const transform = template => { + if (isIterable(template)) { + return [...template].map(v => transform(v)); + } + + if (template instanceof TemplateResult) { + return html(template.strings, ...template.values); + } + + return template; +}; + +class ScopedElementsTestWrapper extends ScopedElementsMixin(LitElement) { + static get properties() { + return { + scopedElements: { type: Object }, + template: { type: Object }, + }; + } + + constructor() { + super(); + + /** @type {ScopedElementsMap} */ + this.scopedElements = {}; + + /** @type {TemplateResult|{}} */ + this.template = nothing; + } + + async firstUpdated() { + // @ts-ignore + await super.firstUpdated(); + + Object.keys(this.scopedElements).forEach(key => + this.defineScopedElement(key, this.scopedElements[key]), + ); + } + + render() { + return transform(this.template); + } +} + +// @ts-ignore +customElements.define('scoped-elements-test-wrapper', ScopedElementsTestWrapper); + +/** + * Wraps the template inside a scopedElements component + * + * @param {import('./litFixture').LitHTMLRenderable} template + * @param {ScopedElementsMap} scopedElements + * @returns {TemplateResult} + */ +export function getScopedElementsTemplate(template, scopedElements) { + return html` + + `; +} diff --git a/packages/testing-helpers/src/stringFixture.js b/packages/testing-helpers/src/stringFixture.js index 2735fc8b..bf0b54c3 100644 --- a/packages/testing-helpers/src/stringFixture.js +++ b/packages/testing-helpers/src/stringFixture.js @@ -1,5 +1,7 @@ +import { html } from 'lit-html'; import { fixtureWrapper } from './fixtureWrapper.js'; import { elementUpdated } from './elementUpdated.js'; +import { litFixture } from './litFixture.js'; /** * Setups an element synchronously from the provided string template and puts it in the DOM. @@ -25,7 +27,12 @@ export function stringFixtureSync(template, options = {}) { * @param {import('./fixture-no-side-effect.js').FixtureOptions} [options] * @returns {Promise} */ -export async function stringFixture(template, options) { +export async function stringFixture(template, options = {}) { + if (options.scopedElements) { + // @ts-ignore + return litFixture(html([template]), options); + } + const el = stringFixtureSync(template, options); await elementUpdated(el); // @ts-ignore diff --git a/packages/testing-helpers/test/fixture.test.js b/packages/testing-helpers/test/fixture.test.js index a1134539..ac716316 100644 --- a/packages/testing-helpers/test/fixture.test.js +++ b/packages/testing-helpers/test/fixture.test.js @@ -1,6 +1,7 @@ // @ts-ignore import sinon from 'sinon'; // @ts-ignore +import { html as litHtml, LitElement } from 'lit-element'; import { expect } from './setup.js'; import { cachedWrappers } from '../src/fixtureWrapper.js'; import { defineCE } from '../src/helpers.js'; @@ -361,4 +362,42 @@ describe('fixtureSync & fixture', () => { await fixture(html`<${litTag}>`); expect(counter).to.equal(2); }); + + it('supports scoped-elements', async () => { + class TestClass extends LitElement { + static get properties() { + return { + foo: { type: String }, + }; + } + + constructor() { + super(); + + this.foo = ''; + } + + render() { + return litHtml` +
${this.foo}
+ `; + } + } + + const elString = await fixture('', { + scopedElements: { + 'test-class': TestClass, + }, + }); + + expect(elString).shadowDom.to.equal('
bar
'); + + const elLit = await fixture(html` `, { + scopedElements: { + 'test-class': TestClass, + }, + }); + + expect(elLit).shadowDom.to.equal('
bar
'); + }); });