diff --git a/src/mock-doc/document.ts b/src/mock-doc/document.ts index e97c1bd3b3c..f77196c6040 100644 --- a/src/mock-doc/document.ts +++ b/src/mock-doc/document.ts @@ -5,20 +5,18 @@ import { MockDocumentFragment } from './document-fragment'; import { MockDocumentTypeNode } from './document-type-node'; import { createElement, createElementNS, MockBaseElement } from './element'; import { resetEventListeners } from './event'; -import { MockElement, MockHTMLElement, MockTextNode, resetElement } from './node'; +import { MockElement, MockHTMLElement, MockNode, MockTextNode, resetElement } from './node'; import { parseHtmlToFragment } from './parse-html'; import { parseDocumentUtil } from './parse-util'; import { MockWindow } from './window'; -export class MockDocument extends MockHTMLElement { +export class MockDocument extends MockNode { defaultView: any; cookie: string; referrer: string; constructor(html: string | boolean = null, win: any = null) { - super(null, null); - this.nodeName = NODE_NAMES.DOCUMENT_NODE; - this.nodeType = NODE_TYPES.DOCUMENT_NODE; + super(null, NODE_TYPES.DOCUMENT_NODE, NODE_NAMES.DOCUMENT_NODE, null); this.defaultView = win; this.cookie = ''; this.referrer = ''; @@ -42,17 +40,13 @@ export class MockDocument extends MockHTMLElement { } } - override get dir() { + get dir() { return this.documentElement.dir; } - override set dir(value: string) { + set dir(value: string) { this.documentElement.dir = value; } - override get localName(): undefined { - return undefined; - } - get location() { if (this.defaultView != null) { return (this.defaultView as Window).location; @@ -225,14 +219,14 @@ export class MockDocument extends MockHTMLElement { return getElementsByName(this, elmName.toLowerCase()); } - override get title() { + get title() { const title = this.head.childNodes.find((elm) => elm.nodeName === 'TITLE') as MockElement; if (title != null && typeof title.textContent === 'string') { return title.textContent.trim(); } return ''; } - override set title(value: string) { + set title(value: string) { const head = this.head; let title = head.childNodes.find((elm) => elm.nodeName === 'TITLE') as MockElement; if (title == null) { @@ -297,7 +291,7 @@ const DOC_KEY_KEEPERS = new Set([ '_shadowRoot', ]); -export function getElementById(elm: MockElement, id: string): MockElement { +export function getElementById(elm: MockElement | MockDocument, id: string): MockElement { const children = elm.children; for (let i = 0, ii = children.length; i < ii; i++) { const childElm = children[i]; @@ -312,7 +306,7 @@ export function getElementById(elm: MockElement, id: string): MockElement { return null; } -function getElementsByName(elm: MockElement, elmName: string, foundElms: MockElement[] = []) { +function getElementsByName(elm: MockElement | MockDocument, elmName: string, foundElms: MockElement[] = []) { const children = elm.children; for (let i = 0, ii = children.length; i < ii; i++) { const childElm = children[i]; diff --git a/src/mock-doc/event.ts b/src/mock-doc/event.ts index 156e47eb939..eefc2b7e675 100644 --- a/src/mock-doc/event.ts +++ b/src/mock-doc/event.ts @@ -43,10 +43,10 @@ export class MockEvent { * @ref https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath * @returns a composed path of the event */ - composedPath(): MockElement[] { - const composedPath: MockElement[] = []; + composedPath(): (MockElement | MockDocument)[] { + const composedPath: (MockElement | MockDocument)[] = []; - let currentElement = this.target; + let currentElement: MockElement | MockDocument = this.target; while (currentElement) { composedPath.push(currentElement); @@ -63,7 +63,7 @@ export class MockEvent { * with the document object instead of the parent element since the parent element * is `null` for HTML elements. */ - if (currentElement.parentElement == null && currentElement.tagName === 'HTML') { + if (currentElement.parentElement == null && 'tagName' in currentElement && currentElement.tagName === 'HTML') { currentElement = currentElement.ownerDocument; } else { currentElement = currentElement.parentElement; diff --git a/src/mock-doc/node.ts b/src/mock-doc/node.ts index 6c775831f86..02082003f93 100644 --- a/src/mock-doc/node.ts +++ b/src/mock-doc/node.ts @@ -33,6 +33,36 @@ export class MockNode { this.childNodes = []; } + get children(): MockElement[] { + return this.childNodes.filter((n) => n.nodeType === NODE_TYPES.ELEMENT_NODE) as MockElement[]; + } + + get childElementCount() { + return this.childNodes.filter((n) => n.nodeType === NODE_TYPES.ELEMENT_NODE).length; + } + + get firstElementChild(): MockElement | null { + return this.children[0] || null; + } + + addEventListener(type: string, handler: (ev?: any) => void) { + addEventListener(this, type, handler); + } + + removeEventListener(type: string, handler: any) { + removeEventListener(this, type, handler); + } + + dispatchEvent(ev: MockEvent) { + return dispatchEvent(this, ev); + } + + getElementsByTagName(tagName: string) { + const results: MockElement[] = []; + getElementsByTagName(this, tagName.toLowerCase(), results); + return results; + } + appendChild(newNode: MockNode) { if (newNode.nodeType === NODE_TYPES.DOCUMENT_FRAGMENT_NODE) { const nodes = newNode.childNodes.slice(); @@ -195,6 +225,14 @@ export class MockNode { return null; } + querySelector(selector: string) { + return selectOne(selector, this); + } + + querySelectorAll(selector: string) { + return selectAll(selector, this); + } + get textContent() { return this._nodeValue ?? ''; } @@ -249,10 +287,6 @@ Testing components with ElementInternals is fully supported in e2e tests.`, this.__attributeMap = null; } - addEventListener(type: string, handler: (ev?: any) => void) { - addEventListener(this, type, handler); - } - attachShadow(_opts: ShadowRootInit) { const shadowRoot = this.ownerDocument.createDocumentFragment(); this.shadowRoot = shadowRoot; @@ -309,14 +343,6 @@ Testing components with ElementInternals is fully supported in e2e tests.`, this.__attributeMap = attrs; } - get children() { - return this.childNodes.filter((n) => n.nodeType === NODE_TYPES.ELEMENT_NODE) as MockElement[]; - } - - get childElementCount() { - return this.childNodes.filter((n) => n.nodeType === NODE_TYPES.ELEMENT_NODE).length; - } - get className() { return this.getAttributeNS(null, 'class') || ''; } @@ -341,7 +367,7 @@ Testing components with ElementInternals is fully supported in e2e tests.`, closest(selector: string) { let elm = this; while (elm != null) { - if (elm.matches(selector)) { + if ('matches' in elm && elm.matches(selector)) { return elm; } elm = elm.parentNode as any; @@ -360,14 +386,6 @@ Testing components with ElementInternals is fully supported in e2e tests.`, this.setAttributeNS(null, 'dir', value); } - dispatchEvent(ev: MockEvent) { - return dispatchEvent(this, ev); - } - - get firstElementChild(): MockElement | null { - return this.children[0] || null; - } - focus(_options?: { preventScroll?: boolean }) { dispatchEvent( this, @@ -613,20 +631,6 @@ Testing components with ElementInternals is fully supported in e2e tests.`, return results; } - getElementsByTagName(tagName: string) { - const results: MockElement[] = []; - getElementsByTagName(this, tagName.toLowerCase(), results); - return results; - } - - querySelector(selector: string) { - return selectOne(selector, this); - } - - querySelectorAll(selector: string) { - return selectAll(selector, this); - } - removeAttribute(attrName: string) { if (attrName === 'style') { delete this.__style; @@ -651,10 +655,6 @@ Testing components with ElementInternals is fully supported in e2e tests.`, } } - removeEventListener(type: string, handler: any) { - removeEventListener(this, type, handler); - } - setAttribute(attrName: string, value: any) { if (attrName === 'style') { this.style = value; @@ -1059,7 +1059,7 @@ function getElementsByClassName(elm: MockElement, classNames: string[], foundElm } } -function getElementsByTagName(elm: MockElement, tagName: string, foundElms: MockElement[]) { +function getElementsByTagName(elm: MockNode, tagName: string, foundElms: MockElement[]) { const children = elm.children; for (let i = 0, ii = children.length; i < ii; i++) { const childElm = children[i]; diff --git a/src/mock-doc/selector.ts b/src/mock-doc/selector.ts index 9bde4810bba..8105eb8f3e8 100644 --- a/src/mock-doc/selector.ts +++ b/src/mock-doc/selector.ts @@ -1,4 +1,4 @@ -import { MockElement } from './node'; +import { MockNode } from './node'; import jQuery from './third-party/jquery'; /** @@ -8,7 +8,7 @@ import jQuery from './third-party/jquery'; * @param elm an element within which to find matching elements * @returns whether the element matches the selector */ -export function matches(selector: string, elm: MockElement): boolean { +export function matches(selector: string, elm: MockNode): boolean { try { const r = jQuery.find(selector, undefined, undefined, [elm]); return r.length > 0; @@ -25,7 +25,7 @@ export function matches(selector: string, elm: MockElement): boolean { * @param elm the element within which to find a matching element * @returns the first matching element, or null if none is found */ -export function selectOne(selector: string, elm: MockElement) { +export function selectOne(selector: string, elm: MockNode) { try { const r = jQuery.find(selector, elm, undefined, undefined); return r[0] || null; @@ -42,7 +42,7 @@ export function selectOne(selector: string, elm: MockElement) { * @param elm an element within which to find matching elements * @returns all matching elements */ -export function selectAll(selector: string, elm: MockElement): any { +export function selectAll(selector: string, elm: MockNode): any { try { return jQuery.find(selector, elm, undefined, undefined); } catch (e) { diff --git a/src/mock-doc/test/element.spec.ts b/src/mock-doc/test/element.spec.ts index 26ff322b745..2c1eade8048 100644 --- a/src/mock-doc/test/element.spec.ts +++ b/src/mock-doc/test/element.spec.ts @@ -511,7 +511,6 @@ describe('element', () => { expect(doc.createElement('input').localName).toBe('input'); expect(doc.createElement('a').localName).toBe('a'); expect(doc.createElement('datalist').localName).toBe('datalist'); - expect(doc.localName).toBe(undefined); expect(doc.createElement('svg').localName).toBe('svg'); expect((document.childNodes[1] as any).localName).toBe('html'); }); diff --git a/src/mock-doc/test/selector.spec.ts b/src/mock-doc/test/selector.spec.ts index 4fde447d0f0..c2454313c2d 100644 --- a/src/mock-doc/test/selector.spec.ts +++ b/src/mock-doc/test/selector.spec.ts @@ -238,7 +238,7 @@ describe('selector', () => { expect(() => doc.querySelector(selector)).toThrow(expectedMessage); expect(() => doc.querySelectorAll(selector)).toThrow(expectedMessage); - expect(() => doc.matches(selector)).toThrow(expectedMessage); + expect(() => doc.body.matches(selector)).toThrow(expectedMessage); }); it('should error for combinations of problematic selectors', () => {