import { Assert, UnitTest } from '@ephox/bedrock-client'; import { Testable } from '@ephox/dispute'; import { Arr } from '@ephox/katamari'; import fc from 'fast-check'; import * as Insert from 'ephox/sugar/api/dom/Insert'; import * as Remove from 'ephox/sugar/api/dom/Remove'; import * as DomEvent from 'ephox/sugar/api/events/DomEvent'; import * as SugarBody from 'ephox/sugar/api/node/SugarBody'; import * as SugarDocument from 'ephox/sugar/api/node/SugarDocument'; import { SugarElement } from 'ephox/sugar/api/node/SugarElement'; import { tElement } from 'ephox/sugar/api/node/SugarElementInstances'; import * as SugarHead from 'ephox/sugar/api/node/SugarHead'; import * as SugarNode from 'ephox/sugar/api/node/SugarNode'; import * as SugarShadowDom from 'ephox/sugar/api/node/SugarShadowDom'; import * as Attribute from 'ephox/sugar/api/properties/Attribute'; import * as SelectorFind from 'ephox/sugar/api/search/SelectorFind'; import { htmlBlockTagName, htmlInlineTagName } from 'ephox/sugar/test/Arbitrary'; import { setupShadowRoot, withIframe, withNormalElement, withShadowElement, withShadowElementInMode } from 'ephox/sugar/test/WithHelpers'; type RootNode = SugarShadowDom.RootNode; UnitTest.test('ShadowDom - SelectorFind.descendant', () => { // https://fast-check.dev/docs/migration-guide/from-3.x-to-4.x/#hexa-or-hexastring const items = '0123456789abcdef'; const hexa = () => fc.integer({ min: 0, max: 15 }).map((n) => items[n]); fc.assert(fc.property(htmlBlockTagName(), htmlInlineTagName(), fc.string({ unit: hexa() }), (block, inline, text) => { withShadowElement((ss) => { const id = 'theid'; const inner = SugarElement.fromHtml(`<${block}>

hello<${inline} id="${id}">${text}

`); Insert.append(ss, inner); const frog: SugarElement = SelectorFind.descendant(ss, `#${id}`).getOrDie('Element not found'); Assert.eq('textcontent', text, frog.dom.textContent); }); })); }); const shouldBeShadowRoot = (n: RootNode) => { Assert.eq('should be shadow root', true, SugarShadowDom.isShadowRoot(n)); Assert.eq('should not be document', false, SugarNode.isDocument(n)); }; const shouldBeDocument = (n: RootNode) => { Assert.eq('should not be shadow root', false, SugarShadowDom.isShadowRoot(n)); Assert.eq('should be document', true, SugarNode.isDocument(n)); }; UnitTest.test('getRootNode === document on normal element in dom', () => { withNormalElement((div) => { Assert.eq('should be document', SugarDocument.getDocument(), SugarShadowDom.getRootNode(div), tElement()); }); }); UnitTest.test('getRootNode(document) === document on normal element in dom', () => { withNormalElement(() => { Assert.eq('should be document', SugarDocument.getDocument(), SugarShadowDom.getRootNode(SugarDocument.getDocument()), tElement()); }); }); UnitTest.test('isDocument(getRootNode) === true on normal element in dom', () => { withNormalElement((div) => { shouldBeDocument(SugarShadowDom.getRootNode(div)); }); }); UnitTest.test('document is document', () => { shouldBeDocument(SugarDocument.getDocument()); }); UnitTest.test('getRootNode === shadowroot on element in shadow root', () => { withShadowElement((sr, innerDiv) => { Assert.eq('should be shadowroot', sr, SugarShadowDom.getRootNode(innerDiv), tElement()); }); }); UnitTest.test('getRootNode(shadowroot) === shadowroot', () => { withShadowElement((sr) => { Assert.eq('should be shadowroot', sr, sr, tElement()); }); }); UnitTest.test('shadow root is shadow root', () => { withShadowElement((sr, innerDiv) => { shouldBeShadowRoot(SugarShadowDom.getRootNode(innerDiv)); shouldBeShadowRoot(sr); }); }); UnitTest.test('getRootNode in iframe', () => { withIframe((div, iframe, cw) => { Assert.eq('should be inner doc', cw.document, SugarShadowDom.getRootNode(div).dom, Testable.tStrict); }); }); UnitTest.test('isDocument in iframe', () => { withIframe((div, iframe, cw) => { shouldBeDocument(SugarElement.fromDom(cw.document)); }); }); UnitTest.test('stylecontainer is shadow root for shadow root', () => { withShadowElement((sr) => { Assert.eq('Should be shadow root', sr, SugarShadowDom.getStyleContainer(sr), tElement()); }); }); UnitTest.test('stylecontainer is head for document', () => { Assert.eq('Should be head', SugarHead.getHead(SugarDocument.getDocument()), SugarShadowDom.getStyleContainer(SugarDocument.getDocument()), tElement()); }); UnitTest.test('contentcontainer is shadow root for shadow root', () => { withShadowElement((sr) => { Assert.eq('Should be shadow root', sr, SugarShadowDom.getContentContainer(sr), tElement()); }); }); UnitTest.test('contentcontainer is body for document', () => { Assert.eq('Should be head', SugarBody.getBody(SugarDocument.getDocument()), SugarShadowDom.getContentContainer(SugarDocument.getDocument()), tElement()); }); UnitTest.test('getShadowHost', () => { withShadowElement((sr, inner, sh) => { Assert.eq('Should be shadow host', sh, SugarShadowDom.getShadowHost(sr), tElement()); }); }); UnitTest.test('isOpenShadowRoot / isClosedShadowRoot', () => { withShadowElementInMode('open', (sr) => { Assert.eq('open shadow root is open', true, SugarShadowDom.isOpenShadowRoot(sr)); Assert.eq('open shadow root is not closed', false, SugarShadowDom.isClosedShadowRoot(sr)); }); withShadowElementInMode('closed', (sr) => { Assert.eq('closed shadow root is not open', false, SugarShadowDom.isOpenShadowRoot(sr)); Assert.eq('closed shadow root is closed', true, SugarShadowDom.isClosedShadowRoot(sr)); }); }); const checkOriginalEventTarget = (mode: 'open' | 'closed', success: UnitTest.SuccessCallback, failure: UnitTest.FailureCallback): void => { const { innerDiv, shadowHost } = setupShadowRoot(mode); const input = (desc: string, parent: SugarElement) => { const i = SugarElement.fromTag('input'); Attribute.setAll(i, { 'type': 'text', 'data-description': desc }); Insert.append(parent, i); return i; }; const i1 = input('i2', SugarBody.body()); const i2 = input('i2', innerDiv); i1.dom.click(); const unbinder = DomEvent.bind(SugarBody.body(), 'click', (evt) => { try { const expected = mode === 'open' ? i2 : shadowHost; Assert.eq('Check event target', expected, evt.target, tElement()); unbinder.unbind(); Remove.remove(i1); Remove.remove(shadowHost); success(); } catch (e: any) { failure(e); } }); i2.dom.click(); }; UnitTest.asynctest('getOriginalEventTarget on a closed shadow root', (success, failure) => { checkOriginalEventTarget('closed', success, failure); }); UnitTest.asynctest('getOriginalEventTarget on an open shadow root', (success, failure) => { checkOriginalEventTarget('open', success, failure); }); UnitTest.test('isOpenShadowHost on open shadow host', () => { withShadowElementInMode('open', (shadowRoot, innerDiv, shadowHost) => () => { Assert.eq('The open shadow host is an open shadow host', true, SugarShadowDom.isOpenShadowHost(shadowHost)); Assert.eq('The innerDiv is not an open shadow host', false, SugarShadowDom.isOpenShadowHost(innerDiv)); }); }); UnitTest.test('isOpenShadowHost on closed shadow host', () => { withShadowElementInMode('closed', (shadowRoot, innerDiv, shadowHost) => () => { Assert.eq('The closed shadow host is an open shadow host', false, SugarShadowDom.isOpenShadowHost(shadowHost)); Assert.eq('The innerDiv is not an open shadow host', false, SugarShadowDom.isOpenShadowHost(innerDiv)); }); }); UnitTest.test('withShadowElement gives us open and closed roots', () => { const roots: Array> = []; withShadowElement((sr) => { roots.push(sr); }); Assert.eq('open then closed', [ 'open', 'closed' ], Arr.map(roots, (r) => (r.dom as any).mode )); });