diff --git a/src/__node_tests__/index.js b/src/__node_tests__/index.js index 6c663360..6714639c 100644 --- a/src/__node_tests__/index.js +++ b/src/__node_tests__/index.js @@ -1,6 +1,14 @@ import {JSDOM} from 'jsdom' +import {configure} from '../config' import * as dtl from '../' +beforeEach(() => { + // reset back to original config + configure({ + queryAllElements: (element, query) => element.querySelectorAll(query), + }) +}) + test('works without a global dom', async () => { const container = new JSDOM(` @@ -77,6 +85,194 @@ test('works without a browser context on a dom node (JSDOM Fragment)', () => { `) }) +test('works with a custom configured element query for shadow dom elements', async () => { + const window = new JSDOM(` + + + + + + `).window + const document = window.document + const container = document.body + + // create custom element as system under test + window.customElements.define( + 'example-input', + class extends window.HTMLElement { + constructor() { + super() + const shadow = this.attachShadow({mode: 'open'}) + + const div = document.createElement('div') + const label = document.createElement('label') + label.setAttribute('for', 'invisible-from-outer-dom') + label.innerHTML = + 'Visible in browser, invisible for traditional queries' + const input = document.createElement('input') + input.setAttribute('id', 'invisible-from-outer-dom') + div.appendChild(label) + div.appendChild(input) + shadow.appendChild(div) + } + }, + ) + + // Given the default configuration is used + + // When querying for the label + // Then it is not in the document + expect( + dtl.queryByLabelText( + container, + /Visible in browser, invisible for traditional queries/i, + ), + ).not.toBeInTheDocument() + + // Given I have a naive query that allows searching shadow dom + const queryMeAndChildrenAndShadow = (element, query) => [ + ...element.querySelectorAll(`:scope > ${query}`), + ...[...element.children].reduce( + (result, child) => [ + ...result, + ...queryMeAndChildrenAndShadow(child, query), + ], + [], + ), + ...(element.shadowRoot?.querySelectorAll(query) ?? []), + ] + + // When I configure the testing tools to use it + configure({ + queryAllElements: queryMeAndChildrenAndShadow, + }) + + // Then it is part of the document + expect( + dtl.queryByLabelText( + container, + /Visible in browser, invisible for traditional queries/i, + ), + ).toBeInTheDocument() + + // And it returns the expected item + expect( + dtl.getByLabelText( + container, + /Visible in browser, invisible for traditional queries/i, + ), + ).toMatchInlineSnapshot(` + + `) +}) + +test('fails with a cross-boundary request for an element query for shadow dom elements with an appropriate error message', async () => { + const window = new JSDOM(` + + + + + + `).window + const document = window.document + const container = document.body + + window.customElements.define( + 'example-input', + class extends window.HTMLElement { + constructor() { + super() + const shadow = this.attachShadow({mode: 'open'}) + + const div = document.createElement('div') + const label = document.createElement('label') + label.setAttribute('for', 'invisible-from-outer-dom') + label.innerHTML = + 'Visible in browser, invisible for traditional queries' + const input = document.createElement('nested-example-input') + label.appendChild(input) + div.appendChild(label) + shadow.appendChild(div) + } + }, + ) + + window.customElements.define( + 'nested-example-input', + class extends window.HTMLElement { + static formAssociated = true + constructor() { + super() + const shadow = this.attachShadow({mode: 'open'}) + + const form = document.createElement('form') + const input = document.createElement('input') + input.setAttribute('id', 'invisible-from-outer-dom') + input.setAttribute('type', 'text') + form.appendChild(input) + shadow.appendChild(form) + } + }, + ) + + // Given I have a naive query that allows searching shadow dom + const queryDeep = (element, query) => + [ + ...element.querySelectorAll(`:scope > ${query}`), + ...(element.shadowRoot ? queryDeep(element.shadowRoot, query) : []), + ...[...element.children].reduce( + (result, child) => [...result, ...queryDeep(child, query)], + [], + ), + ].filter(item => !!item) + const queryAll = (element, query) => [ + ...element.querySelectorAll(query), + ...queryDeep(element, query), + ] + const queryMeAndChildrenAndShadow = (_, query) => + queryAll(document, query).find(element => !!element) + const queryMeAndChildrenAndShadowAll = (_, query) => [ + ...queryAll(document, query), + ] + + // When I configure the testing tools to use it + configure({ + queryElement: queryMeAndChildrenAndShadow, + queryAllElements: queryMeAndChildrenAndShadowAll, + }) + + // Then it should throw an error informing me that the input is currently non-labelable + // While the error message is not correct, the scenario is currently mostly relevant when + // extending the query mechanism with shadow dom, so it's not something that the framework + // can foresee at the moment. https://github.com/WICG/webcomponents/issues/917 will hopefully + // resolve this issue + expect(() => + dtl.getAllByLabelText( + container, + /Visible in browser, invisible for traditional queries/i, + ), + ).toThrowErrorMatchingInlineSnapshot(` + Found a label with the text of: /Visible in browser, invisible for traditional queries/i, however the element associated with this label () is non-labellable [https://html.spec.whatwg.org/multipage/forms.html#category-label]. If you really need to label a , you can use aria-label or aria-labelledby instead. + + Ignored nodes: comments,