// 包含 {tag}#{id}.{class ... 如有多个}[data-attr=... 如有多个] // example: div#xxx.aaa.bbb[data-ccc="111",data-ddd="222"] type ElemInfo = string; type Children = ElemResult[]; type ElemResult = ({ elem: ElemInfo children: Children } | string); function getDataAttributes(elem: Element) { const dataset = (elem as HTMLElement).dataset if (!dataset) { return '' } // @ts-ignore type const dataEntries: Array<[string, string]> = Array.from( // @ts-ignore type // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-call Object.entries(dataset) ) // @ts-ignore type .sort(([aA], [aB]) => { return aA > aB ? 1 : aA < aB ? -1 : 0 }) return dataEntries .map(([key, value]) => `data-${key}="${value}"`) .join(',') } function formatElemInfo(elem: Element): string { const tag = elem.tagName.toLowerCase() const id = elem.id ? `#${elem.id}` : '' const classList = Array.from(elem.classList) .sort((cA, cB) => ( cA > cB ? 1 : cA < cB ? -1 : 0 )) const classes = elem.classList.length > 0 ? `.${classList.join('.')}` : '' const dataAttributes = getDataAttributes(elem) return `${tag}${id}${classes}${dataAttributes ? `[${dataAttributes}]` : ''}` } function getDomElemStructure(node: Node): ElemResult { if (node.nodeType !== Node.ELEMENT_NODE) { return node.nodeValue || '' } const elem = node as Element const elemInfo = formatElemInfo(elem) const tagName = elem.tagName.toLowerCase() const children: Children = [] if ( tagName === 'input' || tagName === 'textarea' ) { const input = elem as HTMLInputElement if (input.type === 'checkbox' || input.type === 'radio') { children.push(`[${input.type} = ${input.checked}]`) } else { // @ts-ignore type children.push(input.value || '') } } for (const child of elem.childNodes) { children.push(getDomElemStructure(child)) } return {elem: elemInfo, children} } function printStructure(structure: ElemResult | ElemResult[], result: string[] = [], indent = 0) { if (Array.isArray(structure)) { for (const child of structure) { if (indent === 0 && typeof child === 'string') { result.push(`=== ${child} ===`) } else { printStructure(child, result, indent) } } return result } const getIndentText = () => ' '.repeat(indent) if (typeof structure === 'string') { if (!structure) { return result } result.push(getIndentText() + structure) return result } result.push(getIndentText() + structure.elem) for (const child of structure.children) { printStructure(child, result, indent + 1) } return result } type TargetElem = | Node | JQuery | string | TargetElem[] export function printDomElem(target: TargetElem): string[] { const structure: ElemResult[] = [] // @ts-ignore jquery type if (target.jquery) { return printDomElem((target as JQuery).get()); } if (!(target instanceof Array)) { return printDomElem([target]); } for (const elem of target) { if (!elem) { continue } if (typeof elem === 'string') { structure.push(elem) continue } if (elem instanceof Node) { structure.push(getDomElemStructure(elem)) continue } (elem as JQuery).get().forEach(elem => { structure.push(getDomElemStructure(elem)) }) } return printStructure(structure) } export function expectDomElem(node: TargetElem) { return expect(printDomElem(node)) }