import { IQuery, IQueryConditionFn } from '../common/IQuery' import { IBuildConfig, ISettings } from '../common/IConfig' import { IDriver } from '../common/IDriver' import { Deferred } from '../types/Deferred' import { JSDOM } from 'jsdom'; import { dfr_resolve } from '../utils/dfr'; import { NetworkDriver } from '../fetch/NetworkDriver' export class JsdomQuery extends IQuery { waitForElement(selector: string, mix?: { visible?: boolean; check?: IQueryConditionFn; } | IQueryConditionFn): IQuery { throw new Error('Method not implemented.'); } protected _onFn(node: Element, type: string, cb: Function): Promise { throw new Error('Method not implemented.'); } protected _onOnceFn(node: Element, type: string, cb: Function): Promise { throw new Error('Method not implemented.'); } protected _offFn(node: Element, type: string, cb: Function): Promise { throw new Error('Method not implemented.'); } hasClassFn (node: Element, name: string): Deferred { return dfr_resolve(node.classList.contains(name)); } addClassFn (node: Element, name: string): Deferred { node.classList.add(name); return dfr_resolve(); } removeClassFn(node: Element, name: string): Deferred { node.classList.remove(name); return dfr_resolve(); } toggleClassFn(node: Element, name: string): Deferred { node.classList.toggle(name); return dfr_resolve(); } textGetFn (node: Element): Deferred { return dfr_resolve(node.textContent); } textSetFn (node: Element, text: string): Deferred { node.textContent = text; return dfr_resolve(); } htmlOuterGetFn (node: Element): Deferred { return dfr_resolve(node.outerHTML); } htmlGetFn (node: Element): Deferred { return dfr_resolve(node.innerHTML); } htmlSetFn (node: Element, text: string): Deferred { node.innerHTML = text; return dfr_resolve(); } appendFn (node: Element, html: string): Deferred { node.insertAdjacentHTML('beforeend', html); return dfr_resolve(); } prependFn (node: Element, html: string): Deferred { node.insertAdjacentHTML('afterbegin', html); return dfr_resolve(); } beforeFn (node: Element, html: string): Deferred { node.insertAdjacentHTML('beforebegin', html); return dfr_resolve(); } afterFn (node: Element, html: string): Deferred { node.insertAdjacentHTML('afterend', html); return dfr_resolve(); } cssGet (node: HTMLElement, prop: string): Promise { return dfr_resolve(node.style[toCamelCase(prop)]); } cssSet (node: HTMLElement, css: { [key: string]: any }): Deferred { for (let key in css) { node.style[toCamelCase(key)] = css[key]; } return dfr_resolve(); } async heightGetFn (node: HTMLElement): Promise { return (await this.getBoundingClientRect(node)).height; } async widthGetFn (node: HTMLElement): Promise { return (await this.getBoundingClientRect(node)).width; } innerHeightFn (node: Element): Promise { return this.getField(node, 'offsetHeight'); } innerWidthFn (node: Element): Promise { return this.getField(node, 'offsetWidth'); } getBoundingClientRect (node: HTMLElement): Promise<{top: number, left: number, width: number, height: number}> { return dfr_resolve(node.getBoundingClientRect()); } async getPosition (node: Element): Promise<{top: number, left: number}> { let dfrTop = this.getField(node, 'offsetTop'); let dfrLeft = this.getField(node, 'offsetLeft'); let [ top, left ] = await Promise.all([dfrTop, dfrLeft]); return { top, left }; } scrollTopGetFn (node: Element): Promise { return this.getField(node, 'scrollTop'); } scrollTopSetFn (node: Element, scroll: number): Deferred { return this.setField(node, 'scrollTop', scroll); } scrollLeftGetFn (node: Element): Promise { return this.getField(node, 'scrollLeft'); } scrollLeftSetFn (node: Element, scroll: number): Deferred { return this.setField(node, 'scrollLeft', scroll); } evalFn(node: Element, mix: Function | string, ...args): Promise { throw new Error('Eval is not supported in JSDOM'); } //#region Events clickFn(node: HTMLElement): Promise { node.click(); return dfr_resolve(); } triggerFn(node: HTMLElement, type: string, ...args): Promise { throw new Error('Trigger is not supported in JSDOM'); } selectFn(node: Element, ...args): Promise { throw new Error('Select is not supported in JSDOM'); } focusFn(node: Element): Promise { throw new Error('FOCUS is not supported in JSDOM'); } blurFn(node: Element): Promise { throw new Error('BLUR is not supported in JSDOM'); } sendKeysFn(node: Element, mix): Promise { throw new Error('SEND_KEYS is not supported in JSDOM'); } typeFn(node: Element, str: string): Promise { throw new Error('TYPE is not supported in JSDOM'); } pressFn (node: Element, str:string): Promise { throw new Error('PRESS is not supported in JSDOM'); } //#endregion //#region Manipulate removeFn (node: HTMLElement): Promise { node.parentElement.removeChild(node); return dfr_resolve(); } //#endregion //#region Properties attrGetFn (node: HTMLElement, prop: string): Promise { return dfr_resolve(node.getAttribute(prop)); } attrSetFn (node: Element, attr: { [key: string]: any }): Deferred { for (let key in attr) { node.setAttribute(key, attr[key]); } return dfr_resolve(); } valGetFn (node: Element): Promise { return this.getField(node, 'value') } valSetFn (node: Element, value: any): Deferred { return this.setField(node, 'value', value); } dataGetFn (node: HTMLElement, key: string): Promise { return dfr_resolve(node.dataset[key]); } dataSetFn (node: HTMLElement, data: object): Deferred { for (let key in data) { node.dataset[key] = data[key]; } return dfr_resolve(); } protected propGetFn(node: HTMLElement, key: string): Promise { return dfr_resolve(node[key]); } protected propSetFn(node: Element, data: object): Deferred { for (let key in data) { node[key] = data[key]; } return dfr_resolve(); } //#endregion findFn (node: Element, selector: string): Deferred { let arr = Array.from(node.querySelectorAll(selector)); return dfr_resolve(arr); } matchesFn (node: Element, selector: string): Deferred { return dfr_resolve(node.matches(selector)); } parentFn (node: Element): Promise { return dfr_resolve(node.parentElement); } closestFn (node: Element, sel: string): Promise { let el = node.parentElement for (; el != null; el = el.parentElement) { el = el.parentElement; if (el.matches(sel)) { break; } } return dfr_resolve(el); } childrenFn (node: Element, sel?: string): Promise { let arr = Array.from(node.children); if (sel) { arr = arr.filter(el => el.matches(sel)); } return dfr_resolve(arr); } nextFn (node: Element, sel?: string): Promise { let next = node.nextElementSibling; if (sel != null) { for (; next != null; next = next.nextElementSibling) { if (next.matches(sel)) break; } } return dfr_resolve(next); } protected getField(node: Element, field: string): Deferred { return node[field]; } protected setField(node: Element, obj: any): Deferred; protected setField(node: Element, field: string, val: any): Deferred; protected setField(node: Element, mix, val?): Deferred { if (arguments.length === 2) { for (let key in mix) { node[key] = mix[key]; } return dfr_resolve(); } node[mix] = val; return dfr_resolve(); } protected callField(node: Element, field: string, ...args): Deferred { return dfr_resolve(node[field](...args)); } static newAsync (mix?, parent?: IQuery) { let query = new JsdomQuery(mix); query.ctx.owner = parent; (query as any).then = query.ctx.thener; return query; } //#region driver utils unlock () { } //#endregion driver utils static build(config: IBuildConfig, setts?: ISettings): Promise { throw new Error('No build for JSDom is required. Use direkt load'); } static load(url: string, config: IBuildConfig, setts?: ISettings) { let query = JsdomQuery.newAsync(); NetworkDriver.load(url, setts?.opts).then(resp => { let html = resp.body.toString(); let jsdom = new JSDOM(html); query.add(jsdom.window.document); query.resolve(query); }) return query; } static fetch(url: string, config?: IBuildConfig, setts?: ISettings) { return this.load(url, config, setts); } static setDriver (driver: IDriver ) { throw new Error('JSDOM does not support driver'); } static getDriver (config: IBuildConfig, setts?: ISettings): Promise { throw new Error('JSDOM does not support driver'); } static unlockDriver (mix) { } } function toCamelCase(property): string { return property.replace(/\-(\w)/g, (full, char: string) => { return char.toUpperCase(); }) }