Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ericclemmons/unique-selector
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.3.3
Choose a base ref
...
head repository: ericclemmons/unique-selector
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Feb 20, 2017

  1. Copy the full SHA
    fc24bf2 View commit details
  2. 0.3.3

    AvraamMavridis committed Feb 20, 2017
    Copy the full SHA
    c0527b7 View commit details
  3. 0.3.4

    AvraamMavridis committed Feb 20, 2017
    Copy the full SHA
    95ce5d5 View commit details

Commits on May 25, 2017

  1. Copy the full SHA
    8c51232 View commit details
  2. Copy the full SHA
    2bd522d View commit details
  3. 0.3.5

    AvraamMavridis committed May 25, 2017
    Copy the full SHA
    b28b43f View commit details
  4. Copy the full SHA
    dfb6598 View commit details

Commits on Jul 4, 2017

  1. fix: classList.toString don't remove extra whitespace characters (#29)

    * fix: classList.toString don't remove extra whitespace characters
    
    * chore: delete old parts of build script, delete unsuported require param, add latest preset
    
    * fix: add polyfill for array.from
    
    * chore: remove polyfill
    jeetiss authored and AvraamMavridis committed Jul 4, 2017
    Copy the full SHA
    fbfaaf4 View commit details

Commits on Jul 6, 2017

  1. 0.3.6

    AvraamMavridis committed Jul 6, 2017
    Copy the full SHA
    77e8bc0 View commit details

Commits on Sep 14, 2017

  1. chore: Update README.md (#30)

    Rodolfo Rodriguez authored and AvraamMavridis committed Sep 14, 2017
    Copy the full SHA
    66e8f2b View commit details

Commits on Sep 26, 2017

  1. Copy the full SHA
    e5030b9 View commit details

Commits on Sep 8, 2018

  1. Merge pull request #31 from osnysantos/remove-invalid-classnames

    Remove invalid CSS class names
    ronkorving authored Sep 8, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    021c8df View commit details

Commits on Nov 19, 2018

  1. Copy the full SHA
    43b867e View commit details
  2. Copy the full SHA
    fa89690 View commit details
  3. 0.4.0

    AvraamMavridis committed Nov 19, 2018
    Copy the full SHA
    7934f59 View commit details
  4. Copy the full SHA
    b2eb954 View commit details
  5. Fix for DOMException when IDs begin with a number (#34)

    * Fix for DOMException when IDs begin with a number
    
    #28
    
    * Update unique-selector.js
    G0dwin authored and AvraamMavridis committed Nov 19, 2018
    Copy the full SHA
    dcc21df View commit details
  6. 0.4.1

    AvraamMavridis committed Nov 19, 2018
    Copy the full SHA
    6e54106 View commit details

Commits on Oct 13, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5d24b11 View commit details
  2. Add LICENSE file (#47)

    fmarier authored Oct 13, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8e7de68 View commit details
  3. Copy the full SHA
    29414d0 View commit details
  4. 0.5.0

    AvraamMavridis committed Oct 13, 2020
    Copy the full SHA
    dbbb4a9 View commit details
Showing with 4,581 additions and 41 deletions.
  1. +1 −1 .babelrc
  2. +1 −2 .travis.yml
  3. +21 −0 LICENSE
  4. +17 −3 README.md
  5. +4,429 −0 package-lock.json
  6. +6 −9 package.json
  7. +10 −14 src/getClasses.js
  8. +3 −2 src/getID.js
  9. +38 −9 src/index.js
  10. +55 −1 test/unique-selector.js
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"presets": ["es2015","stage-0"]
"presets": ["latest"]
}
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: node_js
node_js:
- "4.1"
- "5.1"
- "node"
script:
- npm install
- npm run test
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2012 Eric Clemmons

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -25,12 +25,12 @@ Options
------------
e.g.1 DomElement = `<span id="test"></span>`

```
```
import unique from 'unique-selector';
// Optional Options
options = {
// Array of selector types based on which the unique selector will be generate
// Array of selector types based on which the unique selector will generate
selectorTypes : [ 'ID', 'Class', 'Tag', 'NthChild' ]
}
@@ -51,13 +51,27 @@ options = {
unique( DomElement, options ); // [test="2"]
```

e.g.3 DomElement = `<div id="xyz" class="abc test"></div>`

```
import unique from 'unique-selector';
// Optional Options
options = {
// Regular expression of ID and class names to ignore
excludeRegex : RegExp( 'xyz|abc' )
}
unique( DomElement, options ); // .test
```


Tests
-----

$ npm run test


Contributing
-----
Feel free to open issues, make suggestions or send PRs.
4,429 changes: 4,429 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "unique-selector",
"version": "0.3.2",
"version": "0.5.0",
"description": "Given a DOM node, return a unique CSS selector matching only that element",
"main": "./lib/index.js",
"jsnext:main": "./src/index.js",
"directories": {
"test": "test"
},
"scripts": {
"build": "webpack -p --config --progress --colors",
"test": "npm run compile && mocha --ignore-leaks --compilers js:babel-register --require babel-polyfill",
"compile": "babel --presets es2015,stage-0 --require babel-polyfill -d lib/ src/",
"build": "npm run compile",
"test": "npm run compile && mocha --ignore-leaks --compilers js:babel-register",
"compile": "babel -d lib/ src/",
"prepublish": "npm run compile",
"watch": "npm-scripts-watcher"
},
@@ -41,9 +41,7 @@
"devDependencies": {
"babel-cli": "^6.1.18",
"babel-core": "^6.1.18",
"babel-polyfill": "^6.7.4",
"babel-preset-es2015": "^6.1.18",
"babel-preset-stage-0": "^6.1.18",
"babel-preset-latest": "^6.24.1",
"babel-register": "^6.3.13",
"chai": "^3.5.0",
"component": "~0.10.1",
@@ -55,7 +53,6 @@
"jsdom": "^8.3.0",
"mocha": "~1.7.4",
"mocha-jsdom": "^1.1.0",
"mocha-phantomjs": "~1.1.1",
"open-browser-webpack-plugin": "0.0.2"
"mocha-phantomjs": "~1.1.1"
}
}
24 changes: 10 additions & 14 deletions src/getClasses.js
Original file line number Diff line number Diff line change
@@ -6,29 +6,25 @@
*/
export function getClasses( el )
{
let classNames;

try
if( !el.hasAttribute( 'class' ) )
{
classNames = el.classList.toString().split( ' ' );
return [];
}
catch ( e )
{
if( !el.hasAttribute( 'class' ) )
{
return [];
}

let className = el.getAttribute( 'class' );
try {
let classList = Array.prototype.slice.call( el.classList )

// return only the valid CSS selectors based on RegEx
return classList.filter(item => !/^[a-z_-][a-z\d_-]*$/i.test( item ) ? null : item );
} catch (e) {
let className = el.getAttribute( 'class' );

// remove duplicate and leading/trailing whitespaces
className = className.trim().replace( /\s+/g, ' ' );

// split into separate classnames
classNames = className.split( ' ' );
return className.split( ' ' );
}

return classNames;
}

/**
5 changes: 3 additions & 2 deletions src/getID.js
Original file line number Diff line number Diff line change
@@ -7,9 +7,10 @@ export function getID( el )
{
const id = el.getAttribute( 'id' );

if( id !== null )
if( id !== null && id !== '')
{
return `#${id}`;
// if the ID starts with a number or contains ":" selecting with a hash will cause a DOMException
return id.match(/(?:^\d|:)/) ? `[id="${id}"]` : '#' + id;
}
return null;
}
47 changes: 38 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -48,6 +48,17 @@ function testUniqueness( element, selector )
return elements.length === 1 && elements[ 0 ] === element;
}

