├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── .whitesource ├── README.md ├── gulpfile.js ├── karma.conf.js ├── package.json ├── selector-generator.js ├── spec-runner.html ├── src ├── autogen-check.js ├── css-escaper.js ├── dom-node-path-step.js ├── selector-generator-step.js ├── selector-generator.js └── shim.js └── tests ├── .eslintrc ├── api.spec.js ├── domParser.js ├── fakeElementSelectors.js ├── lib ├── jasmine-html.js ├── jasmine-runner.js ├── jasmine.css └── jasmine.js └── selector-generator.spec.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jquery": true, 5 | "es6": true 6 | }, 7 | "globals": { 8 | "exports":true, 9 | "_":true, 10 | "self": true, 11 | "console": true, 12 | "browser":true, 13 | "jasmine": true, 14 | "describe": true, 15 | "expect": true, 16 | "it": true, 17 | "beforeEach": true, 18 | "afterEach": true, 19 | "di": true, 20 | "require": true, 21 | "chrome":true 22 | }, 23 | "rules": { 24 | "no-bitwise": 0, 25 | "camelcase": 1, 26 | "curly": 2, 27 | "eqeqeq": 2, 28 | "guard-for-in": 2, 29 | "no-extend-native": 2, 30 | "wrap-iife": 0, 31 | "no-use-before-define": ["error", { "functions": false, "classes": false }], 32 | "new-cap": 0, 33 | "no-caller": 2, 34 | "no-empty": 2, 35 | "no-irregular-whitespace": 0, 36 | "no-new": 2, 37 | "no-plusplus": 0, 38 | "quotes": 1, 39 | "no-undef": 2, 40 | "no-unused-vars": 2, 41 | "strict": 0, 42 | "max-params": 0, 43 | "max-depth": 0, 44 | "max-statements": 0, 45 | "complexity": 0, 46 | "max-len": 0, 47 | "no-var": 0, 48 | "semi": 2, 49 | "no-cond-assign": 0, 50 | "no-debugger": 1, 51 | "no-eq-null": 0, 52 | "no-eval": 1, 53 | "no-unused-expressions": 1, 54 | "block-scoped-var": 0, 55 | "no-iterator": 0, 56 | "linebreak-style": 0, 57 | "comma-style": [ 58 | 2, 59 | "last" 60 | ], 61 | "comma-dangle":2, 62 | "no-loop-func": 0, 63 | "no-multi-str": 0, 64 | "require-yield": 0, 65 | "valid-typeof": 0, 66 | "no-proto": 0, 67 | "no-script-url": 0, 68 | "no-shadow": 1, 69 | "dot-notation": 1, 70 | "no-new-func": 0, 71 | "no-new-wrappers": 0, 72 | "no-invalid-this": 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | .idea/ 5 | *.sln 6 | *.config 7 | /certs/key.pem 8 | /build/ 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | [Xx]64/ 25 | [Xx]86/ 26 | 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # DNX 50 | project.lock.json 51 | artifacts/ 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opendb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | *.VC.db 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | 149 | # TODO: Un-comment the next line if you do not want to checkin 150 | # your web deploy settings because they may include unencrypted 151 | # passwords 152 | #*.pubxml 153 | *.publishproj 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Microsoft Azure ApplicationInsights config file 176 | ApplicationInsights.config 177 | 178 | # Windows Store app package directory 179 | AppPackages/ 180 | BundleArtifacts/ 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | [Ss]tyle[Cc]op.* 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # LightSwitch generated files 244 | GeneratedArtifacts/ 245 | ModelManifest.xml 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | 250 | # FAKE - F# Make 251 | .fake/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | src 3 | node_modules 4 | .travis.yml 5 | .idea 6 | .gitignore 7 | karma*.js 8 | .eslintrc 9 | gulpfile.js 10 | spec-runner.html -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | before_install: 5 | - npm install -g gulp 6 | script: gulp 7 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "checkRunSettings": { 3 | "vulnerableCheckRunConclusionLevel": "failure" 4 | }, 5 | "issueSettings": { 6 | "minSeverityLevel": "LOW" 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SelectorGenerator 2 | 3 | [![Built with Gulp](http://img.shields.io/badge/built%20with-gulp.js-red.svg)](http://gulpjs.com/) 4 | [![devDependency Status](https://david-dm.org/flamencist/SelectorGenerator/dev-status.svg)](https://david-dm.org/flamencist/SelectorGenerator#info=devDependencie) 5 | [![Build Status](https://secure.travis-ci.org/flamencist/SelectorGenerator.svg)](http://travis-ci.org/flamencist/SelectorGenerator) 6 | 7 | 8 | JavaScript object that creates a unique CSS selector for a given DOM element. It has no external dependencies. 9 | 10 | Help support the project: 11 | 12 | Buy Me A Coffee 13 | 14 | ## Overview 15 | var generator = new SelectorGenerator(); 16 | var element = document.querySelector("input"); // 17 | var selector = generator.getSelector(element); //=> #login 18 | var path = generator.getPath(element); //=> body > div > input 19 | 20 | ## Installation 21 | 22 | ### Node.js 23 | 24 | To install __SelectorGenerator__ module for Node.js, this command should be used: 25 | 26 | npm install selector-generator 27 | 28 | Or [yarn](https://yarnpkg.com/lang/en/): 29 | 30 | yarn add selector-generator 31 | 32 | ## Tests 33 | 34 | You can view the results of the SelectorGenerator test suite [in your browser!](https://rawgit.com/flamencist/SelectorGenerator/master/spec-runner.html) 35 | 36 | ### License 37 | 38 | This software is distributed under the terms of the MIT License (MIT). 39 | 40 | ### Authors 41 | 42 | Alexander Chermyanin / [LinkedIn](https://www.linkedin.com/in/alexander-chermyanin) 43 | 44 | 45 | 46 | Contributions and bugs reports are welcome. 47 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*global __dirname*/ 2 | const gulp = require("gulp"); 3 | const Server = require("karma").Server; 4 | const eslint = require("gulp-eslint"); 5 | const concat = require("gulp-concat-util"); 6 | const gulpSync = require("gulp-sync")(gulp); 7 | const del = require("del"); 8 | const os = require("os"); 9 | const pkg = require("./package.json"); 10 | const src = [ 11 | "./src/shim.js", 12 | "./src/**/!(shim)*.js" 13 | ]; 14 | 15 | gulp.task("test", function (done) { 16 | var server = new Server({ 17 | configFile: __dirname + "/karma.conf.js", 18 | singleRun: true 19 | }, function () { 20 | done(); 21 | }); 22 | return server.start(); 23 | }); 24 | gulp.task("clean", function () { 25 | return del([pkg.name + ".js"]); 26 | }); 27 | gulp.task("concat", function () { 28 | return gulp.src(src) 29 | .pipe(concat(pkg.name + ".js", { 30 | sep: os.EOL + os.EOL, 31 | process: function (src, filepath) {//eslint-disable-line no-unused-vars 32 | var lines = src.split(os.EOL); 33 | for (var i = 0; i < lines.length; i++) { 34 | lines[i] = " " + lines[i]; 35 | } 36 | return lines.join(os.EOL); 37 | } 38 | })) 39 | .pipe(concat.header("/* selector-generator (ver. " + pkg.version + "). https://github.com/flamencist/SelectorGenerator */" + os.EOL + os.EOL + 40 | "(function () {" + os.EOL + os.EOL + 41 | " \"use strict\";" + os.EOL + os.EOL + 42 | " var exports = {};" + os.EOL + os.EOL + 43 | " if (!(\"version\" in exports)) {" + os.EOL + 44 | " exports.version = \"" + pkg.version + "\";" + os.EOL + 45 | " }" + os.EOL + os.EOL + 46 | " (function(exports){ " + 47 | os.EOL + 48 | " function SelectorGenerator(options){" + 49 | os.EOL + os.EOL)) 50 | .pipe(concat.footer(os.EOL + os.EOL + 51 | " return new SelectorGenerator(options);" + 52 | os.EOL + 53 | " }" + 54 | os.EOL + 55 | " exports.SelectorGenerator = SelectorGenerator;" + 56 | os.EOL + 57 | " } (exports));" + 58 | os.EOL + os.EOL + 59 | "for(var key in exports){ if(exports.hasOwnProperty(key)){ exports.SelectorGenerator[key] = exports[key]; }}" + os.EOL + 60 | " window.SelectorGenerator = exports.SelectorGenerator;" + 61 | os.EOL + os.EOL + 62 | "} ());" + 63 | os.EOL + os.EOL)) 64 | .pipe(gulp.dest("./")); 65 | }); 66 | 67 | gulp.task("eslint", function () { 68 | return gulp.src(["./src/**/*.js", "./tests/*.spec.js", "./selector-generator.js"]) 69 | .pipe(eslint()) 70 | .pipe(eslint.format()) 71 | .pipe(eslint.failAfterError()); 72 | }); 73 | gulp.task("default", function(){ 74 | return gulpSync.sync(["clean", "concat", "test"]) 75 | }); 76 | 77 | gulp.task("watch-test", function () { 78 | return gulp.watch([ 79 | "./src/**/*.js", 80 | "./tests/*.spec.js", 81 | "./tests/fakeElementSelectors.js", 82 | "package.json"], gulpSync.sync(["clean", "concat", "test"])); 83 | }); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: ["PhantomJS"], 4 | frameworks: ["jasmine"], 5 | files: [ 6 | "selector-generator.js", 7 | "tests/domParser.js", 8 | "tests/fakeElementSelectors.js", 9 | "tests/**/*.spec.js" 10 | ] 11 | }); 12 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selector-generator", 3 | "version": "0.3.1", 4 | "description": "JavaScript object that creates a unique CSS selector for a given DOM element. It has no external dependencies.", 5 | "main": "selector-generator.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/flamencist/SelectorGenerator.git" 12 | }, 13 | "keywords": [ 14 | "css", 15 | "selector" 16 | ], 17 | "author": "Alexander Chermyanin ", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/flamencist/SelectorGenerator/issues" 21 | }, 22 | "homepage": "https://github.com/flamencist/SelectorGenerator#readme", 23 | "devDependencies": { 24 | "del": ">=2.2.2", 25 | "eslint": ">=3.7.1", 26 | "gulp": "^3.9.1", 27 | "gulp-concat-util": ">=0.5.5", 28 | "gulp-eslint": ">=3.0.1", 29 | "gulp-sync": ">=0.1.4", 30 | "jasmine-core": ">=2.5.2", 31 | "karma": ">=1.5.0", 32 | "karma-chrome-launcher": ">=2.0.0", 33 | "karma-firefox-launcher": ">=1.0.0", 34 | "karma-ie-launcher": ">=1.0.0", 35 | "karma-jasmine": ">=1.1.0", 36 | "karma-jasmine-html-reporter": ">=0.2.2", 37 | "karma-phantomjs-launcher": ">=1.0.2", 38 | "karma-safari-launcher": ">=1.0.0", 39 | "phantomjs-prebuilt": ">=2.1.13" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /selector-generator.js: -------------------------------------------------------------------------------- 1 | /* selector-generator (ver. 0.3.1). https://github.com/flamencist/SelectorGenerator */ 2 | 3 | (function () { 4 | 5 | "use strict"; 6 | 7 | var exports = {}; 8 | 9 | if (!("version" in exports)) { 10 | exports.version = "0.3.1"; 11 | } 12 | 13 | (function(exports){ 14 | function SelectorGenerator(options){ 15 | 16 | var shim = {}; 17 | //noinspection JSUnresolvedVariable 18 | var call = Function.call; 19 | 20 | /** 21 | * wrap function and use first argument as context (this) in returned function 22 | * @param f {Function} function for call 23 | * @returns {Function} 24 | */ 25 | function uncurryThis(f) { 26 | return function () { 27 | return call.apply(f, arguments); 28 | }; 29 | } 30 | 31 | /** 32 | * check function is native 33 | * @param f {Function} function 34 | * @returns {boolean} 35 | */ 36 | var isFuncNative = function (f) { 37 | return !!f && (typeof f).toLowerCase() === "function" 38 | && (f === Function.prototype 39 | || /^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*\{\s*\[native code\]\s*\}\s*$/i.test(String(f))); 40 | }; 41 | 42 | /** 43 | * 44 | * @method getFuncNative 45 | * @param fun 46 | * @return {null} 47 | * @private 48 | */ 49 | var getFuncNative = function (fun) { 50 | return fun && isFuncNative(fun) ? fun : null; 51 | }; 52 | 53 | var array_reduce = uncurryThis( 54 | Array.prototype.reduce && isFuncNative(Array.prototype.reduce) ? Array.prototype.reduce : function (callback, basis) { 55 | var index = 0, 56 | length = this.length; 57 | // concerning the initial value, if one is not provided 58 | if (arguments.length === 1) { 59 | // seek to the first value in the array, accounting 60 | // for the possibility that is is a sparse array 61 | do { 62 | if (index in this) { 63 | basis = this[index++]; 64 | break; 65 | } 66 | if (++index >= length) { 67 | throw new TypeError(); 68 | } 69 | } while (1); 70 | } 71 | // reduce 72 | for (; index < length; index++) { 73 | // account for the possibility that the array is sparse 74 | if (index in this) { 75 | basis = callback(basis, this[index], index); 76 | } 77 | } 78 | return basis; 79 | } 80 | ); 81 | 82 | var array_map = uncurryThis( 83 | Array.prototype.map && isFuncNative(Array.prototype.map) ? Array.prototype.map : function (callback, thisp) { 84 | var self = this; 85 | var collect = []; 86 | array_reduce(self, function (undefined, value, index) { 87 | collect.push(callback.call(thisp, value, index, self)); 88 | }, void 0); 89 | return collect; 90 | } 91 | ); 92 | 93 | var array_filter = uncurryThis( 94 | Array.prototype.filter && isFuncNative(Array.prototype.filter) ? Array.prototype.filter : 95 | function (predicate, that) { 96 | var other = [], v; 97 | for (var i = 0, n = this.length; i < n; i++) { 98 | if (i in this && predicate.call(that, v = this[i], i, this)) { 99 | other.push(v); 100 | } 101 | } 102 | return other; 103 | } 104 | ); 105 | 106 | /** 107 | * shim for array.indexOf 108 | * 109 | * @function array_indexOf 110 | * @example 111 | * ``` 112 | * var arr = [2,3,4]; 113 | * var result = arrayUtils.indexOf(arr, 3); 114 | * console.log(result); 115 | * ``` 116 | * *result: 1* 117 | * @type {Function} 118 | * @private 119 | */ 120 | var array_indexOf = uncurryThis(getFuncNative(Array.prototype.indexOf) || 121 | function (searchElement, fromIndex) { 122 | var k; 123 | 124 | if (!this || !this.length) {//eslint-disable-line no-invalid-this 125 | throw new TypeError("\"this\" is null or not defined"); 126 | } 127 | 128 | var O = Object(this);//eslint-disable-line no-invalid-this 129 | 130 | var len = O.length >>> 0; 131 | 132 | if (len === 0) { 133 | return -1; 134 | } 135 | 136 | var n = +fromIndex || 0; 137 | 138 | if (Math.abs(n) === Infinity) { 139 | n = 0; 140 | } 141 | 142 | if (n >= len) { 143 | return -1; 144 | } 145 | 146 | k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 147 | 148 | while (k < len) { 149 | if (k in O && O[k] === searchElement) { 150 | return k; 151 | } 152 | k++; 153 | } 154 | return -1; 155 | }); 156 | 157 | /** 158 | * array find shim 159 | * 160 | * @function array_find 161 | * @example 162 | * ``` 163 | * var arr = [2,3,4]; 164 | * var result = arrayUtils.find(arr, function(item){ 165 | * return item % 2 === 0; 166 | * }); 167 | * console.log(result); 168 | * ``` 169 | * *result: 2* 170 | * @type {Function} 171 | * @private 172 | */ 173 | var array_find = uncurryThis(getFuncNative(Array.prototype.find) || 174 | function (predicate, that) { 175 | var length = this.length;//eslint-disable-line no-invalid-this 176 | if (typeof predicate !== "function") { 177 | throw new TypeError("Array#find: predicate must be a function"); 178 | } 179 | if (length === 0) { 180 | return undefined; 181 | } 182 | for (var i = 0, value; i < length; i++) { 183 | value = this[i];//eslint-disable-line no-invalid-this 184 | if (predicate.call(that, value, i, this)) { 185 | return value; 186 | } 187 | } 188 | return undefined; 189 | } 190 | ); 191 | 192 | shim.reduce = array_reduce; 193 | shim.map = array_map; 194 | shim.filter = array_filter; 195 | shim.indexOf = array_indexOf; 196 | shim.find = array_find; 197 | 198 | var _ = shim; //eslint-disable-line no-unused-vars 199 | exports._ = shim; 200 | 201 | var autogenRegexps = [ 202 | /\d{4,}/, 203 | /^ember\d+/, 204 | /^[0-9_-]+$/, 205 | /^_\d{2,}/, 206 | /([a-f_-]*[0-9_-]){6,}/i 207 | ]; 208 | 209 | /** 210 | * check auto-generated selectors 211 | * @param {String} val 212 | * @return {boolean} is auto-generated 213 | */ 214 | function autogenCheck(val){ 215 | if(!val){ 216 | return false; 217 | } 218 | var autogenerated = _.find(autogenRegexps,function(reg){ 219 | return reg.test(val); 220 | }); 221 | return !!autogenerated; 222 | 223 | } 224 | exports.autogenCheck = autogenCheck; 225 | 226 | 227 | var cssEscaper = (function(){ 228 | /** 229 | * @function escapeIdentifierIfNeeded 230 | * @param {string} ident 231 | * @return {string} 232 | */ 233 | function escapeIdentifierIfNeeded(ident) { 234 | if (isCssIdentifier(ident)) { 235 | return ident; 236 | } 237 | var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); 238 | var lastIndex = ident.length - 1; 239 | return ident.replace(/./g, function (c, i) { 240 | return ((shouldEscapeFirst && i === 0) || !isCssIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c; 241 | }); 242 | } 243 | 244 | /** 245 | * @function escapeAsciiChar 246 | * @param {string} c 247 | * @param {boolean} isLast 248 | * @return {string} 249 | */ 250 | function escapeAsciiChar(c, isLast) { 251 | return "\\" + toHexByte(c) + (isLast ? "" : " "); 252 | } 253 | 254 | /** 255 | * @function toHexByte 256 | * @param {string} c 257 | */ 258 | function toHexByte(c) { 259 | var hexByte = c.charCodeAt(0).toString(16); 260 | if (hexByte.length === 1) { 261 | hexByte = "0" + hexByte; 262 | } 263 | return hexByte; 264 | } 265 | 266 | /** 267 | * @function isCssIdentChar 268 | * @param {string} c 269 | * @return {boolean} 270 | */ 271 | function isCssIdentChar(c) { 272 | if (/[a-zA-Z0-9_-]/.test(c)) { 273 | return true; 274 | } 275 | return c.charCodeAt(0) >= 0xA0; 276 | } 277 | 278 | /** 279 | * @function isCssIdentifier 280 | * @param {string} value 281 | * @return {boolean} 282 | */ 283 | function isCssIdentifier(value) { 284 | return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); 285 | } 286 | 287 | return {escape : escapeIdentifierIfNeeded}; 288 | 289 | })(); 290 | 291 | exports.cssEscaper = cssEscaper; 292 | 293 | 294 | /** 295 | * @constructor 296 | * @param {string} value 297 | * @param {boolean} optimized 298 | */ 299 | var DomNodePathStep = function (value, optimized) { 300 | this.value = value; 301 | this.optimized = optimized || false; 302 | }; 303 | 304 | DomNodePathStep.prototype = { 305 | /** 306 | * @return {string} 307 | */ 308 | toString: function () { 309 | return this.value; 310 | } 311 | }; 312 | 313 | exports.DomNodePathStep = DomNodePathStep; 314 | 315 | 316 | /* globals DomNodePathStep, cssEscaper, autogenCheck */ //eslint-disable-line no-unused-vars 317 | /** 318 | * @param {Object?} options 319 | * @param {boolean?} options.withoutNthChild 320 | * @param {boolean?} options.optimized 321 | * @param {Node?} options.targetNode 322 | * @class 323 | * @constructor 324 | */ 325 | function SelectorGeneratorStep(options) { 326 | options = options || { 327 | withoutNthChild: false, 328 | targetNode: null 329 | }; 330 | 331 | /** 332 | * generate selector for current node 333 | * @param {HTMLElement} node 334 | * @return {DomNodePathStep} selector for current node 335 | */ 336 | this.visit = function (node) { 337 | if (node.nodeType !== 1) { 338 | return null; 339 | } 340 | 341 | var nodeName = node.nodeName.toLowerCase(); 342 | var parent = node.parentNode; 343 | var siblings = parent.children || []; 344 | var siblingsWithSameNodeName = getSiblingsWithSameNodeName(node, siblings); 345 | 346 | var needsId = hasId(node, siblingsWithSameNodeName); 347 | if (needsId) { 348 | var id = node.getAttribute("id"); 349 | return new DomNodePathStep(nodeName + idSelector(id), true); 350 | } 351 | var isRootNode = !parent || parent.nodeType === 9; 352 | if (isRootNode) // document node 353 | { 354 | return new DomNodePathStep(nodeName, true); 355 | } 356 | 357 | var hasAttributeName = hasUniqueAttributeName(node,siblingsWithSameNodeName); 358 | var needsClassNames = siblingsWithSameNodeName.length > 0; 359 | var needsNthChild = isNeedsNthChild(node, siblingsWithSameNodeName, hasAttributeName); 360 | var needsType = hasType(node); 361 | 362 | var result = nodeName; 363 | 364 | if (hasAttributeName) { 365 | var attributeName = node.getAttribute("name"); 366 | result += "[name=\"" + cssEscaper.escape(attributeName) + "\"]"; 367 | return new DomNodePathStep(result, true); 368 | } 369 | 370 | if (needsType) { 371 | result += "[type=\"" + node.getAttribute("type") + "\"]"; 372 | } 373 | 374 | if (needsNthChild && !options.withoutNthChild) { 375 | var ownIndex = _.indexOf(siblings, node); 376 | result += ":nth-child(" + (ownIndex + 1) + ")"; 377 | } else if (needsClassNames) { 378 | var prefixedOwnClassNamesArray = prefixedElementClassNames(node); 379 | for (var prefixedName in keySet(prefixedOwnClassNamesArray)) { //eslint-disable-line guard-for-in 380 | result += "." + cssEscaper.escape(prefixedName.substr(1)); 381 | } 382 | } 383 | 384 | return new DomNodePathStep(result, false); 385 | }; 386 | 387 | function hasUniqueAttributeName(node, siblingsWithSameNodeName){ 388 | var attributeName = node.getAttribute("name"); 389 | if(!attributeName || autogenCheck(attributeName)){ 390 | return false; 391 | } 392 | var isSimpleFormElement = isSimpleInput(node, options.targetNode === node) || isFormWithoutId(node); 393 | return !!(isSimpleFormElement && attributeName && !_.find(siblingsWithSameNodeName,function(sibling){ 394 | return sibling.getAttribute("name") === attributeName; 395 | })); 396 | } 397 | 398 | function isNeedsNthChild(node, siblings, isUniqueAttributeName) { 399 | var needsNthChild = false; 400 | var prefixedOwnClassNamesArray = prefixedElementClassNames(node); 401 | for (var i = 0; (!needsNthChild) && i < siblings.length; ++i) { 402 | var sibling = siblings[i]; 403 | if (needsNthChild) { 404 | continue; 405 | } 406 | 407 | var ownClassNames = keySet(prefixedOwnClassNamesArray); 408 | var ownClassNameCount = 0; 409 | 410 | for (var name in ownClassNames) { 411 | if (ownClassNames.hasOwnProperty(name)) { 412 | ++ownClassNameCount; 413 | } 414 | } 415 | if (ownClassNameCount === 0 && !isUniqueAttributeName) { 416 | needsNthChild = true; 417 | continue; 418 | } 419 | var siblingClassNamesArray = prefixedElementClassNames(sibling); 420 | 421 | for (var j = 0; j < siblingClassNamesArray.length; ++j) { 422 | var siblingClass = siblingClassNamesArray[j]; 423 | if (!ownClassNames.hasOwnProperty(siblingClass)) { 424 | continue; 425 | } 426 | delete ownClassNames[siblingClass]; 427 | if (!--ownClassNameCount && !isUniqueAttributeName) { 428 | needsNthChild = true; 429 | break; 430 | } 431 | } 432 | } 433 | return needsNthChild; 434 | } 435 | 436 | function getSiblingsWithSameNodeName(node, siblings) { 437 | return _.filter(siblings, function (sibling) { 438 | return sibling.nodeType === 1 && sibling !== node && sibling.nodeName.toLowerCase() === node.nodeName.toLowerCase(); 439 | }); 440 | } 441 | 442 | function hasType(node) { 443 | return node.getAttribute("type") && ( (isSimpleInput(node, options.targetNode === node) && !getClassName(node)) || isFormWithoutId(node) || isButtonWithoutId(node)); 444 | } 445 | 446 | /** 447 | * @function idSelector 448 | * @param {string} id 449 | * @return {string} 450 | */ 451 | function idSelector(id) { 452 | return "#" + cssEscaper.escape(id); 453 | } 454 | 455 | /** 456 | * element has siblings with same id and same tag 457 | * @function hasId 458 | * @param {Element} node 459 | * @param {Array} siblings Array of elements , parent.children 460 | * @return {boolean} 461 | */ 462 | function hasId(node, siblings) { 463 | var id = node.getAttribute("id"); 464 | if(!id){ 465 | return false; 466 | } 467 | if(autogenCheck(id)){ 468 | return false; 469 | } 470 | return _.filter(siblings, function (s) { 471 | return s.getAttribute("id") === id; 472 | }).length === 0; 473 | } 474 | 475 | /** 476 | * @function keySet 477 | * @param {Array} array of keys 478 | * @return {Object} 479 | */ 480 | function keySet(array) { 481 | var keys = {}; 482 | for (var i = 0; i < array.length; ++i) { 483 | keys[array[i]] = true; 484 | } 485 | return keys; 486 | } 487 | 488 | /** 489 | * @function prefixedElementClassNames 490 | * @param {HTMLElement} node 491 | * @return {!Array.} 492 | */ 493 | function prefixedElementClassNames(node) { 494 | var classAttribute = getClassName(node); 495 | if (!classAttribute) { 496 | return []; 497 | } 498 | 499 | var classes = classAttribute.split(/\s+/g); 500 | var existClasses = _.filter(classes, function(c){ 501 | return c && !autogenCheck(c); 502 | }); 503 | return _.map(existClasses, function (name) { 504 | // The prefix is required to store "__proto__" in a object-based map. 505 | return "$" + name; 506 | }); 507 | } 508 | 509 | function isFormWithoutId(node) { 510 | return node.nodeName.toLowerCase() === "form" && !node.getAttribute("id"); 511 | } 512 | 513 | function isButtonWithoutId(node) { 514 | return node.nodeName.toLowerCase() === "button" && !node.getAttribute("id"); 515 | } 516 | 517 | /** 518 | * target is simple input without classes,id 519 | * @function isSimpleInput 520 | * @param node 521 | * @param isTargetNode 522 | * @return {boolean} 523 | */ 524 | function isSimpleInput(node, isTargetNode) { 525 | return isTargetNode && node.nodeName.toLowerCase() === "input" ; 526 | } 527 | 528 | /** 529 | * @function getClassName 530 | * get css class of element 531 | * @param {HTMLElement} node Web element 532 | * @return {string} 533 | */ 534 | function getClassName(node) { 535 | return node.getAttribute("class") || node.className; 536 | } 537 | 538 | } 539 | 540 | exports.SelectorGeneratorStep = SelectorGeneratorStep; 541 | 542 | 543 | /*global SelectorGeneratorStep */ //eslint-disable-line no-unused-vars 544 | /** 545 | * @class 546 | * get unique selector, path of node 547 | * @param {Object?} options 548 | * @param {function?} options.querySelectorAll 549 | * @constructor 550 | */ 551 | function SelectorGenerator(options) { //eslint-disable-line no-unused-vars 552 | 553 | options = options || {}; 554 | 555 | /** 556 | * @description get full path of node 557 | * @function getPath 558 | * @param {HTMLElement} node 559 | * @return {string} 560 | */ 561 | function getPath(node) { 562 | if (!node || node.nodeType !== 1) { 563 | return ""; 564 | } 565 | var selectorGeneratorStep = new SelectorGeneratorStep({ 566 | withoutNthChild: true, 567 | targetNode: node 568 | }); 569 | var steps = []; 570 | var contextNode = node; 571 | while (contextNode) { 572 | var step = selectorGeneratorStep.visit(contextNode); 573 | if (!step) { 574 | break; 575 | } // Error - bail out early. 576 | steps.push(step); 577 | contextNode = contextNode.parentNode; 578 | } 579 | 580 | steps.reverse(); 581 | return steps.join(" "); 582 | } 583 | 584 | /** 585 | * @param {HTMLElement} node 586 | * @return {string} 587 | */ 588 | function getSelector(node) { 589 | if (!node || node.nodeType !== 1) { 590 | return ""; 591 | } 592 | var selectorGeneratorStep = new SelectorGeneratorStep({targetNode: node}); 593 | var steps = []; 594 | var contextNode = node; 595 | while (contextNode) { 596 | var step = selectorGeneratorStep.visit(contextNode); 597 | if (!step) { 598 | break; // Error - bail out early. 599 | } 600 | steps.push(step); 601 | if (step.optimized) { 602 | if (isUniqueSelector(buildSelector(steps))) { 603 | break; 604 | } 605 | } 606 | contextNode = contextNode.parentNode; 607 | } 608 | 609 | var simplifiedSteps = simplifySelector(steps); 610 | return buildSelector(simplifiedSteps); 611 | } 612 | 613 | /** 614 | * simplify selector 615 | * @example 616 | * ``` 617 | *
618 | *
619 | *
620 | * 621 | *
622 | *
623 | *
624 | * 625 | * var steps = [new DomNodePathStep("input[type='text']"), new DomNodePathStep("form"), new DomNodePathStep("div"), new DomNodePathStep("div")]; 626 | * var simplified = simplifySelector(steps); // ["input[type='text']", "form"] 627 | * ``` 628 | * 629 | * @example 630 | * ``` 631 | *
632 | *
633 | *
634 | * 635 | *
636 | *
637 | *
638 | * 639 | * var steps = [new DomNodePathStep("input[type='text']"), new DomNodePathStep("div"), new DomNodePathStep("div"), new DomNodePathStep("div#loginForm")]; 640 | * var simplified = simplifySelector(steps); // [["input[type='text']"],["div#loginForm"]] 641 | * ``` 642 | * 643 | * @method simplifySelector 644 | * @param {Array} steps parts of selector 645 | * @return {Array} steps array of steps or array Arrays of steps 646 | */ 647 | function simplifySelector(steps) { 648 | var minLength = 2; 649 | //if count of selectors is little, that not modify selector 650 | if (steps.length <= minLength) { 651 | return steps; 652 | } 653 | 654 | var stepsCopy = steps.slice(); 655 | removeHtmlBodySteps(stepsCopy); 656 | 657 | var lastStep = stepsCopy[stepsCopy.length - 1]; 658 | var parentWithId = lastStep.toString().indexOf("#") >= 0; 659 | var parentWithName = lastStep.toString().indexOf("name=") >= 0; 660 | 661 | if (parentWithId || parentWithName) { 662 | return simplifyStepsWithParent(stepsCopy); 663 | } else { 664 | return regularSimplifySteps(stepsCopy, minLength); 665 | } 666 | } 667 | 668 | /** 669 | * remove Html, Body Steps 670 | * @param steps 671 | */ 672 | function removeHtmlBodySteps(steps) { 673 | while (steps[steps.length - 1].toString() === "html" || steps[steps.length - 1].toString() === "body") { 674 | steps.pop(); 675 | } 676 | } 677 | 678 | /** 679 | * simplifyStepsWithParent 680 | * @function simplifyStepsWithParent 681 | * @param steps 682 | * @return {Array} array of arrays 683 | */ 684 | function simplifyStepsWithParent(steps) { 685 | var parentStep = steps.slice(-1); 686 | var sliced = steps.slice(0, 1); 687 | while (sliced.length < (steps.length - 1)) { 688 | var selector = buildSelector([sliced, parentStep]); 689 | if (isUniqueSelector(selector)) { 690 | break; 691 | } 692 | sliced = steps.slice(0, sliced.length + 1); 693 | } 694 | return [sliced, parentStep]; 695 | } 696 | 697 | /** 698 | * regularSimplifySteps 699 | * @method regularSimplifySteps 700 | * @param {Array} steps 701 | * @param {int=2} minLength 702 | * @return {Array} array of steps 703 | */ 704 | function regularSimplifySteps(steps, minLength) { 705 | minLength = minLength || 2; 706 | var sliced = steps.slice(0, minLength); 707 | while (sliced.length < steps.length) { 708 | var selector = buildSelector(sliced); 709 | if (isUniqueSelector(selector)) { 710 | break; 711 | } 712 | sliced = steps.slice(0, sliced.length + 1); 713 | } 714 | return sliced; 715 | } 716 | 717 | /** 718 | * create selector string from steps array 719 | * @function buildSelector 720 | * @example 721 | * with single array of steps 722 | * ``` 723 | *
724 | * 725 | *
726 | * 727 | * var steps = [new DomNodePathStep("input[type='text']"),new DomNodePathStep("form#loginForm")]; 728 | * var selector = buildSelector(steps); // "form#loginForm > input[type='text']" 729 | * ``` 730 | * 731 | * @example 732 | * with multiple array of steps 733 | * ``` 734 | *
735 | *
736 | *
737 | * 738 | *
739 | *
740 | *
741 | * 742 | * var steps = [[new DomNodePathStep("input[type='text']")],[new DomNodePathStep("div#loginForm")]]; 743 | * var selector = buildSelector(steps); // "div#loginForm input[type='text']" 744 | * ``` 745 | * 746 | * @param {Array} steps Array of string or array of Array of string 747 | * @return {string} selector string 748 | */ 749 | function buildSelector(steps) { 750 | var stepsCopy = steps.slice(); 751 | stepsCopy.reverse(); 752 | //check steps is regular array of steps 753 | if (typeof(stepsCopy[0].value) !== "undefined") { 754 | return stepsCopy.join(" > "); 755 | } else { 756 | return _.reduce(stepsCopy, function (previosValue, currentValue) { 757 | var selector = buildSelector(currentValue); 758 | return previosValue ? previosValue + " " + selector : selector; 759 | }, ""); 760 | } 761 | } 762 | 763 | /** 764 | * @function isUniqueSelector 765 | * detect selector is unique 766 | * @param {String} selector 767 | * @return {boolean} unique selector? 768 | */ 769 | function isUniqueSelector(selector) { 770 | if (!options.querySelectorAll) { 771 | return true; 772 | } 773 | return options.querySelectorAll(selector).length < 2; 774 | } 775 | 776 | /** 777 | * get unique selector 778 | * @method 779 | * @param {HTMLElement} node html element 780 | * @param {boolean?} optimized get short selector 781 | * @returns {String} selector 782 | */ 783 | this.getSelector = getSelector; 784 | /** 785 | * path of node 786 | * @method 787 | * @param {HTMLElement} node html element 788 | * @returns {String} path 789 | */ 790 | this.getPath = getPath; 791 | } 792 | 793 | exports.SelectorGenerator = SelectorGenerator; 794 | 795 | return new SelectorGenerator(options); 796 | } 797 | exports.SelectorGenerator = SelectorGenerator; 798 | } (exports)); 799 | 800 | for(var key in exports){ if(exports.hasOwnProperty(key)){ exports.SelectorGenerator[key] = exports[key]; }} 801 | window.SelectorGenerator = exports.SelectorGenerator; 802 | 803 | } ()); 804 | 805 | -------------------------------------------------------------------------------- /spec-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/autogen-check.js: -------------------------------------------------------------------------------- 1 | var autogenRegexps = [ 2 | /\d{4,}/, 3 | /^ember\d+/, 4 | /^[0-9_-]+$/, 5 | /^_\d{2,}/, 6 | /([a-f_-]*[0-9_-]){6,}/i 7 | ]; 8 | 9 | /** 10 | * check auto-generated selectors 11 | * @param {String} val 12 | * @return {boolean} is auto-generated 13 | */ 14 | function autogenCheck(val){ 15 | if(!val){ 16 | return false; 17 | } 18 | var autogenerated = _.find(autogenRegexps,function(reg){ 19 | return reg.test(val); 20 | }); 21 | return !!autogenerated; 22 | 23 | } 24 | exports.autogenCheck = autogenCheck; 25 | -------------------------------------------------------------------------------- /src/css-escaper.js: -------------------------------------------------------------------------------- 1 | var cssEscaper = (function(){ 2 | /** 3 | * @function escapeIdentifierIfNeeded 4 | * @param {string} ident 5 | * @return {string} 6 | */ 7 | function escapeIdentifierIfNeeded(ident) { 8 | if (isCssIdentifier(ident)) { 9 | return ident; 10 | } 11 | var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); 12 | var lastIndex = ident.length - 1; 13 | return ident.replace(/./g, function (c, i) { 14 | return ((shouldEscapeFirst && i === 0) || !isCssIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c; 15 | }); 16 | } 17 | 18 | /** 19 | * @function escapeAsciiChar 20 | * @param {string} c 21 | * @param {boolean} isLast 22 | * @return {string} 23 | */ 24 | function escapeAsciiChar(c, isLast) { 25 | return "\\" + toHexByte(c) + (isLast ? "" : " "); 26 | } 27 | 28 | /** 29 | * @function toHexByte 30 | * @param {string} c 31 | */ 32 | function toHexByte(c) { 33 | var hexByte = c.charCodeAt(0).toString(16); 34 | if (hexByte.length === 1) { 35 | hexByte = "0" + hexByte; 36 | } 37 | return hexByte; 38 | } 39 | 40 | /** 41 | * @function isCssIdentChar 42 | * @param {string} c 43 | * @return {boolean} 44 | */ 45 | function isCssIdentChar(c) { 46 | if (/[a-zA-Z0-9_-]/.test(c)) { 47 | return true; 48 | } 49 | return c.charCodeAt(0) >= 0xA0; 50 | } 51 | 52 | /** 53 | * @function isCssIdentifier 54 | * @param {string} value 55 | * @return {boolean} 56 | */ 57 | function isCssIdentifier(value) { 58 | return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); 59 | } 60 | 61 | return {escape : escapeIdentifierIfNeeded}; 62 | 63 | })(); 64 | 65 | exports.cssEscaper = cssEscaper; 66 | -------------------------------------------------------------------------------- /src/dom-node-path-step.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @param {string} value 4 | * @param {boolean} optimized 5 | */ 6 | var DomNodePathStep = function (value, optimized) { 7 | this.value = value; 8 | this.optimized = optimized || false; 9 | }; 10 | 11 | DomNodePathStep.prototype = { 12 | /** 13 | * @return {string} 14 | */ 15 | toString: function () { 16 | return this.value; 17 | } 18 | }; 19 | 20 | exports.DomNodePathStep = DomNodePathStep; 21 | -------------------------------------------------------------------------------- /src/selector-generator-step.js: -------------------------------------------------------------------------------- 1 | /* globals DomNodePathStep, cssEscaper, autogenCheck */ //eslint-disable-line no-unused-vars 2 | /** 3 | * @param {Object?} options 4 | * @param {boolean?} options.withoutNthChild 5 | * @param {boolean?} options.optimized 6 | * @param {Node?} options.targetNode 7 | * @class 8 | * @constructor 9 | */ 10 | function SelectorGeneratorStep(options) { 11 | options = options || { 12 | withoutNthChild: false, 13 | targetNode: null 14 | }; 15 | 16 | /** 17 | * generate selector for current node 18 | * @param {HTMLElement} node 19 | * @return {DomNodePathStep} selector for current node 20 | */ 21 | this.visit = function (node) { 22 | if (node.nodeType !== 1) { 23 | return null; 24 | } 25 | 26 | var nodeName = node.nodeName.toLowerCase(); 27 | var parent = node.parentNode; 28 | var siblings = parent.children || []; 29 | var siblingsWithSameNodeName = getSiblingsWithSameNodeName(node, siblings); 30 | 31 | var needsId = hasId(node, siblingsWithSameNodeName); 32 | if (needsId) { 33 | var id = node.getAttribute("id"); 34 | return new DomNodePathStep(nodeName + idSelector(id), true); 35 | } 36 | var isRootNode = !parent || parent.nodeType === 9; 37 | if (isRootNode) // document node 38 | { 39 | return new DomNodePathStep(nodeName, true); 40 | } 41 | 42 | var hasAttributeName = hasUniqueAttributeName(node,siblingsWithSameNodeName); 43 | var needsClassNames = siblingsWithSameNodeName.length > 0; 44 | var needsNthChild = isNeedsNthChild(node, siblingsWithSameNodeName, hasAttributeName); 45 | var needsType = hasType(node); 46 | 47 | var result = nodeName; 48 | 49 | if (hasAttributeName) { 50 | var attributeName = node.getAttribute("name"); 51 | result += "[name=\"" + cssEscaper.escape(attributeName) + "\"]"; 52 | return new DomNodePathStep(result, true); 53 | } 54 | 55 | if (needsType) { 56 | result += "[type=\"" + node.getAttribute("type") + "\"]"; 57 | } 58 | 59 | if (needsNthChild && !options.withoutNthChild) { 60 | var ownIndex = _.indexOf(siblings, node); 61 | result += ":nth-child(" + (ownIndex + 1) + ")"; 62 | } else if (needsClassNames) { 63 | var prefixedOwnClassNamesArray = prefixedElementClassNames(node); 64 | for (var prefixedName in keySet(prefixedOwnClassNamesArray)) { //eslint-disable-line guard-for-in 65 | result += "." + cssEscaper.escape(prefixedName.substr(1)); 66 | } 67 | } 68 | 69 | return new DomNodePathStep(result, false); 70 | }; 71 | 72 | function hasUniqueAttributeName(node, siblingsWithSameNodeName){ 73 | var attributeName = node.getAttribute("name"); 74 | if(!attributeName || autogenCheck(attributeName)){ 75 | return false; 76 | } 77 | var isSimpleFormElement = isSimpleInput(node, options.targetNode === node) || isFormWithoutId(node); 78 | return !!(isSimpleFormElement && attributeName && !_.find(siblingsWithSameNodeName,function(sibling){ 79 | return sibling.getAttribute("name") === attributeName; 80 | })); 81 | } 82 | 83 | function isNeedsNthChild(node, siblings, isUniqueAttributeName) { 84 | var needsNthChild = false; 85 | var prefixedOwnClassNamesArray = prefixedElementClassNames(node); 86 | for (var i = 0; (!needsNthChild) && i < siblings.length; ++i) { 87 | var sibling = siblings[i]; 88 | if (needsNthChild) { 89 | continue; 90 | } 91 | 92 | var ownClassNames = keySet(prefixedOwnClassNamesArray); 93 | var ownClassNameCount = 0; 94 | 95 | for (var name in ownClassNames) { 96 | if (ownClassNames.hasOwnProperty(name)) { 97 | ++ownClassNameCount; 98 | } 99 | } 100 | if (ownClassNameCount === 0 && !isUniqueAttributeName) { 101 | needsNthChild = true; 102 | continue; 103 | } 104 | var siblingClassNamesArray = prefixedElementClassNames(sibling); 105 | 106 | for (var j = 0; j < siblingClassNamesArray.length; ++j) { 107 | var siblingClass = siblingClassNamesArray[j]; 108 | if (!ownClassNames.hasOwnProperty(siblingClass)) { 109 | continue; 110 | } 111 | delete ownClassNames[siblingClass]; 112 | if (!--ownClassNameCount && !isUniqueAttributeName) { 113 | needsNthChild = true; 114 | break; 115 | } 116 | } 117 | } 118 | return needsNthChild; 119 | } 120 | 121 | function getSiblingsWithSameNodeName(node, siblings) { 122 | return _.filter(siblings, function (sibling) { 123 | return sibling.nodeType === 1 && sibling !== node && sibling.nodeName.toLowerCase() === node.nodeName.toLowerCase(); 124 | }); 125 | } 126 | 127 | function hasType(node) { 128 | return node.getAttribute("type") && ( (isSimpleInput(node, options.targetNode === node) && !getClassName(node)) || isFormWithoutId(node) || isButtonWithoutId(node)); 129 | } 130 | 131 | /** 132 | * @function idSelector 133 | * @param {string} id 134 | * @return {string} 135 | */ 136 | function idSelector(id) { 137 | return "#" + cssEscaper.escape(id); 138 | } 139 | 140 | /** 141 | * element has siblings with same id and same tag 142 | * @function hasId 143 | * @param {Element} node 144 | * @param {Array} siblings Array of elements , parent.children 145 | * @return {boolean} 146 | */ 147 | function hasId(node, siblings) { 148 | var id = node.getAttribute("id"); 149 | if(!id){ 150 | return false; 151 | } 152 | if(autogenCheck(id)){ 153 | return false; 154 | } 155 | return _.filter(siblings, function (s) { 156 | return s.getAttribute("id") === id; 157 | }).length === 0; 158 | } 159 | 160 | /** 161 | * @function keySet 162 | * @param {Array} array of keys 163 | * @return {Object} 164 | */ 165 | function keySet(array) { 166 | var keys = {}; 167 | for (var i = 0; i < array.length; ++i) { 168 | keys[array[i]] = true; 169 | } 170 | return keys; 171 | } 172 | 173 | /** 174 | * @function prefixedElementClassNames 175 | * @param {HTMLElement} node 176 | * @return {!Array.} 177 | */ 178 | function prefixedElementClassNames(node) { 179 | var classAttribute = getClassName(node); 180 | if (!classAttribute) { 181 | return []; 182 | } 183 | 184 | var classes = classAttribute.split(/\s+/g); 185 | var existClasses = _.filter(classes, function(c){ 186 | return c && !autogenCheck(c); 187 | }); 188 | return _.map(existClasses, function (name) { 189 | // The prefix is required to store "__proto__" in a object-based map. 190 | return "$" + name; 191 | }); 192 | } 193 | 194 | function isFormWithoutId(node) { 195 | return node.nodeName.toLowerCase() === "form" && !node.getAttribute("id"); 196 | } 197 | 198 | function isButtonWithoutId(node) { 199 | return node.nodeName.toLowerCase() === "button" && !node.getAttribute("id"); 200 | } 201 | 202 | /** 203 | * target is simple input without classes,id 204 | * @function isSimpleInput 205 | * @param node 206 | * @param isTargetNode 207 | * @return {boolean} 208 | */ 209 | function isSimpleInput(node, isTargetNode) { 210 | return isTargetNode && node.nodeName.toLowerCase() === "input" ; 211 | } 212 | 213 | /** 214 | * @function getClassName 215 | * get css class of element 216 | * @param {HTMLElement} node Web element 217 | * @return {string} 218 | */ 219 | function getClassName(node) { 220 | return node.getAttribute("class") || node.className; 221 | } 222 | 223 | } 224 | 225 | exports.SelectorGeneratorStep = SelectorGeneratorStep; 226 | -------------------------------------------------------------------------------- /src/selector-generator.js: -------------------------------------------------------------------------------- 1 | /*global SelectorGeneratorStep */ //eslint-disable-line no-unused-vars 2 | /** 3 | * @class 4 | * get unique selector, path of node 5 | * @param {Object?} options 6 | * @param {function?} options.querySelectorAll 7 | * @constructor 8 | */ 9 | function SelectorGenerator(options) { //eslint-disable-line no-unused-vars 10 | 11 | options = options || {}; 12 | 13 | /** 14 | * @description get full path of node 15 | * @function getPath 16 | * @param {HTMLElement} node 17 | * @return {string} 18 | */ 19 | function getPath(node) { 20 | if (!node || node.nodeType !== 1) { 21 | return ""; 22 | } 23 | var selectorGeneratorStep = new SelectorGeneratorStep({ 24 | withoutNthChild: true, 25 | targetNode: node 26 | }); 27 | var steps = []; 28 | var contextNode = node; 29 | while (contextNode) { 30 | var step = selectorGeneratorStep.visit(contextNode); 31 | if (!step) { 32 | break; 33 | } // Error - bail out early. 34 | steps.push(step); 35 | contextNode = contextNode.parentNode; 36 | } 37 | 38 | steps.reverse(); 39 | return steps.join(" "); 40 | } 41 | 42 | /** 43 | * @param {HTMLElement} node 44 | * @return {string} 45 | */ 46 | function getSelector(node) { 47 | if (!node || node.nodeType !== 1) { 48 | return ""; 49 | } 50 | var selectorGeneratorStep = new SelectorGeneratorStep({targetNode: node}); 51 | var steps = []; 52 | var contextNode = node; 53 | while (contextNode) { 54 | var step = selectorGeneratorStep.visit(contextNode); 55 | if (!step) { 56 | break; // Error - bail out early. 57 | } 58 | steps.push(step); 59 | if (step.optimized) { 60 | if (isUniqueSelector(buildSelector(steps))) { 61 | break; 62 | } 63 | } 64 | contextNode = contextNode.parentNode; 65 | } 66 | 67 | var simplifiedSteps = simplifySelector(steps); 68 | return buildSelector(simplifiedSteps); 69 | } 70 | 71 | /** 72 | * simplify selector 73 | * @example 74 | * ``` 75 | *
76 | *
77 | *
78 | * 79 | *
80 | *
81 | *
82 | * 83 | * var steps = [new DomNodePathStep("input[type='text']"), new DomNodePathStep("form"), new DomNodePathStep("div"), new DomNodePathStep("div")]; 84 | * var simplified = simplifySelector(steps); // ["input[type='text']", "form"] 85 | * ``` 86 | * 87 | * @example 88 | * ``` 89 | *
90 | *
91 | *
92 | * 93 | *
94 | *
95 | *
96 | * 97 | * var steps = [new DomNodePathStep("input[type='text']"), new DomNodePathStep("div"), new DomNodePathStep("div"), new DomNodePathStep("div#loginForm")]; 98 | * var simplified = simplifySelector(steps); // [["input[type='text']"],["div#loginForm"]] 99 | * ``` 100 | * 101 | * @method simplifySelector 102 | * @param {Array} steps parts of selector 103 | * @return {Array} steps array of steps or array Arrays of steps 104 | */ 105 | function simplifySelector(steps) { 106 | var minLength = 2; 107 | //if count of selectors is little, that not modify selector 108 | if (steps.length <= minLength) { 109 | return steps; 110 | } 111 | 112 | var stepsCopy = steps.slice(); 113 | removeHtmlBodySteps(stepsCopy); 114 | 115 | var lastStep = stepsCopy[stepsCopy.length - 1]; 116 | var parentWithId = lastStep.toString().indexOf("#") >= 0; 117 | var parentWithName = lastStep.toString().indexOf("name=") >= 0; 118 | 119 | if (parentWithId || parentWithName) { 120 | return simplifyStepsWithParent(stepsCopy); 121 | } else { 122 | return regularSimplifySteps(stepsCopy, minLength); 123 | } 124 | } 125 | 126 | /** 127 | * remove Html, Body Steps 128 | * @param steps 129 | */ 130 | function removeHtmlBodySteps(steps) { 131 | while (steps[steps.length - 1].toString() === "html" || steps[steps.length - 1].toString() === "body") { 132 | steps.pop(); 133 | } 134 | } 135 | 136 | /** 137 | * simplifyStepsWithParent 138 | * @function simplifyStepsWithParent 139 | * @param steps 140 | * @return {Array} array of arrays 141 | */ 142 | function simplifyStepsWithParent(steps) { 143 | var parentStep = steps.slice(-1); 144 | var sliced = steps.slice(0, 1); 145 | while (sliced.length < (steps.length - 1)) { 146 | var selector = buildSelector([sliced, parentStep]); 147 | if (isUniqueSelector(selector)) { 148 | break; 149 | } 150 | sliced = steps.slice(0, sliced.length + 1); 151 | } 152 | return [sliced, parentStep]; 153 | } 154 | 155 | /** 156 | * regularSimplifySteps 157 | * @method regularSimplifySteps 158 | * @param {Array} steps 159 | * @param {int=2} minLength 160 | * @return {Array} array of steps 161 | */ 162 | function regularSimplifySteps(steps, minLength) { 163 | minLength = minLength || 2; 164 | var sliced = steps.slice(0, minLength); 165 | while (sliced.length < steps.length) { 166 | var selector = buildSelector(sliced); 167 | if (isUniqueSelector(selector)) { 168 | break; 169 | } 170 | sliced = steps.slice(0, sliced.length + 1); 171 | } 172 | return sliced; 173 | } 174 | 175 | /** 176 | * create selector string from steps array 177 | * @function buildSelector 178 | * @example 179 | * with single array of steps 180 | * ``` 181 | *
182 | * 183 | *
184 | * 185 | * var steps = [new DomNodePathStep("input[type='text']"),new DomNodePathStep("form#loginForm")]; 186 | * var selector = buildSelector(steps); // "form#loginForm > input[type='text']" 187 | * ``` 188 | * 189 | * @example 190 | * with multiple array of steps 191 | * ``` 192 | *
193 | *
194 | *
195 | * 196 | *
197 | *
198 | *
199 | * 200 | * var steps = [[new DomNodePathStep("input[type='text']")],[new DomNodePathStep("div#loginForm")]]; 201 | * var selector = buildSelector(steps); // "div#loginForm input[type='text']" 202 | * ``` 203 | * 204 | * @param {Array} steps Array of string or array of Array of string 205 | * @return {string} selector string 206 | */ 207 | function buildSelector(steps) { 208 | var stepsCopy = steps.slice(); 209 | stepsCopy.reverse(); 210 | //check steps is regular array of steps 211 | if (typeof(stepsCopy[0].value) !== "undefined") { 212 | return stepsCopy.join(" > "); 213 | } else { 214 | return _.reduce(stepsCopy, function (previosValue, currentValue) { 215 | var selector = buildSelector(currentValue); 216 | return previosValue ? previosValue + " " + selector : selector; 217 | }, ""); 218 | } 219 | } 220 | 221 | /** 222 | * @function isUniqueSelector 223 | * detect selector is unique 224 | * @param {String} selector 225 | * @return {boolean} unique selector? 226 | */ 227 | function isUniqueSelector(selector) { 228 | if (!options.querySelectorAll) { 229 | return true; 230 | } 231 | return options.querySelectorAll(selector).length < 2; 232 | } 233 | 234 | /** 235 | * get unique selector 236 | * @method 237 | * @param {HTMLElement} node html element 238 | * @param {boolean?} optimized get short selector 239 | * @returns {String} selector 240 | */ 241 | this.getSelector = getSelector; 242 | /** 243 | * path of node 244 | * @method 245 | * @param {HTMLElement} node html element 246 | * @returns {String} path 247 | */ 248 | this.getPath = getPath; 249 | } 250 | 251 | exports.SelectorGenerator = SelectorGenerator; -------------------------------------------------------------------------------- /src/shim.js: -------------------------------------------------------------------------------- 1 | var shim = {}; 2 | //noinspection JSUnresolvedVariable 3 | var call = Function.call; 4 | 5 | /** 6 | * wrap function and use first argument as context (this) in returned function 7 | * @param f {Function} function for call 8 | * @returns {Function} 9 | */ 10 | function uncurryThis(f) { 11 | return function () { 12 | return call.apply(f, arguments); 13 | }; 14 | } 15 | 16 | /** 17 | * check function is native 18 | * @param f {Function} function 19 | * @returns {boolean} 20 | */ 21 | var isFuncNative = function (f) { 22 | return !!f && (typeof f).toLowerCase() === "function" 23 | && (f === Function.prototype 24 | || /^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*\{\s*\[native code\]\s*\}\s*$/i.test(String(f))); 25 | }; 26 | 27 | /** 28 | * 29 | * @method getFuncNative 30 | * @param fun 31 | * @return {null} 32 | * @private 33 | */ 34 | var getFuncNative = function (fun) { 35 | return fun && isFuncNative(fun) ? fun : null; 36 | }; 37 | 38 | var array_reduce = uncurryThis( 39 | Array.prototype.reduce && isFuncNative(Array.prototype.reduce) ? Array.prototype.reduce : function (callback, basis) { 40 | var index = 0, 41 | length = this.length; 42 | // concerning the initial value, if one is not provided 43 | if (arguments.length === 1) { 44 | // seek to the first value in the array, accounting 45 | // for the possibility that is is a sparse array 46 | do { 47 | if (index in this) { 48 | basis = this[index++]; 49 | break; 50 | } 51 | if (++index >= length) { 52 | throw new TypeError(); 53 | } 54 | } while (1); 55 | } 56 | // reduce 57 | for (; index < length; index++) { 58 | // account for the possibility that the array is sparse 59 | if (index in this) { 60 | basis = callback(basis, this[index], index); 61 | } 62 | } 63 | return basis; 64 | } 65 | ); 66 | 67 | var array_map = uncurryThis( 68 | Array.prototype.map && isFuncNative(Array.prototype.map) ? Array.prototype.map : function (callback, thisp) { 69 | var self = this; 70 | var collect = []; 71 | array_reduce(self, function (undefined, value, index) { 72 | collect.push(callback.call(thisp, value, index, self)); 73 | }, void 0); 74 | return collect; 75 | } 76 | ); 77 | 78 | var array_filter = uncurryThis( 79 | Array.prototype.filter && isFuncNative(Array.prototype.filter) ? Array.prototype.filter : 80 | function (predicate, that) { 81 | var other = [], v; 82 | for (var i = 0, n = this.length; i < n; i++) { 83 | if (i in this && predicate.call(that, v = this[i], i, this)) { 84 | other.push(v); 85 | } 86 | } 87 | return other; 88 | } 89 | ); 90 | 91 | /** 92 | * shim for array.indexOf 93 | * 94 | * @function array_indexOf 95 | * @example 96 | * ``` 97 | * var arr = [2,3,4]; 98 | * var result = arrayUtils.indexOf(arr, 3); 99 | * console.log(result); 100 | * ``` 101 | * *result: 1* 102 | * @type {Function} 103 | * @private 104 | */ 105 | var array_indexOf = uncurryThis(getFuncNative(Array.prototype.indexOf) || 106 | function (searchElement, fromIndex) { 107 | var k; 108 | 109 | if (!this || !this.length) {//eslint-disable-line no-invalid-this 110 | throw new TypeError("\"this\" is null or not defined"); 111 | } 112 | 113 | var O = Object(this);//eslint-disable-line no-invalid-this 114 | 115 | var len = O.length >>> 0; 116 | 117 | if (len === 0) { 118 | return -1; 119 | } 120 | 121 | var n = +fromIndex || 0; 122 | 123 | if (Math.abs(n) === Infinity) { 124 | n = 0; 125 | } 126 | 127 | if (n >= len) { 128 | return -1; 129 | } 130 | 131 | k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 132 | 133 | while (k < len) { 134 | if (k in O && O[k] === searchElement) { 135 | return k; 136 | } 137 | k++; 138 | } 139 | return -1; 140 | }); 141 | 142 | /** 143 | * array find shim 144 | * 145 | * @function array_find 146 | * @example 147 | * ``` 148 | * var arr = [2,3,4]; 149 | * var result = arrayUtils.find(arr, function(item){ 150 | * return item % 2 === 0; 151 | * }); 152 | * console.log(result); 153 | * ``` 154 | * *result: 2* 155 | * @type {Function} 156 | * @private 157 | */ 158 | var array_find = uncurryThis(getFuncNative(Array.prototype.find) || 159 | function (predicate, that) { 160 | var length = this.length;//eslint-disable-line no-invalid-this 161 | if (typeof predicate !== "function") { 162 | throw new TypeError("Array#find: predicate must be a function"); 163 | } 164 | if (length === 0) { 165 | return undefined; 166 | } 167 | for (var i = 0, value; i < length; i++) { 168 | value = this[i];//eslint-disable-line no-invalid-this 169 | if (predicate.call(that, value, i, this)) { 170 | return value; 171 | } 172 | } 173 | return undefined; 174 | } 175 | ); 176 | 177 | shim.reduce = array_reduce; 178 | shim.map = array_map; 179 | shim.filter = array_filter; 180 | shim.indexOf = array_indexOf; 181 | shim.find = array_find; 182 | 183 | var _ = shim; //eslint-disable-line no-unused-vars 184 | exports._ = shim; -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jasmine": true, 5 | "jquery": true, 6 | "mocha": true, 7 | "es6": true 8 | }, 9 | "globals": { 10 | "exports":true, 11 | "_":true, 12 | "self": true, 13 | "console": true, 14 | "browser":true, 15 | "jasmine": true, 16 | "describe": true, 17 | "expect": true, 18 | "it": true, 19 | "beforeEach": true, 20 | "afterEach": true, 21 | "di": true, 22 | "require": true, 23 | "chrome":true 24 | }, 25 | "rules": { 26 | "no-bitwise": 0, 27 | "camelcase": 1, 28 | "curly": 2, 29 | "eqeqeq": 2, 30 | "guard-for-in": 2, 31 | "no-extend-native": 2, 32 | "wrap-iife": 0, 33 | "no-use-before-define": ["error", { "functions": false, "classes": false }], 34 | "new-cap": 0, 35 | "no-caller": 2, 36 | "no-empty": 2, 37 | "no-irregular-whitespace": 0, 38 | "no-new": 2, 39 | "no-plusplus": 0, 40 | "quotes": 1, 41 | "no-undef": 2, 42 | "no-unused-vars": 2, 43 | "strict": 0, 44 | "max-params": 0, 45 | "max-depth": 0, 46 | "max-statements": 0, 47 | "complexity": 0, 48 | "max-len": 0, 49 | "no-var": 0, 50 | "semi": 2, 51 | "no-cond-assign": 0, 52 | "no-debugger": 1, 53 | "no-eq-null": 0, 54 | "no-eval": 1, 55 | "no-unused-expressions": 1, 56 | "block-scoped-var": 0, 57 | "no-iterator": 0, 58 | "linebreak-style": 0, 59 | "comma-style": [ 60 | 2, 61 | "last" 62 | ], 63 | "comma-dangle":2, 64 | "no-loop-func": 0, 65 | "no-multi-str": 0, 66 | "require-yield": 0, 67 | "valid-typeof": 0, 68 | "no-proto": 0, 69 | "no-script-url": 0, 70 | "no-shadow": 1, 71 | "dot-notation": 1, 72 | "no-new-func": 0, 73 | "no-new-wrappers": 0, 74 | "no-invalid-this": 1 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/api.spec.js: -------------------------------------------------------------------------------- 1 | /* global SelectorGenerator */ 2 | describe("Api",function(){ 3 | it("should have property 'version'", function () { 4 | expect(SelectorGenerator.version).not.toBeUndefined(); 5 | }); 6 | it("should have method 'getSelector'", function () { 7 | expect(new SelectorGenerator().getSelector).not.toBeUndefined(); 8 | }); 9 | 10 | it("should have method 'getPath'", function () { 11 | expect(new SelectorGenerator().getPath).not.toBeUndefined(); 12 | }); 13 | }); -------------------------------------------------------------------------------- /tests/domParser.js: -------------------------------------------------------------------------------- 1 | /*global document, DOMParser*/ 2 | (function (DOMParser) { 3 | "use strict"; 4 | 5 | var 6 | DOMParser_proto = DOMParser.prototype, 7 | real_parseFromString = DOMParser_proto.parseFromString 8 | ; 9 | 10 | // Firefox/Opera/IE throw errors on unsupported types 11 | try { 12 | // WebKit returns null on unsupported types 13 | if ((new DOMParser).parseFromString("", "text/html")) { 14 | // text/html parsing is natively supported 15 | return; 16 | } 17 | } catch (ex) { } 18 | 19 | var createDocumentForOldBrowser = function(markup) { 20 | var doc = document.implementation.createHTMLDocument(""); 21 | if (markup.toLowerCase().indexOf(" -1) { 22 | doc.documentElement.innerHTML = markup; 23 | } 24 | else { 25 | doc.body.innerHTML = markup; 26 | } 27 | return doc; 28 | }; 29 | 30 | /** @const */ 31 | var _Function_apply_ = Function.prototype.apply 32 | 33 | , _Array_slice_ = Array.prototype.slice 34 | 35 | 36 | /** Use native or unsafe but fast 'bind' for service and performance needs 37 | * @const 38 | * @param {Object} object 39 | * @param {...} var_args 40 | * @return {Function} */ 41 | , _fastUnsafe_Function_bind_ = Function.prototype.bind || function(object, var_args) { 42 | var __method = this 43 | , args 44 | ; 45 | 46 | if( arguments.length > 1 ) { 47 | args = _Array_slice_.call(arguments, 1); 48 | return function () { 49 | return _Function_apply_.call(__method, object, args.concat(_Array_slice_.call(arguments))); 50 | }; 51 | } 52 | 53 | return function () { 54 | return _Function_apply_.call(__method, object, arguments); 55 | }; 56 | }; 57 | 58 | function prepareTextForIFrame(text) { 59 | return text 60 | .replace(/]*>[\s\S]*?<\/script>/gi, '')//remove script tags from HTML text 61 | //TODO:: not remove all