diff --git a/.eslintignore b/.eslintignore index b0fc978..e9f4a29 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ dist/* public/* test/*.js +docs diff --git a/.eslintrc b/.eslintrc index 63f4a49..6d62dc3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,20 +2,33 @@ "parser": "@typescript-eslint/parser", "ignorePatterns": ["lib.es5.d.ts"], "parserOptions": { - "requireConfigFile": false, - "babelOptions": { - } + "requireConfigFile": false }, + "extends": [ + "standard", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "@typescript-eslint" + ], "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], "operator-linebreak": ["off"], - "react/jsx-first-prop-new-line": ["off"], - "react/jsx-indent-props": "off", - "react/jsx-closing-tag-location": ["off"], - "react/jsx-closing-bracket-location": ["off"], - "react/jsx-curly-newline": ["off"], - "react/jsx-boolean-value": ["off"], - "jsx-quotes": ["error", "prefer-double"], "multiline-ternary": "off", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports" + } + ], "no-multiple-empty-lines": [ "error", { @@ -23,7 +36,6 @@ "maxEOF": 1 } ], - "no-undef": "off", "indent": ["error", 4, { "SwitchCase": 1, "ignoredNodes": ["TemplateLiteral *"] diff --git a/README.md b/README.md index a0a0cc1..77d6847 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![types](https://img.shields.io/npm/types/msgpackr?style=flat-square)](README.md) [![module](https://img.shields.io/badge/module-ESM%2FCJS-blue?style=flat-square)](README.md) [![dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen.svg?style=flat-square)](package.json) +[![semantic versioning](https://img.shields.io/badge/semver-2.0.0-blue?logo=semver&style=flat-square)](https://semver.org/) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) Helpers for working with the DOM, useful for tests. diff --git a/package.json b/package.json index 744bd87..9f21ba3 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,13 @@ "build-cjs": "esbuild src/*.ts --format=cjs --tsconfig=tsconfig.build.json --keep-names --outdir=./dist --out-extension:.js=.cjs", "build": "mkdir -p ./dist && rm -rf ./dist/* && npm run build-cjs && tsc --project tsconfig.build.json", "build-docs": "typedoc ./src/index.ts", - "preversion": "npm run lint", "changelog": "auto-changelog --template keepachangelog --breaking-pattern 'BREAKING CHANGE' && git add CHANGELOG.md && git commit -m 'changelog'", + "toc": "markdown-toc --maxdepth 3 -i README.md", + "preversion": "npm run lint", + "version": "npm run toc && auto-changelog -p --template keepachangelog --breaking-pattern 'BREAKING CHANGE:' && git add CHANGELOG.md README.md", "postversion": "npm run changelog && git push --follow-tags && npm publish", "prepublishOnly": "npm run build" }, - "dependencies": {}, "devDependencies": { "@bicycle-codes/tapzero": "^0.10.0", "@typescript-eslint/eslint-plugin": "^8.0.0", @@ -30,6 +31,7 @@ "esbuild": "^0.24.0", "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", + "markdown-toc": "^1.2.0", "tap-spec": "^5.0.0", "tape-run": "^11.0.0", "typedoc": "^0.26.2", diff --git a/src/index.ts b/src/index.ts index 51910e8..aff61fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -131,71 +131,74 @@ export function waitForText (args:{ multipleTags?:boolean, regex?:RegExp }):Promise { - return waitFor({ - timeout: args.timeout - }, () => { - const { - element, - text, - regex, - multipleTags - } = args - - const elems:Element[] = [] - - let maxLoop = 10000 - const stack:Element[] = [element] - // Walk the DOM tree breadth first and build up a list of - // elements with the leafs last. - while (stack.length > 0 && maxLoop-- >= 0) { - const current = stack.pop() - if (current && current.children.length > 0) { - stack.push(...current.children) - elems.push(...current.children) + return waitFor( + { timeout: args.timeout }, + () => { + const { + element, + text, + regex, + multipleTags + } = args + + const elems:Element[] = [] + + let maxLoop = 10000 + const stack:Element[] = [element] + // Walk the DOM tree breadth first and build up a list of + // elements with the leafs last. + while (stack.length > 0 && maxLoop-- >= 0) { + const current = stack.pop() + if (current && current.children.length > 0) { + stack.push(...current.children) + elems.push(...current.children) + } } - } - // Loop over children in reverse to scan the LEAF nodes first. - let match:HTMLElement|null = null - for (let i = elems.length - 1; i >= 0; i--) { - const node = elems[i] - if (!node.textContent) continue + // Loop over children in reverse to scan the LEAF nodes first. + let match:HTMLElement|null = null + for (let i = elems.length - 1; i >= 0; i--) { + const node = elems[i] + if (!node.textContent) continue - if (regex && regex.test(node.textContent)) { - return node - } + if (regex && regex.test(node.textContent)) { + return node + } - if (text && node.textContent?.includes(text)) { - return node - } + if (text && node.textContent?.includes(text)) { + return node + } - if (text && multipleTags) { - if (text[0] !== (node.textContent)[0]) continue - - // if equal, check the sibling nodes - let sibling = node.nextSibling - let i = 1 - - // while there is a potential match, keep checking the siblings - while (i < text.length) { - if (sibling && (sibling.textContent === text[i])) { - // is equal still, check the next sibling - sibling = sibling.nextSibling - i++ - match = node.parentElement - } else { - if (i === (text.length - 1)) return node.parentElement - match = null - break + if (text && multipleTags) { + if (text[0] !== (node.textContent)[0]) continue + + // if equal, check the sibling nodes + let sibling = node.nextSibling + let i = 1 + + // while there is a potential match, keep checking the siblings + while (i < text.length) { + if (sibling && (sibling.textContent === text[i])) { + // is equal still, check the next sibling + sibling = sibling.nextSibling + i++ + match = node.parentElement + } else { + if (i === (text.length - 1)) return node.parentElement + match = null + break + } } } } - } - return match - }) + return match + } + ) } +type Lambda = () => Element|null + /** * Find an element * @@ -204,19 +207,21 @@ export function waitForText (args:{ * visible?: boolean, // the element needs to be visible * timeout?: number // how long to wait * }|string} args - * @param {() => HTMLElement | null} [lambda] [lambda] + * @param {() => Element|null} [lambda] A function to match an element * @throws {Error} - Throws an error if neither `lambda` nor `selector` * is provided. * @throws {Error} - Throws an error if the element is not found within * the timeout. - * @returns {HTMLElement|null} The HTML element + * @returns {Element|null} The HTML element */ -export function waitFor (args:{ - selector?:string, - visible?:boolean, - timeout?:number -// }|string, lambda?:() => Element|null):Promise { -}|string, lambda?:() => HTMLElement|null):Promise>> { +export function waitFor ( + args:{ + selector?:string, + visible?:boolean, + timeout?:number + }|string, + lambda?:Lambda +):Promise { let selector:string let visible:boolean = true let timeout = DEFAULT_TIMEOUT diff --git a/test/index.ts b/test/index.ts index 4f46b7e..b7e5f0f 100644 --- a/test/index.ts +++ b/test/index.ts @@ -48,7 +48,8 @@ test('call waitFor with a string', async t => { test('dom.click', async t => { const p = await dom.waitFor({ selector: 'p' }) - dom.click(p!) + dom.click(p as HTMLElement) + t.ok('does not throw') }) test('dom.waitForText', async t => { @@ -204,8 +205,7 @@ test('another case for text + tags', async t => { try { const aaa = await dom.waitForText({ - // @ts-ignore - element: document.getElementById('test-two'), + element: document.getElementById('test-two') as HTMLElement, multipleTags: true, text: 'aaa', timeout: 1000