/**
* Tests all selectors for uniqueness and returns the first unique selector.
* @param { Object } element
* @param { Array } selectors
* @return { String }
*/
function getFirstUnique( element, selectors )
{
return selectors.find( testUniqueness.bind( null, element ) );
}

/**
* Checks all the possible selectors of an element to find one unique and return it
* @param { Object } element
@@ -57,15 +68,23 @@ function testUniqueness( element, selector )
*/
function getUniqueCombination( element, items, tag )
{
const combinations = getCombinations( items, 3 );
const uniqCombinations = combinations.filter( testUniqueness.bind( this, element ) );
if( uniqCombinations.length ) return uniqCombinations[ 0 ];
let combinations = getCombinations( items, 3 ),
firstUnique = getFirstUnique( element, combinations );

if( Boolean( firstUnique ) )
{
return firstUnique;
}

if( Boolean( tag ) )
{
const combinations = items.map( item => tag + item );
const uniqCombinations = combinations.filter( testUniqueness.bind( this, element ) );
if( uniqCombinations.length ) return uniqCombinations[ 0 ];
combinations = combinations.map( combination => tag + combination );
firstUnique = getFirstUnique( element, combinations );

if( Boolean( firstUnique ) )
{
return firstUnique;
}
}

return null;
@@ -77,12 +96,18 @@ function getUniqueCombination( element, items, tag )
* @param { Array } options
* @return { String }
*/
function getUniqueSelector( element, selectorTypes, attributesToIgnore )
function getUniqueSelector( element, selectorTypes, attributesToIgnore, excludeRegex )
{
let foundSelector;

const elementSelectors = getAllSelectors( element, selectorTypes, attributesToIgnore );

if( excludeRegex && excludeRegex instanceof RegExp )
{
elementSelectors.ID = excludeRegex.test( elementSelectors.ID ) ? null : elementSelectors.ID;
elementSelectors.Class = elementSelectors.Class.filter( className => !excludeRegex.test( className ) );
}

for( let selectorType of selectorTypes )
{
const { ID, Tag, Class : Classes, Attributes, NthChild } = elementSelectors;
@@ -143,13 +168,17 @@ function getUniqueSelector( element, selectorTypes, attributesToIgnore )

export default function unique( el, options={} )
{
const { selectorTypes=[ 'ID', 'Class', 'Tag', 'NthChild' ], attributesToIgnore= ['id', 'class', 'length'] } = options;
const {
selectorTypes = ['ID', 'Class', 'Tag', 'NthChild'],
attributesToIgnore = ['id', 'class', 'length'],
excludeRegex = null,
} = options;
const allSelectors = [];
const parents = getParents( el );

for( let elem of parents )
{
const selector = getUniqueSelector( elem, selectorTypes, attributesToIgnore );
const selector = getUniqueSelector( elem, selectorTypes, attributesToIgnore, excludeRegex );
if( Boolean( selector ) )
{
allSelectors.push( selector );
56 changes: 55 additions & 1 deletion test/unique-selector.js
Original file line number Diff line number Diff line change
@@ -17,6 +17,24 @@ describe( 'Unique Selector Tests', () =>
expect( uniqueSelector ).to.equal( '#so' );
} );

it( 'ID', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div id="1so" class="test3"></div>' );
const findNode = $( 'body' ).find( '.test3' ).get( 0 );
const uniqueSelector = unique( findNode );
expect( uniqueSelector ).to.equal( '[id="1so"]' );
} );

it( 'ID', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div id="api:key" class="test3"></div>' );
const findNode = $( 'body' ).find( '.test3' ).get( 0 );
const uniqueSelector = unique( findNode );
expect( uniqueSelector ).to.equal( '[id="api:key"]' );
} );

it( 'Class', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
@@ -35,7 +53,7 @@ describe( 'Unique Selector Tests', () =>
expect( uniqueSelector ).to.equal( 'body > :nth-child(1)' );
} );

it( 'Classes', () =>
it( 'Classes multiple', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div class="test2 ca cb cc cd cx"></div><div class="test2 ca cb cc cd ce"></div><div class="test2 ca cb cc cd ce"></div><div class="test2 ca cb cd ce cf cx"></div>' );
@@ -44,6 +62,32 @@ describe( 'Unique Selector Tests', () =>
expect( uniqueSelector ).to.equal( '.cc.cx' );
} );

it( 'Classes with newline', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div class="test2\n ca\n cb\n cc\n cd\n cx"></div><div class="test2\n ca\n cb\n cc\n cd\n ce"></div><div class="test2\n ca\n cb\n cc\n cd\n ce"></div><div class="test2\n ca\n cb\n cd\n ce\n cf\n cx"></div>' );
const findNode = $( 'body' ).find( '.test2' ).get( 0 );
const uniqueSelector = unique( findNode );
expect( uniqueSelector ).to.equal( '.cc.cx' );
} );

it( 'Classes with invalid name', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div class="test2 ca=1 cb cc cd cx"></div><div class="test2 ca=1 cb cc cd ce"></div><div class="test2 ca=1 cb cc cd cz"></div><div class="test2 ca=1 cb cd ce cf cx"></div>' );
const findNode = $( 'body' ).find( '.test2' ).get( 0 );
const uniqueSelector = unique( findNode );
expect( uniqueSelector ).to.equal( '.cc.cx' );
} );

it( 'Classes with invalid name', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( "<div class='test2 ca{}1 cb cc cd cx'></div><div class='test2 ca{}1 cb cc cd ce'></div><div class='test2 ca{}1 cb cc cd cz'></div><div class='test2 ca=1 cb cd ce cf cx'></div>" );
const findNode = $( 'body' ).find( '.test2' ).get( 0 );
const uniqueSelector = unique( findNode );
expect( uniqueSelector ).to.equal( '.cc.cx' );
} );

it( 'Tag', () =>
{
@@ -82,4 +126,14 @@ describe( 'Unique Selector Tests', () =>
expect( uniqueSelector ).to.equal( '[test="5"]' );
} );

it( 'ID with exclude regex option', () =>
{
$( 'body' ).get( 0 ).innerHTML = ''; //Clear previous appends
$( 'body' ).append( '<div id="xyz" class="abc test"></div>' );
const findNode = $( 'body' ).find( '.test' ).get( 0 );
const options = { excludeRegex : RegExp( 'xyz|abc' ) };
const uniqueSelector = unique( findNode, options );
expect( uniqueSelector ).to.equal( '.test' );
} );

} );