diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5308a80 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,48 @@ +name: Test + +on: [push] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20.x' + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm run test + + release: + name: Release + needs: test + runs-on: ubuntu-latest + + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + id-token: write # to enable use of OIDC for npm provenance + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20.x' + - name: Install dependencies + run: npm ci + - name: Release + if: github.ref == 'refs/heads/master' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release \ No newline at end of file diff --git a/src/index.js b/src/index.js index 12cd80c..29cdd7c 100644 --- a/src/index.js +++ b/src/index.js @@ -48,8 +48,12 @@ function getAllSelectors( el, selectors, attributesToIgnore ) function testUniqueness( element, selector ) { const { parentNode } = element; - const elements = parentNode.querySelectorAll( selector ); - return elements.length === 1 && elements[ 0 ] === element; + try { + const elements = parentNode.querySelectorAll( selector ); + return elements.length === 1 && elements[0] === element; + } catch (e) { + return false + } } /** @@ -167,31 +171,45 @@ function getUniqueSelector( element, selectorTypes, attributesToIgnore ) * @api private */ -export default function unique( el, options={} ) -{ - const { selectorTypes=['id', 'class', 'tag', 'nth-child'], attributesToIgnore= ['id', 'class', 'length'] } = options; +export default function unique( el, options={} ) { + const { + selectorTypes=['id', 'class', 'tag', 'nth-child'], + attributesToIgnore= ['id', 'class', 'length'], + selectorCache, + isUniqueCache + } = options; const allSelectors = []; - const parents = getParents( el ); - for( let elem of parents ) - { - const selector = getUniqueSelector( elem, selectorTypes, attributesToIgnore ); - if( Boolean( selector ) ) - { - allSelectors.push( selector ); + let currentElement = el + while (currentElement) { + let selector = selectorCache ? selectorCache.get(currentElement) : undefined + + if (!selector) { + selector = getUniqueSelector( + currentElement, + selectorTypes, + attributesToIgnore + ) + if (selectorCache) { + selectorCache.set(currentElement, selector) + } + } + + allSelectors.unshift(selector) + const maybeUniqueSelector = allSelectors.join(' > ') + let isUniqueSelector = isUniqueCache ? isUniqueCache.get(maybeUniqueSelector) : undefined + if (isUniqueSelector === undefined) { + isUniqueSelector = isUnique(el, maybeUniqueSelector) + if (isUniqueCache) { + isUniqueCache.set(maybeUniqueSelector, isUniqueSelector) + } } - } - const selectors = []; - for( let it of allSelectors ) - { - selectors.unshift( it ); - const selector = selectors.join( ' > ' ); - if( isUnique( el, selector ) ) - { - return selector; + if (isUniqueSelector) { + return maybeUniqueSelector } - } + currentElement = currentElement.parentNode + } return null; } diff --git a/src/isUnique.js b/src/isUnique.js index bcd1e53..3d7819c 100644 --- a/src/isUnique.js +++ b/src/isUnique.js @@ -7,6 +7,10 @@ export function isUnique( el, selector ) { if( !Boolean( selector ) ) return false; - const elems = el.ownerDocument.querySelectorAll( selector ); - return elems.length === 1 && elems[ 0 ] === el; + try { + var elems = el.ownerDocument.querySelectorAll(selector); + return elems.length === 1 && elems[0] === el; + } catch (e) { + return false + } }