diff --git a/src/testing/puppeteer/puppeteer-element.ts b/src/testing/puppeteer/puppeteer-element.ts index b1c72e89..9ad9729a 100644 --- a/src/testing/puppeteer/puppeteer-element.ts +++ b/src/testing/puppeteer/puppeteer-element.ts @@ -549,6 +549,11 @@ export async function find(page: pd.E2EPageInternal, rootHandle: puppeteer.Eleme if (typeof selector === 'string' && selector.includes('>>>')) { const handle = await page.$(selector); + + if (!handle) { + return null; + } + const elm = new E2EElement(page, handle); await elm.e2eSync(); return elm; diff --git a/test/end-to-end/src/components.d.ts b/test/end-to-end/src/components.d.ts index 2baa7dfc..7d5115da 100644 --- a/test/end-to-end/src/components.d.ts +++ b/test/end-to-end/src/components.d.ts @@ -36,6 +36,10 @@ export namespace Components { } interface ElementCmp { } + interface EmptyCmp { + } + interface EmptyCmpShadow { + } interface EnvData { } interface EventCmp { @@ -187,6 +191,18 @@ declare global { prototype: HTMLElementCmpElement; new (): HTMLElementCmpElement; }; + interface HTMLEmptyCmpElement extends Components.EmptyCmp, HTMLRindoElement { + } + var HTMLEmptyCmpElement: { + prototype: HTMLEmptyCmpElement; + new (): HTMLEmptyCmpElement; + }; + interface HTMLEmptyCmpShadowElement extends Components.EmptyCmpShadow, HTMLRindoElement { + } + var HTMLEmptyCmpShadowElement: { + prototype: HTMLEmptyCmpShadowElement; + new (): HTMLEmptyCmpShadowElement; + }; interface HTMLEnvDataElement extends Components.EnvData, HTMLRindoElement { } var HTMLEnvDataElement: { @@ -284,6 +300,8 @@ declare global { "dom-interaction": HTMLDomInteractionElement; "dom-visible": HTMLDomVisibleElement; "element-cmp": HTMLElementCmpElement; + "empty-cmp": HTMLEmptyCmpElement; + "empty-cmp-shadow": HTMLEmptyCmpShadowElement; "env-data": HTMLEnvDataElement; "event-cmp": HTMLEventCmpElement; "import-assets": HTMLImportAssetsElement; @@ -328,6 +346,10 @@ declare namespace LocalJSX { } interface ElementCmp { } + interface EmptyCmp { + } + interface EmptyCmpShadow { + } interface EnvData { } interface EventCmp { @@ -376,6 +398,8 @@ declare namespace LocalJSX { "dom-interaction": DomInteraction; "dom-visible": DomVisible; "element-cmp": ElementCmp; + "empty-cmp": EmptyCmp; + "empty-cmp-shadow": EmptyCmpShadow; "env-data": EnvData; "event-cmp": EventCmp; "import-assets": ImportAssets; @@ -408,6 +432,8 @@ declare module "@rindo/core" { "dom-interaction": LocalJSX.DomInteraction & JSXBase.HTMLAttributes; "dom-visible": LocalJSX.DomVisible & JSXBase.HTMLAttributes; "element-cmp": LocalJSX.ElementCmp & JSXBase.HTMLAttributes; + "empty-cmp": LocalJSX.EmptyCmp & JSXBase.HTMLAttributes; + "empty-cmp-shadow": LocalJSX.EmptyCmpShadow & JSXBase.HTMLAttributes; "env-data": LocalJSX.EnvData & JSXBase.HTMLAttributes; "event-cmp": LocalJSX.EventCmp & JSXBase.HTMLAttributes; "import-assets": LocalJSX.ImportAssets & JSXBase.HTMLAttributes; diff --git a/test/end-to-end/src/non-existent-element/empty-cmp-shadow.tsx b/test/end-to-end/src/non-existent-element/empty-cmp-shadow.tsx new file mode 100644 index 00000000..e97b624f --- /dev/null +++ b/test/end-to-end/src/non-existent-element/empty-cmp-shadow.tsx @@ -0,0 +1,11 @@ +import { Component, h, Host } from '@rindo/core'; + +@Component({ + tag: 'empty-cmp-shadow', + shadow: true, +}) +export class EmptyComponentShadow { + render() { + return I have no children!; + } +} diff --git a/test/end-to-end/src/non-existent-element/empty-cmp.tsx b/test/end-to-end/src/non-existent-element/empty-cmp.tsx new file mode 100644 index 00000000..46b875fe --- /dev/null +++ b/test/end-to-end/src/non-existent-element/empty-cmp.tsx @@ -0,0 +1,12 @@ +import { Component, h, Host } from '@rindo/core'; + +@Component({ + tag: 'empty-cmp', + shadow: false, + scoped: false, +}) +export class EmptyComponent { + render() { + return I have no children!; + } +} diff --git a/test/end-to-end/src/non-existent-element/non-existent-element.e2e.ts b/test/end-to-end/src/non-existent-element/non-existent-element.e2e.ts new file mode 100644 index 00000000..fa2f341b --- /dev/null +++ b/test/end-to-end/src/non-existent-element/non-existent-element.e2e.ts @@ -0,0 +1,55 @@ +import { newE2EPage } from '@rindo/core/testing'; + +describe('Querying non-existent element(s)', () => { + describe('Shadow DOM', () => { + it('returns `null` if the element does not exist', async () => { + // create a new puppeteer page + const page = await newE2EPage({ + html: ` + + `, + }); + + const elm = await page.find('empty-cmp-shadow >>> .non-existent'); + expect(elm).toBeNull(); + }); + + it('returns an empty array if no elements match the selector', async () => { + // create a new puppeteer page + const page = await newE2EPage({ + html: ` + + `, + }); + + const elm = await page.findAll('empty-cmp-shadow >>> .non-existent'); + expect(elm).toEqual([]); + }); + }); + + describe('Light DOM', () => { + it('returns `null` if the element does not exist', async () => { + // create a new puppeteer page + const page = await newE2EPage({ + html: ` + + `, + }); + + const elm = await page.find('empty-cmp >>> .non-existent'); + expect(elm).toBeNull(); + }); + + it('returns an empty array if no elements match the selector', async () => { + // create a new puppeteer page + const page = await newE2EPage({ + html: ` + + `, + }); + + const elm = await page.findAll('empty-cmp >>> .non-existent'); + expect(elm).toEqual([]); + }); + }); +}); diff --git a/test/end-to-end/src/non-existent-element/readme.md b/test/end-to-end/src/non-existent-element/readme.md new file mode 100644 index 00000000..947c2869 --- /dev/null +++ b/test/end-to-end/src/non-existent-element/readme.md @@ -0,0 +1,10 @@ +# empty-cmp + + + + + + +---------------------------------------------- + +*Built with [RindoJS](https://rindojs.web.app/)*