/* global Node */ const uuid = require('uuid') const REF_ID = 'applitools-ref-id' type RefType = 'cypress-driver' | 'element' | 'context' | 'driver' | 'function' type Ref = { [REF_ID]: string type?: RefType } class Refer { store: Map relation: Map> constructor() { this.store = new Map() this.relation = new Map() } isRef(ref: any): ref is Ref { // isRef(ref) { return Boolean(ref && ref[REF_ID]) } check(value: any): RefType | undefined { // check(value) { if (!value) return if (value === '__CYPRESS_DRIVER__') { return 'cypress-driver' } else if (value.nodeType === Node.ELEMENT_NODE) { return 'element' } else if (value.nodeType === Node.DOCUMENT_NODE || value.ownerDocument) { return 'context' } else if (value.constructor && value.constructor.name === 'Window') { return 'driver' } else if (typeof value === 'function') { return 'function' } } ref(value: any, parentRef?: Ref) { // ref(value, parentRef) { const refType = this.check(value) if (refType) { const ref = uuid.v4() this.store.set(ref, value) if (parentRef) { let childRefs = this.relation.get(parentRef[REF_ID]) if (!childRefs) { childRefs = new Set() this.relation.set(parentRef[REF_ID], childRefs) } childRefs.add({[REF_ID]: ref}) } return {[REF_ID]: ref, type: refType} } else if (Array.isArray(value)) { return value.map(value => this.ref(value, parentRef)) } else if (typeof value === 'object' && value !== null) { return Object.entries(value).reduce((obj, [key, value]) => { return Object.assign(obj, {[key]: this.ref(value, parentRef)}) }, {}) } else { return value } } deref(ref) { if (this.isRef(ref)) { const value = this.store.get(ref[REF_ID]) return value === '__CYPRESS_DRIVER__' ? (cy as any).state('document') : value } else { return ref } } destroy(ref) { if (!this.isRef(ref)) return const childRefs = this.relation.get(ref[REF_ID]) if (childRefs) { childRefs.forEach(childRef => this.destroy(childRef)) } this.store.delete(ref[REF_ID]) } } module.exports = Refer