├── .editorconfig ├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.md ├── bower.json ├── dist ├── gearbox.js └── gearbox.min.js ├── doc ├── api-action.zh.md ├── api-dom.zh.md ├── api-str.zh.md ├── api-template.zh.md ├── api-ua.zh.md └── api-url.zh.md ├── gulpfile.js ├── package.json ├── src ├── _wrapper │ ├── dist-trad.js │ ├── mod-action.js │ └── mod-template.js ├── core.js ├── dom.js ├── plugin │ └── migrate.js ├── root.js ├── str.js ├── ua.js └── url.js └── test ├── _sandbox.html ├── test-action.js ├── test-dev.html ├── test-dist-trad-jquery.html ├── test-dist-trad.html ├── test-dom.js ├── test-root.js ├── test-str.js ├── test-template.js ├── test-ua.js └── test-url.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = tab 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [{package,bower}.json] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.{html}] 18 | quote_type = double 19 | 20 | [*.md] 21 | # use `
` to insert a line break explicitly. 22 | ; trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | ehthumbs.db 3 | [Dd]esktop.ini 4 | $RECYCLE.BIN/ 5 | .DS_Store 6 | .klive 7 | .dropbox.cache 8 | 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *.lnk 13 | 14 | .svn 15 | .idea 16 | 17 | node_modules/ 18 | bower_components/ 19 | npm-debug.log 20 | 21 | *.zip 22 | *.gz 23 | 24 | # no umd package for now. 25 | *.umd.* 26 | *-umd.* 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !package.json 4 | !README.md 5 | !LICENSE.txt 6 | !dist/*.js 7 | !src/plugin/*.js 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 cssmagic and other contributors 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gearbox 2 | 3 | > Lightweight JavaScript utilities for web development, based on `_` (Underscore) and `$` (jQuery/Zepto). 4 | 5 | > 为 Web 开发打造的轻量级 Javascript 工具库,基于 `_`(Underscore)和 `$`(jQuery/Zepto)。 6 | 7 | ## 兼容性 8 | 9 | #### 浏览器支持 10 | 11 | * 支持以下移动平台的主流浏览器: 12 | * iOS 7+ 13 | * Android 4+ 14 | 15 | * 同样支持以下桌面浏览器: 16 | * Firefox (Latest) 17 | * Chrome (Latest) 18 | * Safari (Latest) 19 | * IE 8+(需要 jQuery 1.x) 20 | 21 | #### 外部依赖 22 | 23 | * `_`(Underscore 1.6+) 24 | * `$`(Zepto 1.1+ 或 jQuery) 25 | 26 | ## 安装 27 | 28 | 0. 通过 npm 3 安装: 29 | 30 | ```sh 31 | $ npm install cmui-gearbox 32 | ``` 33 | 34 | 0. 在页面中加载 Gearbox 以及必要的依赖: 35 | 36 | ```html 37 | 38 | 39 | 40 | ``` 41 | 42 | ## API 文档 43 | 44 | 所有文档入口在 [Wiki 页面](https://github.com/CMUI/gearbox/wiki),快去看吧! 45 | 46 | ## 谁在用? 47 | 48 | 移动 UI 框架 [CMUI](https://github.com/CMUI/CMUI) 采用 Gearbox 作为全局的工具库,因此所有 CMUI 用户都在使用它: 49 | 50 | * [百姓网 - 手机版](http://m.baixing.com/) 51 | * ~~优e网 - 手机版 (m.uemall.com)~~(已下线) 52 | * ~~薇姿官方电子商城 - 手机版 (m.vichy.com.cn)~~(已改版) 53 | 54 | 以下桌面网站也在用 Gearbox: 55 | 56 | * [百姓网 - 桌面版](http://www.baixing.com/) 57 | 58 | *** 59 | 60 | ## 参与开发 61 | 62 | #### 功能模块 63 | 64 | Gearbox 的部分功能模块已经分离出去,成为独立项目。这些模块以开发依赖的方式引入,并打包到发布文件中。因此,参与这些独立项目的开发即可修改这些模块。 65 | 66 | * `gearbox.action` - [Action](https://github.com/cssmagic/action) 67 | * `gearbox.template` - [Underscore-template](https://github.com/cssmagic/underscore-template) 68 | 69 | #### 构建 70 | 71 | 0. 把本项目的代码 fork 并 clone 到本地。 72 | 0. 在项目根目录执行 `npm install`,安装必要的依赖。 73 | 0. 在项目根目录执行 `npm run dist`,运行构建脚本。 74 | 0. 构建生成的发布文件将存放在 `/dist` 目录下。 75 | 76 | #### 单元测试 77 | 78 | 0. 把本项目的代码 fork 并 clone 到本地。 79 | 0. 在项目根目录执行 `bower install`,安装必要的依赖。 80 | 0. 在浏览器中打开以下文件即可运行单元测试: 81 | * `test/test-dev.html` - 测试源码(用于开发阶段的测试) 82 | * `test/test-dist-trad.html` - 测试发布文件(用于测试 Zepto 的兼容性) 83 | * `test/test-dist-trad-jquery.html` - 测试发布文件(用于测试 jQuery 的兼容性) 84 | 85 | *** 86 | 87 | ## License 88 | 89 | [MIT License](http://www.opensource.org/licenses/mit-license.php) 90 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gearbox", 3 | "homepage": "https://github.com/CMUI/gearbox", 4 | "authors": [ 5 | "cssmagic " 6 | ], 7 | "description": "Lightweight JavaScript utilities for web development.", 8 | "main": "dist/gearbox.js", 9 | "moduleType": [ 10 | "globals" 11 | ], 12 | "keywords": [ 13 | "util", 14 | "utilities", 15 | "web" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "package.json", 21 | "node_modules", 22 | "bower_components", 23 | "gulpfile.js", 24 | "test", 25 | "doc", 26 | "src" 27 | ], 28 | "dependencies": { 29 | "underscore": "^1.6", 30 | "zepto.js": "^1.1" 31 | }, 32 | "devDependencies": { 33 | "html5shiv": "^3.7.3", 34 | "jquery": "1.*", 35 | "action": "0.3.2", 36 | "underscore-template": "0.3.0", 37 | "underscore.string": "2.*", 38 | "mocha.css": "0.1.0", 39 | "mocha": "1.*", 40 | "expect.js": "^0.3.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dist/gearbox.js: -------------------------------------------------------------------------------- 1 | /*! Gearbox | MIT License | https://github.com/CMUI/gearbox */ 2 | !function (window, undefined) { 3 | // check conflict 4 | if (window.gearbox) return false 5 | 6 | // shortcut 7 | var _ = window._ 8 | var $ = window.Zepto || window.jQuery || window.$ 9 | 10 | // check dependency 11 | if (!_ || !$) return false 12 | 13 | //////////////////// START: source code //////////////////// 14 | /* */ 15 | 16 | //////////////////// core //////////////////// 17 | // namespace 18 | var gearbox = {} 19 | 20 | // shortcut 21 | var document = window.document 22 | 23 | void function (window, gearbox) { 24 | 'use strict' 25 | 26 | gearbox.__defineModule = function (moduleName, apiSet) { 27 | if (!moduleName || !_.isString(moduleName) || !apiSet || !_.isObject(apiSet)) return 28 | 29 | if (moduleName === 'root') { 30 | // {apiSet}.xxx => gearbox.xxx 31 | _.each(apiSet, function (value, key) { 32 | gearbox[key] = value 33 | }) 34 | } else { 35 | // {apiSet}.xxx => gearbox.{key}.xxx 36 | gearbox[moduleName] = apiSet 37 | } 38 | 39 | } 40 | 41 | }(window, gearbox) 42 | 43 | ; 44 | 45 | //////////////////// str //////////////////// 46 | void function (window, gearbox) { 47 | 'use strict' 48 | 49 | // namespace 50 | var str = {} 51 | 52 | //////////////////// START: alternative to underscore.string //////////////////// 53 | // this section contains apis same as underscore.string's. 54 | // heavily inspired by [underscore.string](https://github.com/epeli/underscore.string) 55 | // source: https://github.com/epeli/underscore.string/blob/master/lib/underscore.string.js 56 | 57 | // util 58 | var nativeTrim = String.prototype.trim 59 | var nativeTrimRight = String.prototype.trimRight 60 | var nativeTrimLeft = String.prototype.trimLeft 61 | 62 | function makeString(object) { 63 | if (object == null) return '' 64 | return '' + object 65 | } 66 | function toPositive(number) { 67 | return number < 0 ? 0 : (+number || 0) 68 | } 69 | 70 | function defaultToWhiteSpace(characters) { 71 | if (characters == null) 72 | return '\\s' 73 | else if (characters.source) 74 | return characters.source 75 | else 76 | return '[' + str.escapeRegExp(characters) + ']' 77 | } 78 | str.escapeRegExp = function (str) { 79 | if (str == null) return '' 80 | return makeString(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') 81 | } 82 | 83 | // trim 84 | str.trim = function (str, characters) { 85 | if (str == null) return '' 86 | if (!characters && nativeTrim) return nativeTrim.call(str) 87 | characters = defaultToWhiteSpace(characters) 88 | return makeString(str).replace(new RegExp('^' + characters + '+|' + characters + '+$', 'g'), '') 89 | } 90 | str.ltrim = function (str, characters) { 91 | if (str == null) return '' 92 | if (!characters && nativeTrimLeft) return nativeTrimLeft.call(str) 93 | characters = defaultToWhiteSpace(characters) 94 | return makeString(str).replace(new RegExp('^' + characters + '+'), '') 95 | } 96 | str.rtrim = function (str, characters) { 97 | if (str == null) return '' 98 | if (!characters && nativeTrimRight) return nativeTrimRight.call(str) 99 | characters = defaultToWhiteSpace(characters) 100 | return makeString(str).replace(new RegExp(characters + '+$'), '') 101 | } 102 | 103 | // sub-string 104 | str.includes = function (str, needle, position) { 105 | if (needle === '') return true 106 | position = position == null ? 0 : Math.min(toPositive(position), str.length) 107 | return makeString(str).slice(position).indexOf(needle) !== -1 108 | } 109 | str.startsWith = function (str, starts, position) { 110 | str = makeString(str) 111 | starts = '' + starts 112 | position = position == null ? 0 : Math.min(toPositive(position), str.length) 113 | return str.lastIndexOf(starts, position) === position 114 | } 115 | str.endsWith = function (str, ends, position) { 116 | str = makeString(str) 117 | ends = '' + ends 118 | if (typeof position == 'undefined') { 119 | position = str.length - ends.length 120 | } else { 121 | position = Math.min(toPositive(position), str.length) - ends.length 122 | } 123 | return position >= 0 && str.indexOf(ends, position) === position 124 | } 125 | 126 | // aliases 127 | str.contains = str.includes 128 | // @DEPRECATED 129 | str.include = str.includes 130 | 131 | //////////////////// END: alternative to underscore.string //////////////////// 132 | 133 | 134 | // shortcuts for frequently-used characters 135 | str.CNY = str.RMB = '\xA5' // CNY(RMB) symbol 136 | str.FULL_WIDTH_CNY = str.FULL_WIDTH_RMB = '\uffe5' // full-width version of CNY(RMB) symbol 137 | 138 | // shortcuts for frequently-used regexp 139 | str.RE_EMAIL = /^(?:[a-z\d]+[_\-\+\.]?)*[a-z\d]+@(?:([a-z\d]+\-?)*[a-z\d]+\.)+([a-z]{2,})+$/i 140 | str.RE_MOBILE = /^1[3456789]\d{9}$/ 141 | str.RE_POSTCODE = /^\d{6}$/ 142 | 143 | // hash 144 | str.isHash = function (str) { 145 | str = gearbox.str.trim(str) 146 | return gearbox.str.startsWith(str, '#') 147 | } 148 | str.stripHash = function (str) { 149 | str = gearbox.str.trim(str) 150 | str = gearbox.str.ltrim(str, '#') 151 | if (gearbox.str.startsWith(str, '!')) str = str.slice(1) 152 | return str 153 | } 154 | 155 | // more `toNumber` methods 156 | str.toFloat = function (str) {return parseFloat(str)} 157 | str.toInt = function (str) { 158 | var n = parseFloat(str) 159 | return n < 0 ? Math.ceil(n) : Math.floor(n) 160 | } 161 | str.toFixed = function (str, i) {return gearbox.str.toFloat(gearbox.str.toFloat(str).toFixed(i || 0))} 162 | 163 | // exports 164 | gearbox.__defineModule('str', str) 165 | 166 | }(window, gearbox) 167 | 168 | ; 169 | 170 | //////////////////// root //////////////////// 171 | void function (window, gearbox) { 172 | 'use strict' 173 | 174 | var root = { 175 | $: function (input) { 176 | var result 177 | if (_.isElement(input)) { 178 | result = input.__$__ = input.__$__ || $(input) 179 | } else if (gearbox.dom.is$Element(input)) { 180 | result = input 181 | } else { 182 | result = $(input) 183 | } 184 | return result 185 | }, 186 | } 187 | 188 | gearbox.__defineModule('root', root) 189 | 190 | }(window, gearbox) 191 | 192 | ; 193 | 194 | //////////////////// ua //////////////////// 195 | void function (window, gearbox) { 196 | 'use strict' 197 | 198 | // namespace 199 | var ua = {} 200 | 201 | // detect by feature 202 | // we want it to work with chrome's touch device simulator, 203 | // so we don't use `document.createTouch` to detect. 204 | ua.isTouchDevice = ('ontouchstart' in window) && ('ontouchmove' in window) && 205 | ('ontouchend' in window) 206 | 207 | // detect by ua string 208 | ua.str = navigator.userAgent 209 | 210 | function _detect(ua) { 211 | var s = ua.str.toLowerCase() 212 | var _includes = gearbox.str.includes 213 | 214 | ua.isSafari = /\bapple\b/i.test(navigator.vendor) && /\bsafari\b/i.test(s) 215 | ua.isChrome = _includes(s, 'chrome') || 216 | _includes(s, 'crios') // both desktop and mobile version 217 | 218 | // platform version and device 219 | ua.osVersion = '' 220 | ua.isIOS = /\(i(?:phone|pod|pad)\b/.test(s) || /\bios \d+\./.test(s) 221 | if (ua.isIOS) { 222 | ua.isIPad = /\(ipad\b/.test(s) 223 | ua.isIPod = /\(ipod\b/.test(s) 224 | ua.isIPhone = /\(iphone\b/.test(s) 225 | ua.osVersion = (/[\/; i]os[\/: _](\d+(?:[\._]\d+)?)[\._; ]/.exec(s) || [0, ''])[1] 226 | .replace('_', '.') 227 | } else { 228 | var _includeAndroid = _includes(s, 'android') 229 | var _includeAdr = /\badr\b/.test(s) && /\blinux;\s*u;/.test(s) 230 | var _isJUC = /juc\s*\(linux;\s*u;\s*\d+\.\d+/.test(s) 231 | ua.isAndroid = _includeAndroid || _includeAdr || _isJUC 232 | if (_includeAdr || _isJUC) { 233 | ua.osVersion = ( 234 | /\badr[\/: ]?(\d+\.\d)\d*\b/.exec(s) || 235 | /\blinux;\s*u;\s*(\d+\.\d)\d*\b/.exec(s) || [0, ''] 236 | )[1] 237 | } else { 238 | ua.osVersion = (/\bandroid(?:_os)?[\/: ]?(\d+\.\d)\d*\b/.exec(s) || [0, ''])[1] 239 | } 240 | } 241 | // fix - Windows Phone might pretend to be iOS or Android 242 | if (_includes(s, 'windows phone')) { 243 | ua.isIOS = ua.isAndroid = false 244 | ua.osVersion = '' 245 | } 246 | if (ua.osVersion && !_includes(ua.osVersion, '.')) ua.osVersion += '.0' 247 | 248 | // summery 249 | ua.isMobileDevice = !!(ua.isIOS || ua.isAndroid) 250 | 251 | // get browser info 252 | var browser = '' 253 | if (_includes(s, 'micromessenger')) { 254 | browser = 'wechat' 255 | } else if (_includes(s, 'ucbrowser') || _includes(s, 'ucweb') || _includes(s, ' uc applewebkit')) { 256 | browser = 'uc' 257 | } else if (_includes(s, 'baiduhd') || _includes(s, 'baiduboxapp')) { 258 | browser = 'baidu-app' 259 | } else if (_includes(s, 'baidubrowser')) { 260 | browser = 'baidu-browser' 261 | } else if (_includes(s, 'mqqbrowser')) { 262 | browser = 'm-qq-browser' 263 | } else if (_includes(s, 'miuibrowser')) { 264 | browser = 'miui' 265 | } else if (_includes(s, '_weibo_') || _includes(s, ' weibo ')) { 266 | browser = 'weibo' 267 | } else if (_includes(s, 'firefox')) { 268 | browser = 'firefox' 269 | } else if (_includes(s, 'opera')) { 270 | browser = 'opera' 271 | } else if (_includes(s, ' edge/')) { 272 | browser = 'edge' 273 | } else if (_includes(s, 'iemobile')) { 274 | browser = 'ie-mobile' 275 | } 276 | // these two must be the last 277 | else if (ua.isChrome) { 278 | browser = 'chrome' 279 | if (ua.isAndroid && /\bwv\b/.test(s)) browser = 'chrome-webview' 280 | } else if (ua.isSafari) { 281 | browser = 'safari' 282 | } 283 | 284 | // fix - some browsers might be detected as Chrome or Safari 285 | if (browser !== 'chrome') ua.isChrome = false 286 | if (browser !== 'safari') ua.isSafari = false 287 | 288 | // get engine info 289 | var engine = '' 290 | var engineVersion = '' 291 | var testChrome = /chrome[^\d]*([\.\d]*)[ ;\/]/.exec(s) 292 | if (testChrome) { 293 | engine = 'chrome' 294 | engineVersion = _trimVersion(testChrome[1]) 295 | } else { 296 | var testWebKit = /webkit[^\d]*([\.\d]*)\+*[ ;\/]/.exec(s) 297 | if (testWebKit) { 298 | engine = 'webkit' 299 | engineVersion = _trimVersion(testWebKit[1]) 300 | } 301 | } 302 | if (!engine) { 303 | if (_includes(s, 'webkit')) { 304 | engine = 'webkit' 305 | } else if (ua.isIOS) { 306 | engine = 'webkit' 307 | } else if (ua.isAndroid && browser === 'm-qq-browser') { 308 | engine = 'webkit' 309 | } 310 | if (browser === 'firefox' && !ua.isIOS) engine = 'gecko' 311 | if (browser === 'opera' && !ua.isIOS && _includes(s, 'presto')) engine = 'presto' 312 | } 313 | // fix Windows Phone, IE Mobile and Edge 314 | if (browser === 'edge') { 315 | engine = 'edge' 316 | engineVersion = '' 317 | } else if (browser === 'ie-mobile') { 318 | engine = engineVersion = '' 319 | } 320 | 321 | // output 322 | ua.browser = browser 323 | ua.engine = engine 324 | ua.engineVersion = engineVersion 325 | return ua 326 | } 327 | 328 | // TODO: detect size and features of screen 329 | /* 330 | function __detectScreen(ua) {} 331 | */ 332 | 333 | // util 334 | // TODO: implement a stricter API: `gearbox.str.formatVersion(ver, length)`, e.g. ('1.2', 3) -> '1.2.0' 335 | function _trimVersion(ver, length) { 336 | var temp = ver.split('.') 337 | temp.length = length || 2 338 | return _.compact(temp).join('.') 339 | } 340 | 341 | // init 342 | _detect(ua) 343 | 344 | /* 345 | // exports for unit test 346 | ua.__detect = _detect 347 | ua.__trimVersion = _trimVersion 348 | */ 349 | 350 | // exports 351 | gearbox.__defineModule('ua', ua) 352 | 353 | }(window, gearbox) 354 | 355 | ; 356 | 357 | //////////////////// url //////////////////// 358 | void function (window, gearbox) { 359 | 'use strict' 360 | 361 | // namespace 362 | var url = {} 363 | 364 | // shortcut 365 | var loc = window.location 366 | 367 | // url param processing 368 | url.parseQuery = function (query) { 369 | var data = {} 370 | if (query && _.isString(query)) { 371 | var pairs = query.split('&'), pair, name, value 372 | _.each(pairs, function(n) { 373 | pair = n.split('=') 374 | name = pair[0] 375 | value = pair[1] || '' 376 | if (name) { 377 | data[decodeURIComponent(name).toLowerCase()] = decodeURIComponent(value) 378 | } 379 | }) 380 | } 381 | return data 382 | } 383 | 384 | var _query, _cacheParam = null 385 | function _getQuery() { 386 | return loc.search.slice(1) 387 | } 388 | url.getParam = function (s) { 389 | if (!s || !_.isString(s)) return false 390 | if (typeof _query === 'undefined') { // first run 391 | _query = _getQuery() 392 | } else { 393 | var currentQuery = _getQuery() 394 | if (currentQuery !== _query) { 395 | _cacheParam = null // clear cache to enforce re-parse 396 | _query = currentQuery 397 | } 398 | } 399 | if (!_cacheParam) { 400 | _cacheParam = this.parseQuery(_query) 401 | } 402 | return _cacheParam[s.toLowerCase()] 403 | } 404 | 405 | url.appendParam = function (url, param) { 406 | var s = '' 407 | url = _.isString(url) ? url : '' 408 | url = gearbox.url.removeHashFromUrl(url) 409 | if ($.isPlainObject(param)) { 410 | param = $.param(param) 411 | } else if (_.isString(param)) { 412 | // fix param string 413 | if (gearbox.str.startsWith(param, '&') || gearbox.str.startsWith(param, '?')) { 414 | param = param.slice(1) 415 | } 416 | } else { 417 | param = null 418 | } 419 | // append 420 | s = param ? url + (gearbox.str.includes(url, '?') ? '&' : '?') + param : s 421 | return s || false 422 | } 423 | 424 | // hash processing 425 | url.removeHashFromUrl = function (s) { 426 | return arguments.length ? String(s).split('#')[0] : '' 427 | } 428 | url.getHashFromUrl = function (s) { 429 | var a = String(s).split('#') 430 | a[0] = '' 431 | return a.join('#') 432 | } 433 | 434 | // aliases 435 | url.isHash = gearbox.str.isHash 436 | url.stripHash = gearbox.str.stripHash 437 | 438 | // exports 439 | gearbox.__defineModule('url', url) 440 | 441 | }(window, gearbox) 442 | 443 | ; 444 | 445 | //////////////////// dom //////////////////// 446 | void function (window, gearbox) { 447 | 'use strict' 448 | 449 | // namespace 450 | var dom = {} 451 | 452 | // shortcuts for frequently-used elements 453 | dom.$win = $(window) 454 | dom.$root = $(document.documentElement) 455 | dom.$body = $(document.body) 456 | 457 | // methods 458 | dom.is$Element = function (o) { 459 | if (!o || !_.isObject(o)) return false 460 | var result = false 461 | if ('__proto__' in o) { 462 | result = o.__proto__ === $.fn 463 | } else { 464 | var Class = ($.zepto && $.zepto.Z) || $ 465 | result = o instanceof Class 466 | } 467 | return result 468 | } 469 | 470 | // exports 471 | gearbox.__defineModule('dom', dom) 472 | 473 | }(window, gearbox) 474 | 475 | ; 476 | 477 | //////////////////// action //////////////////// 478 | // include and wrap external module: Action 479 | 480 | void function (window, gearbox) { 481 | 'use strict' 482 | 483 | /* ================= START: source code ================= */ 484 | /* */ 485 | /** 486 | * Action - Easy and lazy solution for click-event-binding. 487 | * Released under the MIT license. 488 | * https://github.com/cssmagic/action 489 | */ 490 | var action = (function () { 491 | 'use strict' 492 | 493 | // namespace 494 | var action = {} 495 | 496 | var SELECTOR = '[data-action]' 497 | var _actionList = {} 498 | 499 | // util 500 | function _getActionName($elem) { 501 | var result = $elem.attr('data-action') || '' 502 | if (!result) { 503 | var href = $.trim($elem.attr('href')) 504 | if (href && href.indexOf('#') === 0) result = href 505 | } 506 | return _formatActionName(result) 507 | } 508 | 509 | function _formatActionName(s) { 510 | return s ? $.trim(String(s).replace(/^[#!\s]+/, '')) : '' 511 | } 512 | 513 | function _init() { 514 | var $wrapper = $(document.body || document.documentElement) 515 | $wrapper.on('click', SELECTOR, function (ev) { 516 | // notice: default click behavior will be prevented. 517 | ev.preventDefault() 518 | 519 | var $elem = $(this) 520 | var actionName = _getActionName($elem) 521 | _handle(actionName, this) 522 | }) 523 | } 524 | 525 | function _handle(actionName, context) { 526 | if (!actionName) { 527 | /* 528 | console.warn('[Action] Empty action. Do nothing.') 529 | */ 530 | 531 | return 532 | } 533 | var fn = _actionList[actionName] 534 | if (fn && $.isFunction(fn)) { 535 | /* 536 | console.log('[Action] Executing action `%s`.', actionName) 537 | */ 538 | 539 | return fn.call(context || window) 540 | } else { 541 | /* 542 | console.error('[Action] Not found action `%s`.', actionName) 543 | */ 544 | } 545 | } 546 | 547 | // API 548 | function add(actionSet) { 549 | if (!$.isPlainObject(actionSet)) { 550 | /* 551 | console.error('[Action] Param must be a plain object.') 552 | */ 553 | 554 | return 555 | } 556 | 557 | $.each(actionSet, function (key, value) { 558 | var actionName = _formatActionName(key) 559 | 560 | if (!actionName) { 561 | /* 562 | console.error('[Action] The action name `%s` is invalid.', key) 563 | */ 564 | 565 | return 566 | } 567 | 568 | if (!$.isFunction(value)) { 569 | /* 570 | console.error('[Action] The function for action `%s` is invalid.', actionName) 571 | */ 572 | 573 | return 574 | } 575 | 576 | /* 577 | if (_actionList[actionName]) { 578 | console.warn('[Action] The existing action `%s` has been overwritten.', actionName) 579 | } 580 | */ 581 | 582 | _actionList[actionName] = value 583 | }) 584 | } 585 | 586 | function trigger(actionName, context) { 587 | return _handle(_formatActionName(actionName), context) 588 | } 589 | 590 | // init 591 | _init() 592 | 593 | /* 594 | // exports for unit test 595 | action.__actionList = _actionList 596 | action.__getActionName = _getActionName 597 | action.__formatActionName = _formatActionName 598 | */ 599 | 600 | // exports 601 | action.add = add 602 | action.trigger = trigger 603 | return action 604 | 605 | }()) 606 | 607 | /* */ 608 | /* ================= END: source code ================= */ 609 | 610 | gearbox.__defineModule('action', action) 611 | 612 | }(window, gearbox) 613 | ; 614 | 615 | //////////////////// template //////////////////// 616 | // include and wrap external module: Underscore-template 617 | 618 | void function (window, gearbox) { 619 | 'use strict' 620 | 621 | /* ================= START: source code ================= */ 622 | /* */ 623 | /** 624 | * Underscore-template - More APIs for Underscore's template engine - template fetching, rendering and caching. 625 | * Released under the MIT license. 626 | * https://github.com/cssmagic/underscore-template 627 | */ 628 | var template = (function () { 629 | 'use strict' 630 | 631 | // namespace 632 | var template = {} 633 | 634 | // config 635 | var ELEM_ID_PREFIX = 'template-' 636 | 637 | // cache 638 | var _cacheTemplate = {} 639 | var _cacheCompiledTemplate = {} 640 | 641 | // util - string 642 | function _trim(str) { 643 | return str.replace(/^\s+|\s+$/g, '') 644 | } 645 | function _include(str, substring) { 646 | return str.length > substring.length ? str.indexOf(substring) > -1 : false 647 | } 648 | function _startsWith(str, starts) { 649 | return str.length > starts.length ? str.indexOf(starts) === 0 : false 650 | } 651 | function _endsWith(str, ends) { 652 | return str.length > ends.length ? str.indexOf(ends) === (str.length - ends.length) : false 653 | } 654 | 655 | // util 656 | function _toTemplateId(id) { 657 | /** example: 658 | `#template-my-tmpl-001` -> `my-tmpl-001` 659 | `template-my-tmpl-001` -> `my-tmpl-001` 660 | `my-tmpl-001` -> `my-tmpl-001` 661 | */ 662 | id = id && _.isString(id) ? _trim(id).replace(/^[#!]+/, '') : '' 663 | return _trim(id).replace(ELEM_ID_PREFIX, '') 664 | } 665 | function _toElementId(id) { 666 | /** example: 667 | `template-my-tmpl-001` -> `template-my-tmpl-001` 668 | `my-tmpl-001` -> `template-my-tmpl-001` 669 | */ 670 | id = id && _.isString(id) ? _trim(id) : '' 671 | return _startsWith(id, ELEM_ID_PREFIX) ? id : ELEM_ID_PREFIX + id 672 | } 673 | // get template by ID (of dummy script element in html) 674 | function _getTemplateById(id) { 675 | if (!id) return false 676 | var result = '' 677 | var elementId = _toElementId(String(id)) 678 | var elem = document.getElementById(elementId) 679 | if (elem) { 680 | result = _trim(elem.innerHTML) 681 | /* 682 | if (!result) { 683 | console.warn('[Template] Element "#' + elementId + '" is empty!') 684 | } else if (!_isTemplateCode(result)) { 685 | console.warn('[Template] Template code in element "#' + elementId + '" is invalid!') 686 | } 687 | */ 688 | } else { 689 | /* 690 | console.warn('[Template] Element "#' + elementId + '" not found!') 691 | */ 692 | } 693 | return result 694 | } 695 | function _isTemplateCode(s) { 696 | var code = String(s) 697 | var config = _.templateSettings 698 | return ( 699 | // it must contain any template tags 700 | config.escape.test(code) || 701 | config.interpolate.test(code) || 702 | config.evaluate.test(code) 703 | ) && ( 704 | // it must contain variable name (if we have specified a variable name) 705 | config.variable ? _include(code, config.variable) : true 706 | ) 707 | } 708 | 709 | // API 710 | function add(id, templateCode) { 711 | // TODO: accept second param as a function, to support pre-compiled template. 712 | var result = false 713 | if (arguments.length < 2) { 714 | /* 715 | console.error('[Template] Missing param for `.add()` method.') 716 | */ 717 | return result 718 | } 719 | 720 | if (templateCode) { 721 | var templateId = _toTemplateId(id) 722 | /* 723 | if (_cacheTemplate[templateId] || _cacheCompiledTemplate[templateId]) { 724 | console.warn('[Template] Template id "' + templateId + '" already existed.') 725 | } 726 | */ 727 | if (_cacheCompiledTemplate[templateId]) { 728 | _cacheCompiledTemplate[templateId] = null 729 | } 730 | _cacheTemplate[templateId] = String(templateCode) 731 | result = true 732 | } 733 | return result 734 | } 735 | function render(id, data) { 736 | var result = '' 737 | if (arguments.length < 2) { 738 | /* 739 | console.error('[Template] Missing param for `.render()` method.') 740 | */ 741 | return result 742 | } 743 | 744 | var templateId = _toTemplateId(id) 745 | 746 | // TODO: refactor: use recursion to simplify these codes 747 | // search in _cacheCompiledTemplate 748 | var fn = _cacheCompiledTemplate[templateId] 749 | var templateCode = _cacheTemplate[templateId] 750 | if (_.isFunction(fn)) { 751 | result = fn(data) 752 | } 753 | // search in _cacheTemplate 754 | else if (templateCode) { 755 | fn = _.template(templateCode) 756 | _cacheCompiledTemplate[templateId] = fn 757 | result = fn(data) 758 | // clear _cacheTemplate 759 | _cacheTemplate[templateId] = null 760 | } 761 | // get template code from dom 762 | else { 763 | templateCode = _getTemplateById(templateId) 764 | if (templateCode) { 765 | fn = _.template(templateCode) 766 | _cacheCompiledTemplate[templateId] = fn 767 | result = fn(data) 768 | } 769 | } 770 | return result || '' 771 | } 772 | 773 | /* 774 | // exports for unit test 775 | template.__trim = _trim 776 | template.__include = _include 777 | template.__startsWith = _startsWith 778 | template.__endsWith = _endsWith 779 | template.__toTemplateId = _toTemplateId 780 | template.__toElementId = _toElementId 781 | template.__isTemplateCode = _isTemplateCode 782 | template.__cacheTemplate = _cacheTemplate 783 | template.__cacheCompiledTemplate = _cacheCompiledTemplate 784 | */ 785 | 786 | // exports 787 | template.add = add 788 | template.render = render 789 | return template 790 | 791 | }()) 792 | 793 | /* */ 794 | /* ================= END: source code ================= */ 795 | 796 | gearbox.__defineModule('template', template) 797 | 798 | }(window, gearbox) 799 | /* */ 800 | //////////////////// END: source code //////////////////// 801 | 802 | window.gearbox = gearbox 803 | 804 | }(window) -------------------------------------------------------------------------------- /dist/gearbox.min.js: -------------------------------------------------------------------------------- 1 | /*! Gearbox | MIT License | https://github.com/CMUI/gearbox */ 2 | !function(e,t){if(e.gearbox)return!1;var r=e._,n=e.Zepto||e.jQuery||e.$;if(!r||!n)return!1;var i={},o=e.document;void function(e,t){"use strict";t.__defineModule=function(e,n){e&&r.isString(e)&&n&&r.isObject(n)&&("root"===e?r.each(n,function(e,r){t[r]=e}):t[e]=n)}}(e,i),void function(e,t){"use strict";function r(e){return null==e?"":""+e}function n(e){return e<0?0:+e||0}function i(e){return null==e?"\\s":e.source?e.source:"["+o.escapeRegExp(e)+"]"}var o={},s=String.prototype.trim,u=String.prototype.trimRight,a=String.prototype.trimLeft;o.escapeRegExp=function(e){return null==e?"":r(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},o.trim=function(e,t){return null==e?"":!t&&s?s.call(e):(t=i(t),r(e).replace(new RegExp("^"+t+"+|"+t+"+$","g"),""))},o.ltrim=function(e,t){return null==e?"":!t&&a?a.call(e):(t=i(t),r(e).replace(new RegExp("^"+t+"+"),""))},o.rtrim=function(e,t){return null==e?"":!t&&u?u.call(e):(t=i(t),r(e).replace(new RegExp(t+"+$"),""))},o.includes=function(e,t,i){return""===t||(i=null==i?0:Math.min(n(i),e.length),r(e).slice(i).indexOf(t)!==-1)},o.startsWith=function(e,t,i){return e=r(e),t=""+t,i=null==i?0:Math.min(n(i),e.length),e.lastIndexOf(t,i)===i},o.endsWith=function(e,t,i){return e=r(e),t=""+t,i="undefined"==typeof i?e.length-t.length:Math.min(n(i),e.length)-t.length,i>=0&&e.indexOf(t,i)===i},o.contains=o.includes,o.include=o.includes,o.CNY=o.RMB="¥",o.FULL_WIDTH_CNY=o.FULL_WIDTH_RMB="¥",o.RE_EMAIL=/^(?:[a-z\d]+[_\-\+\.]?)*[a-z\d]+@(?:([a-z\d]+\-?)*[a-z\d]+\.)+([a-z]{2,})+$/i,o.RE_MOBILE=/^1[3456789]\d{9}$/,o.RE_POSTCODE=/^\d{6}$/,o.isHash=function(e){return e=t.str.trim(e),t.str.startsWith(e,"#")},o.stripHash=function(e){return e=t.str.trim(e),e=t.str.ltrim(e,"#"),t.str.startsWith(e,"!")&&(e=e.slice(1)),e},o.toFloat=function(e){return parseFloat(e)},o.toInt=function(e){var t=parseFloat(e);return t<0?Math.ceil(t):Math.floor(t)},o.toFixed=function(e,r){return t.str.toFloat(t.str.toFloat(e).toFixed(r||0))},t.__defineModule("str",o)}(e,i),void function(e,t){"use strict";var i={$:function(e){var i;return i=r.isElement(e)?e.__$__=e.__$__||n(e):t.dom.is$Element(e)?e:n(e)}};t.__defineModule("root",i)}(e,i),void function(e,t){"use strict";function n(e){var r=e.str.toLowerCase(),n=t.str.includes;if(e.isSafari=/\bapple\b/i.test(navigator.vendor)&&/\bsafari\b/i.test(r),e.isChrome=n(r,"chrome")||n(r,"crios"),e.osVersion="",e.isIOS=/\(i(?:phone|pod|pad)\b/.test(r)||/\bios \d+\./.test(r),e.isIOS)e.isIPad=/\(ipad\b/.test(r),e.isIPod=/\(ipod\b/.test(r),e.isIPhone=/\(iphone\b/.test(r),e.osVersion=(/[\/; i]os[\/: _](\d+(?:[\._]\d+)?)[\._; ]/.exec(r)||[0,""])[1].replace("_",".");else{var o=n(r,"android"),s=/\badr\b/.test(r)&&/\blinux;\s*u;/.test(r),u=/juc\s*\(linux;\s*u;\s*\d+\.\d+/.test(r);e.isAndroid=o||s||u,s||u?e.osVersion=(/\badr[\/: ]?(\d+\.\d)\d*\b/.exec(r)||/\blinux;\s*u;\s*(\d+\.\d)\d*\b/.exec(r)||[0,""])[1]:e.osVersion=(/\bandroid(?:_os)?[\/: ]?(\d+\.\d)\d*\b/.exec(r)||[0,""])[1]}n(r,"windows phone")&&(e.isIOS=e.isAndroid=!1,e.osVersion=""),e.osVersion&&!n(e.osVersion,".")&&(e.osVersion+=".0"),e.isMobileDevice=!(!e.isIOS&&!e.isAndroid);var a="";n(r,"micromessenger")?a="wechat":n(r,"ucbrowser")||n(r,"ucweb")||n(r," uc applewebkit")?a="uc":n(r,"baiduhd")||n(r,"baiduboxapp")?a="baidu-app":n(r,"baidubrowser")?a="baidu-browser":n(r,"mqqbrowser")?a="m-qq-browser":n(r,"miuibrowser")?a="miui":n(r,"_weibo_")||n(r," weibo ")?a="weibo":n(r,"firefox")?a="firefox":n(r,"opera")?a="opera":n(r," edge/")?a="edge":n(r,"iemobile")?a="ie-mobile":e.isChrome?(a="chrome",e.isAndroid&&/\bwv\b/.test(r)&&(a="chrome-webview")):e.isSafari&&(a="safari"),"chrome"!==a&&(e.isChrome=!1),"safari"!==a&&(e.isSafari=!1);var c="",d="",l=/chrome[^\d]*([\.\d]*)[ ;\/]/.exec(r);if(l)c="chrome",d=i(l[1]);else{var f=/webkit[^\d]*([\.\d]*)\+*[ ;\/]/.exec(r);f&&(c="webkit",d=i(f[1]))}return c||(n(r,"webkit")?c="webkit":e.isIOS?c="webkit":e.isAndroid&&"m-qq-browser"===a&&(c="webkit"),"firefox"!==a||e.isIOS||(c="gecko"),"opera"===a&&!e.isIOS&&n(r,"presto")&&(c="presto")),"edge"===a?(c="edge",d=""):"ie-mobile"===a&&(c=d=""),e.browser=a,e.engine=c,e.engineVersion=d,e}function i(e,t){var n=e.split(".");return n.length=t||2,r.compact(n).join(".")}var o={};o.isTouchDevice="ontouchstart"in e&&"ontouchmove"in e&&"ontouchend"in e,o.str=navigator.userAgent,n(o),t.__defineModule("ua",o)}(e,i),void function(e,t){"use strict";function i(){return s.search.slice(1)}var o={},s=e.location;o.parseQuery=function(e){var t={};if(e&&r.isString(e)){var n,i,o,s=e.split("&");r.each(s,function(e){n=e.split("="),i=n[0],o=n[1]||"",i&&(t[decodeURIComponent(i).toLowerCase()]=decodeURIComponent(o))})}return t};var u,a=null;o.getParam=function(e){if(!e||!r.isString(e))return!1;if("undefined"==typeof u)u=i();else{var t=i();t!==u&&(a=null,u=t)}return a||(a=this.parseQuery(u)),a[e.toLowerCase()]},o.appendParam=function(e,i){var o="";return e=r.isString(e)?e:"",e=t.url.removeHashFromUrl(e),n.isPlainObject(i)?i=n.param(i):r.isString(i)?(t.str.startsWith(i,"&")||t.str.startsWith(i,"?"))&&(i=i.slice(1)):i=null,o=i?e+(t.str.includes(e,"?")?"&":"?")+i:o,o||!1},o.removeHashFromUrl=function(e){return arguments.length?String(e).split("#")[0]:""},o.getHashFromUrl=function(e){var t=String(e).split("#");return t[0]="",t.join("#")},o.isHash=t.str.isHash,o.stripHash=t.str.stripHash,t.__defineModule("url",o)}(e,i),void function(e,t){"use strict";var i={};i.$win=n(e),i.$root=n(o.documentElement),i.$body=n(o.body),i.is$Element=function(e){if(!e||!r.isObject(e))return!1;var t=!1;if("__proto__"in e)t=e.__proto__===n.fn;else{var i=n.zepto&&n.zepto.Z||n;t=e instanceof i}return t},t.__defineModule("dom",i)}(e,i),void function(e,t){"use strict";var r=function(){function t(e){var t=e.attr("data-action")||"";if(!t){var i=n.trim(e.attr("href"));i&&0===i.indexOf("#")&&(t=i)}return r(t)}function r(e){return e?n.trim(String(e).replace(/^[#!\s]+/,"")):""}function i(){var e=n(o.body||o.documentElement);e.on("click",d,function(e){e.preventDefault();var r=n(this),i=t(r);s(i,this)})}function s(t,r){if(t){var i=l[t];return i&&n.isFunction(i)?i.call(r||e):void 0}}function u(e){n.isPlainObject(e)&&n.each(e,function(e,t){var i=r(e);i&&n.isFunction(t)&&(l[i]=t)})}function a(e,t){return s(r(e),t)}var c={},d="[data-action]",l={};return i(),c.add=u,c.trigger=a,c}();t.__defineModule("action",r)}(e,i),void function(e,t){"use strict";var n=function(){function e(e){return e.replace(/^\s+|\s+$/g,"")}function t(e,t){return e.length>t.length&&0===e.indexOf(t)}function n(t){return t=t&&r.isString(t)?e(t).replace(/^[#!]+/,""):"",e(t).replace(d,"")}function i(n){return n=n&&r.isString(n)?e(n):"",t(n,d)?n:d+n}function s(t){if(!t)return!1;var r="",n=i(String(t)),s=o.getElementById(n);return s&&(r=e(s.innerHTML)),r}function u(e,t){var r=!1;if(arguments.length<2)return r;if(t){var i=n(e);f[i]&&(f[i]=null),l[i]=String(t),r=!0}return r}function a(e,t){var i="";if(arguments.length<2)return i;var o=n(e),u=f[o],a=l[o];return r.isFunction(u)?i=u(t):a?(u=r.template(a),f[o]=u,i=u(t),l[o]=null):(a=s(o),a&&(u=r.template(a),f[o]=u,i=u(t))),i||""}var c={},d="template-",l={},f={};return c.add=u,c.render=a,c}();t.__defineModule("template",n)}(e,i),e.gearbox=i}(window); -------------------------------------------------------------------------------- /doc/api-action.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `action` 模块 2 | 3 | 本项目的 `action` 模块由 [Action](https://github.com/cssmagic/action) 类库实现,详细功能说明请参见该类库的 [API 文档](https://github.com/cssmagic/action/issues/9) 和 [wiki](https://github.com/cssmagic/action/wiki)。 4 | 5 | 需要注意的是,所有 JavaScript API 均挂接在 `gearbox.action` 命名空间下。比如: 6 | 7 | * `action.add()` → `gearbox.action.add()` 8 | * `action.trigger()` → `gearbox.action.trigger()` 9 | * ... 10 | -------------------------------------------------------------------------------- /doc/api-dom.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `dom` 模块 2 | 3 | ## JavaScript 变量   4 | 5 | 为减少业务层对常用 DOM 元素的重复获取和包装,`dom` 模块预先缓存了这些元素的 Zepto 包装对象。在业务层可以直接使用。 6 | 7 | ### `.$win`   8 | 9 | `window` 对象的 Zepto 包装对象。 10 | 11 | #### 示例 12 | 13 | 监听 `resize` 事件: 14 | 15 | ```js 16 | gearbox.dom.$win.on('resize', function (ev) { 17 | //... 18 | }) 19 | ``` 20 | 21 | ### `.$root`   22 | 23 | `document.documentElement` 对象(即 `` 元素)的 Zepto 包装对象。 24 | 25 | ### `.$body`   26 | 27 | `document.body` 对象(即 `` 元素)的 Zepto 包装对象。 28 | 29 | #### 注意事项 30 | 31 | 为确保对 `document.body` 对象的正确获取,加载 Gearbox 的脚本标签须放置在页面的 `` 标签内。当然,根据前端性能的最佳实践,所有外链脚本也确实应该放置在页面的最底部: 32 | 33 | ```html 34 | 35 | ... 36 | 37 | ... 38 | 39 | 40 | 41 | ``` 42 | 43 | ## JavaScript 接口   44 | 45 | ### `.is$Element(obj)`   46 | 47 | 判断是否为 Zepto 包装对象(或 Zepto 集合)。 48 | 49 | 如果外部环境没有加载 Zepto 但有 jQuery,则理论上此方法也可以判断 jQuery 包装对象(或 jQuery 集合)。 50 | 51 | #### 参数 52 | 53 | * `obj` -- 任意类型。需要判断的对象。 54 | 55 | #### 返回值 56 | 57 | 布尔值。判断结果。 58 | 59 | #### 示例 60 | 61 | ```js 62 | gearbox.dom.is$Element(gearbox.dom.$win) // => true 63 | ``` 64 | -------------------------------------------------------------------------------- /doc/api-str.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `str` 模块 2 | 3 | ## JavaScript 变量   4 | 5 | 以下预定义的变量在业务层可以直接使用。 6 | 7 | ### `.RE_EMAIL`   8 | 9 | 校验电子邮箱的正则表达式。 10 | 11 | #### 示例 12 | 13 | ```js 14 | gearbox.str.RE_EMAIL.test('foo@bar.com') // => true 15 | gearbox.str.RE_EMAIL.test('foo@bar') // => false 16 | gearbox.str.RE_EMAIL.test('foo.bar.cn') // => false 17 | ``` 18 | 19 | ### `.RE_MOBILE`   20 | 21 | 校验手机号的正则表达式。 22 | 23 | 手机号必须是中国大陆的手机号,11 位数字,不可包含空格、横杠等特殊字符。 24 | 25 | #### 示例 26 | 27 | ```js 28 | gearbox.str.RE_MOBILE.test('13355668899') // => true 29 | gearbox.str.RE_MOBILE.test('021-55668899') // => false 30 | gearbox.str.RE_MOBILE.test('10086') // => false 31 | ``` 32 | 33 | ### `.RE_POSTCODE`   34 | 35 | 校验邮政编码的正则表达式。 36 | 37 | 邮政编码必须是中国大陆的邮政编码,6 位数字,不可包含空格、横杠等特殊字符。 38 | 39 | #### 示例 40 | 41 | ```js 42 | gearbox.str.RE_POSTCODE.test('200030') // => true 43 | gearbox.str.RE_POSTCODE.test('4008517517') // => false 44 | gearbox.str.RE_POSTCODE.test('1234') // => false 45 | ``` 46 | 47 | *** 48 | 49 | ### `.CNY`   50 | 51 | > **别名**: `.RMB` 52 | 53 | 人民币符号 `¥`。 54 | 55 | ### `.FULL_WIDTH_CNY`   56 | 57 | > **别名**: `.FULL_WIDTH_RMB` 58 | 59 | 全角的人民币符号 `¥`。 60 | 61 | #### 示例 62 | 63 | ```js 64 | // 将所有全角的人民币符号替换为半角 65 | var text = '¥1000 - ¥2000' 66 | text.split(gearbox.str.FULL_WIDTH_CNY).join(gearbox.str.CNY) // => '¥1000 - ¥2000' 67 | ``` 68 | 69 | 70 | ## JavaScript 接口   71 | 72 | ### `.isHash(string)`   73 | 74 | 判断是否为 hash 字符串。 75 | 76 | Hash 字符串以 `#` 开头,比如 `#foo` 就是一个 hash 字符串。这种字符串通常出现于链接锚点(`bar`)、ID 选择符(`$('#id')`)、Twitter 标签或 `location.hash` 的值等等。 77 | 78 | 字符串开头的空白符将被忽略,不影响判断结果。 79 | 80 | #### 参数 81 | 82 | * `string` -- 字符串。需要判断的字符串。 83 | 84 | #### 返回值 85 | 86 | 布尔值。判断结果。 87 | 88 | #### 示例 89 | 90 | ```js 91 | gearbox.str.isHash('#foo') // => true 92 | gearbox.str.isHash('bar') // => false 93 | gearbox.str.isHash(' #foo-bar') // => true 94 | ``` 95 | 96 | *** 97 | 98 | ### `.stripHash(string)`   99 | 100 | 去除 hash 字符串开头的 `#` 字符。 101 | 102 | 字符串头尾的空白符也将被去除。 103 | 104 | #### 参数 105 | 106 | * `string` -- 字符串(非字符串会被强制转换为字符串)。需要处理的字符串。 107 | 108 | #### 返回值 109 | 110 | 字符串。处理结果。 111 | 112 | #### 示例 113 | 114 | ```js 115 | gearbox.str.stripHash('#foo') // => 'foo' 116 | gearbox.str.stripHash('bar') // => 'bar' 117 | gearbox.str.stripHash(' #foo-bar') // => 'foo-bar' 118 | ``` 119 | 120 | *** 121 | 122 | ### `.toFloat(string)`   123 | 124 | 转换为浮点数。 125 | 126 | 可以视为 `parseFloat()` 的别名。 127 | 128 | #### 参数 129 | 130 | * `string` -- 字符串。 131 | 132 | #### 返回值 133 | 134 | 数字。转换结果。 135 | 136 | #### 示例 137 | 138 | ```js 139 | gearbox.str.toFloat('0') // => 0 140 | gearbox.str.toFloat('1.77') // => 1.77 141 | gearbox.str.toFloat('2.3.6') // => 2.3 142 | gearbox.str.toFloat('2e3') // => 2000 143 | gearbox.str.toFloat('1.23foo') // => 1.23 144 | gearbox.str.toFloat('foo123') // => NaN 145 | ``` 146 | 147 | *** 148 | 149 | ### `.toInt(string)`   150 | 151 | 转换为整数。 152 | 153 | 可以视为 `parseInt(string, 10)` 的别名。直接取整,不做舍入。 154 | 155 | #### 参数 156 | 157 | * `string` -- 字符串。 158 | 159 | #### 返回值 160 | 161 | 数字。转换结果。 162 | 163 | #### 示例 164 | 165 | ```js 166 | gearbox.str.toInt('0') // => 0 167 | gearbox.str.toInt('1.77') // => 1 168 | gearbox.str.toInt('2.3.6') // => 2 169 | gearbox.str.toInt('2e3') // => 2000 170 | gearbox.str.toInt('1.23foo') // => 1 171 | gearbox.str.toInt('foo123') // => NaN 172 | ``` 173 | 174 | *** 175 | 176 | ### `.toFixed(string, [i])`   177 | 178 | 转换为固定位数的小数。会做舍入。 179 | 180 | 与 `Number.prototype.toFixed()` 的功能类似,但此接口接收字符串,输出数字。 181 | 182 | #### 参数 183 | 184 | * `string` -- 字符串。 185 | * `i` -- 可选。整数。保留的位数。默认值为 `0`。 186 | 187 | #### 返回值 188 | 189 | 数字。转换结果。 190 | 191 | #### 示例 192 | 193 | ```js 194 | gearbox.str.toFixed('0') // => 0 195 | gearbox.str.toFixed('0', 2) // => 0 196 | gearbox.str.toFixed('1.77') // => 2 197 | gearbox.str.toFixed('1.77', 1) // => 1.8 198 | gearbox.str.toFixed('2.3.6', 2) // => 2.3 199 | gearbox.str.toFixed('2e3', 3) // => 2000 200 | gearbox.str.toFixed('1.23foo', 1) // => 1.2 201 | gearbox.str.toFixed('foo123') // => NaN 202 | ``` 203 | 204 | 205 | ## Underscore.string 同名接口   206 | 207 | `str` 模块提供的部分接口与 Underscore.string 类库的同名接口完全一致。这些接口的源码均引用了 Underscore.string 的实现,并存放在 `src/str-backup.js` 文件中。 208 | 209 | ### 字符串裁剪 210 | 211 | * `.trim(string, [characters])`   212 | 213 | 请参考 Underscore.string 的文档: [`trim`](https://epeli.github.io/underscore.string/#trim-string-characters-gt-string) 214 | 215 | * `.ltrim(string, [characters])`   216 | 217 | 请参考 Underscore.string 的文档: [`ltrim`](https://epeli.github.io/underscore.string/#ltrim-string-characters-gt-string) 218 | 219 | * `.rtrim(string, [characters])`   220 | 221 | 请参考 Underscore.string 的文档: [`rtrim`](https://epeli.github.io/underscore.string/#rtrim-string-characters-gt-string) 222 | 223 | ### 字符串包含关系 224 | 225 | * `.includes(string, substring)`   226 | 227 | > **别名**: `.contains()` 228 | 229 | 请参考 Underscore.string 的文档: [`include`](https://epeli.github.io/underscore.string/#include-string-substring-gt-boolean) 230 | 231 | **注意**:从 Gearbox v0.6 开始,`.include()` 已弃用,已改名为 `.includes()`。 232 | 233 | * `.startsWith(string, starts, [position])`   234 | 235 | 请参考 Underscore.string 的文档: [`startsWith`](https://epeli.github.io/underscore.string/#startswith-string-starts-position-gt-boolean) 236 | 237 | * `.endsWith(string, ends, [position])`   238 | 239 | 请参考 Underscore.string 的文档: [`endsWith`](https://epeli.github.io/underscore.string/#endswith-string-ends-position-gt-boolean) 240 | -------------------------------------------------------------------------------- /doc/api-template.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `template` 模块 2 | 3 | 本项目的 `template` 模块由 [Underscore-template](https://github.com/cssmagic/underscore-template) 类库实现,详细功能说明请参见该类库的 [API 文档](https://github.com/cssmagic/underscore-template/issues/5) 和 [wiki](https://github.com/cssmagic/underscore-template/wiki)。 4 | 5 | 需要注意的是,所有 JavaScript API 均挂接在 `gearbox.template` 命名空间下。比如: 6 | 7 | * `template.add()` → `gearbox.template.add()` 8 | * `template.render()` → `gearbox.template.render()` 9 | * ... 10 | -------------------------------------------------------------------------------- /doc/api-ua.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `ua` 模块 2 | 3 | 这个模块在加载时会对当前 UA 进行探测(通过特性检测或 UA 字符串分析等方式),并以变量的方式提供探测结果。 4 | 5 | 这些探测浏览器内核和类型的接口仅用于底层开发或流量统计,不建议在业务层的常规功能中使用。 6 | 7 | ## JavaScript 变量   8 | 9 | ### `.isTouchDevice`   10 | 11 | 布尔值。当前 UA 是否为触屏设备。Chrome 开启触摸调试之后也将被视为触屏设备。 12 | 13 | 此探测基于特性检测。 14 | 15 | *** 16 | 17 | ### `.isSafari`   18 | 19 | 布尔值。当前 UA 是否为 Safari 浏览器(包括桌面版与移动版)。 20 | 21 | 此探测基于 UA 信息。 22 | 23 | ### `.isChrome`   24 | 25 | 布尔值。当前 UA 是否为 Chrome 浏览器(包括桌面版与移动版)。 26 | 27 | 此探测基于 UA 信息。 28 | 29 | *** 30 | 31 | ### `.isIOS`   32 | 33 | 布尔值。当前操作系统是否为 iOS 系统。 34 | 35 | 还有以下对 iOS 设备更细节的探测(值为布尔值或 `undefined`): 36 | 37 | * `.isIPhone` -- 当前 UA 是否为 iPhone。 38 | * `.isIPad` -- 当前 UA 是否为 iPad。 39 | * `.isIPod` -- 当前 UA 是否为 iPod touch。 40 | 41 | 这些探测均基于 UA 信息。 42 | 43 | ### `.isAndroid`   44 | 45 | 布尔值。当前 UA 是否为 Android 系统。 46 | 47 | 此探测基于 UA 信息。 48 | 49 | ### `.isMobileDevice`   50 | 51 | 布尔值。当前 UA 是否为移动设备。所有 iOS 和 Android 设备会被识别为移动设备。 52 | 53 | 此探测基于 UA 信息。 54 | 55 | *** 56 | 57 | ### `.osVersion`   58 | 59 | 字符串。当前移动操作系统的版本号,格式为 `{主版本号}.{次版本号}`。仅可识别 iOS 和 Android 系统的版本号,对于非移动操作系统或不可识别的移动操作系统,其值一律为空字符串。 60 | 61 | 此探测基于 UA 信息。 62 | 63 | #### 示例 64 | 65 | * `'7.0'` -- 对 iOS 7.0.1 的探测结果。 66 | * `'4.4'` -- 对 Android 4.4.4 的探测结果。 67 | * `''` -- 对 Windows、Mac OS、WinPhone 等系统的探测结果。 68 | 69 | *** 70 | 71 | ### `.browser`   72 | 73 | 字符串。当前浏览器的名称,可能的值如下: 74 | 75 | * `'chrome'` -- Chrome(谷歌浏览器) 76 | * `'safari'` -- Safari(苹果浏览器) 77 | * `'firefox'` -- Firefox(火狐浏览器) 78 | * `'opera'` -- Opera 浏览器 79 | * `'uc'` -- UC 浏览器 80 | * `'baidu-app'` -- 百度客户端 81 | * `'baidu-browser'` -- 百度浏览器 82 | * `'m-qq-browser'` -- 手机 QQ 浏览器 83 | * `'miui'` -- 小米浏览器 84 | * `'ie-mobile'` -- IE 移动版 85 | * `'edge'` -- 微软 Edge 浏览器 86 | * `'wechat'` -- 微信 87 | * `'weibo'` -- 微博 88 | * `'chrome-webview'` -- 采用 Chrome 内核的 WebView 89 | 90 | 此探测基于 UA 信息。 91 | 92 | ### `.engine`   93 | 94 | 字符串。当前浏览器的引擎(内核)名称,可能的值如下: 95 | 96 | * `'webkit'` -- iOS 与新版 Android 浏览器的内核 97 | * `'chrome'` -- Chrome 内核(新版 Android 浏览器与 Opera 的内核) 98 | * `'gecko'` -- Firefox 的内核 99 | * `'presto'` -- 旧版 Opera 的内核 100 | * `'edge'` -- Edge 内核 101 | 102 | 对于无法识别的引擎,其值一律为空字符串。 103 | 104 | 此探测基于 UA 信息。 105 | 106 | ### `.engineVersion`   107 | 108 | 字符串。当前浏览器的引擎(内核)版本。对于无法识别的引擎版本,其值一律为空字符串。 109 | 110 | 此探测基于 UA 信息。 111 | 112 | #### 示例 113 | 114 | * `'533.1'` -- 对 Android 2.2.2 内置浏览器引擎 WebKit 版本的探测结果。 115 | * `'43.0'` -- 对 Chrome 43.0.2357.65 引擎版本的探测结果。 116 | * `''` -- 对 Firefox 引擎版本的探测结果。 117 | * `''` -- 对 Edge 引擎版本的探测结果。 118 | -------------------------------------------------------------------------------- /doc/api-url.zh.md: -------------------------------------------------------------------------------- 1 | # API 文档 - `url` 模块 2 | 3 | ## 术语   4 | 5 | #### Query String 6 | 7 | 举例来说,`http://domain.com/path/file?foo&bar=2` 中的 `foo&bar=2` 部分即为 query string。 8 | 9 | Query String 的本质是对一些名值对进行编码和序列化之后的结果。而 query string 的解析就是一个反序列化和解码的过程。 10 | 11 | #### URL 参数 12 | 13 | Query String 所保存的这些名值对即称作 “URL 参数”。 14 | 15 | 16 | ## JavaScript 接口   17 | 18 | ### `.parseQuery(queryString)`   19 | 20 | 把 query string 解析为以对象的方式保存的名值对。 21 | 22 | #### 参数 23 | 24 | * `queryString` -- 字符串。需要解析的 query string。 25 | 26 | #### 返回值 27 | 28 | 对象。解析结果,以名值对的方式保存。 29 | 30 | #### 示例 31 | 32 | ```js 33 | gearbox.url.parseQuery('foo=1&bar=2') // => {foo: '1', bar: '2'} 34 | gearbox.url.parseQuery('foo=&bar=2') // => {foo: '', bar: '2'} 35 | gearbox.url.parseQuery('foo&bar=2') // => {foo: '', bar: '2'} 36 | gearbox.url.parseQuery('') // => {} 37 | ``` 38 | 39 | #### 注意事项 40 | 41 | * 传入不合法的参数,则一律返回空对象(`{}`)。 42 | * Query string 中的所有 key 都会被转换为小写。 43 | * Query string 的格式为 `foo=1&bar=true`,不包含问号。如果此接口接收的参数以问号开头,则会被视为第一个 URL 参数的一部分,因为 `?foo` 是一个合法的 URL 参数名。 44 | * Query string 中多个连续的 `&` 字符会被视为一个。 45 | * 解析结果中的值如果为 `true`、`false`、`null`、`undefined` 或数字时,总是以字符串的方式保存,不会自动转换数据类型。 46 | * 当 query string 中出现某个 key 但没有对应的值时(比如 `foo&bar=2` 或 `foo=&bar=2` 中的 `foo`),其值将解析为空字符串。 47 | 48 | #### 已知问题 49 | 50 | * 重复出现的 key 将只解析最后一次出现的值,不会把所有值加入到一个数组中。 51 | * 不处理复杂模式的 key,比如 `foo[]` 或 `foo[bar]` 都不会被视为特殊含义,只会视为普通的 key。 52 | 53 | *** 54 | 55 | ### `.getParam(key)`   56 | 57 | 获取当前页面 URL 的某个 URL 参数的值。 58 | 59 | #### 参数 60 | 61 | * `key` -- 字符串。需要获取的 URL 参数名,忽略大小写。 62 | 63 | #### 返回值 64 | 65 | 字符串或 `undefined`。对应 URL 参数的值。 66 | 67 | #### 示例 68 | 69 | 假设当前页面的 URL 为 `http://domain.com/path/file?foo&bar=2`,此时: 70 | 71 | ```js 72 | gearbox.url.getParam('foo') // => '' 73 | gearbox.url.getParam('bar') // => '2' 74 | gearbox.url.getParam('absentKey') // => undefined 75 | ``` 76 | 77 | #### 注意事项 78 | 79 | * Query string 的解析方式参见 `.parseQuery()` 方法。 80 | * 当页面 URL 发生变化时(比如调用 `history.pushState()` 等方法时),返回结果总是当前的。 81 | 82 | *** 83 | 84 | ### `.appendParam(url, param)`   85 | 86 | 为给定的 URL 附加新的参数。 87 | 88 | #### 参数 89 | 90 | * `url` -- 字符串。待处理的 URL。 91 | * `param` -- 对象。需要附加的 URL 参数(名值对)。 92 | 93 | #### 返回值 94 | 95 | 字符串。已附加 URL 参数的新的 URL。 96 | 97 | #### 示例 98 | 99 | ```js 100 | var url = 'http://domain.com/path/file' 101 | 102 | url = gearbox.url.appendParam(url, {foo: 'bar'}) 103 | // => 'http://domain.com/path/file?foo=bar' 104 | 105 | url = gearbox.url.appendParam(url, {test: 1}) 106 | // => 'http://domain.com/path/file?foo=bar&test=1' 107 | ``` 108 | 109 | #### 注意事项 110 | 111 | * `param` 参数中的 key 并不会覆盖 `url` 参数中已有的同名 key,只是追加一个同名参数。 112 | 113 | *** 114 | 115 | ### `.removeHashFromUrl(url)`   116 | 117 | 把 URL 中的 hash 部分去除。 118 | 119 | #### 参数 120 | 121 | * `url` -- 字符串。待处理的 URL,可以是完整的 URL,也可以是相对路径。若传入其它类型的数值,将被转换为字符串。 122 | 123 | #### 返回值 124 | 125 | 字符串。去除 hash 之后的 URL。若未传入参数则返回空字符串。 126 | 127 | #### 示例 128 | 129 | ```js 130 | var url = 'http://domain.com/foo#bar' 131 | gearbox.url.removeHashFromUrl(url) // => 'http://domain.com/foo' 132 | ``` 133 | 134 | *** 135 | 136 | ### `.getHashFromUrl(url)`   137 | 138 | 获取 URL 中的 hash 部分。获取结果包含开头的 `#` 字符。 139 | 140 | 如果需要得到当前页面 URL 的 hash 部分,直接使用 `location.hash` 即可。 141 | 142 | #### 参数 143 | 144 | * `url` -- 字符串。待处理的 URL,可以是完整的 URL,也可以是相对路径。若传入其它类型的数值,将被转换为字符串。 145 | 146 | #### 返回值 147 | 148 | 字符串。若传入的 URL 不包含 hash 部分则返回空字符串;若未传入参数则返回空字符串。 149 | 150 | #### 示例 151 | 152 | ```js 153 | var url = 'http://domain.com/foo#bar' 154 | gearbox.url.getHashFromUrl(url) // => '#bar' 155 | ``` 156 | 157 | 158 | ## 别名   159 | 160 | ### `.isHash()`   161 | 162 | `gearbox.str.isHash()` 的别名。 163 | 164 | ### `.stripHash()`   165 | 166 | `gearbox.str.stripHash()` 的别名。 167 | 168 | 169 | *** 170 | *** 171 | 172 | ## 暂未实现的接口 :warning: 173 | 174 | ### `.parse(url)`   175 | 176 | > **别名**: `.parseUrl()` 177 | 178 | > 此接口的行为与 Node.js 内置的 [`url` 模块](https://iojs.org/api/url.html) 的 `.parse()` 接口的功能保持基本一致,但仍然有细微差异,详见 “注意事项” 部分。 179 | 180 | 解析 URL 的各个要素,解析结果以对象的方式输出。举例来说,当传入以下 URL 时: 181 | 182 | ``` 183 | 'http://user:pass@domain.com:8080/path/file?query=string#hash' 184 | ``` 185 | 186 | 解析结果中各个 key 的含义和值如下: 187 | 188 | * **`href`** -- 完整 URL 值。协议名和域名会被转换为全小写。 189 | 值:(同传入的 URL) 190 | 191 | * **`protocol`** -- 协议。 192 | 值:`'http:'` 193 | 194 | * **`slashes`** -- 布尔值,此协议是否需要双斜杠。 195 | 值:`true` 196 | 197 | * **`host`** -- 主机(含端口号)。域名会被转换为全小写。 198 | 值:`'domain.com:8080'` 199 | 200 | * **`auth`** -- 身份验证信息。 201 | 值:`'user:pass'` 202 | 203 | * **`hostname`** -- 主机名(不含端口号)。域名会被转换为全小写。 204 | 值:`'domain.com'` 205 | 206 | * **`port`** -- 端口号。它不会被转换为数字。 207 | 值:`'8080'` 208 | 209 | * **`pathname`** -- 路径(含文件名)。 210 | 值:`'/path/file'` 211 | 212 | * **`search`** -- query string 部分(含开头的 `?` 字符)。参数名和值不会被解码。 213 | 值:`'?query=string'` 214 | 215 | * **`path`** -- 路径加上 query string 部分。参数名和值不会被解码。 216 | 值:`'/path/file?query=string'` 217 | 218 | * **`query`** -- query string 部分(不含开头的 `?` 字符)。 219 | 值:`'query=string'` 220 | 221 | * **`hash`** -- hash 部分(含开头的 `#` 字符)。 222 | 值:`'query=string'` 223 | 224 | 可以看出它们涵盖了 `location` 对象的各个 key,且含义相同。 225 | 226 | #### 参数 227 | 228 | * `url` -- 字符串。需要解析的 URL。 229 | 230 | #### 返回值 231 | 232 | 对象。整个 URL 的解析结果,URL 的各个要素部分以名值对的方式保存。 233 | 234 | 当参数不合法时,返回空对象(`{}`)。 235 | 236 | #### 注意事项 237 | 238 | * 此接口的实现依赖 DOM,无法用于 Worker。 239 | * 若传入的 URL 不完整,则视为相对路径,以当前页面为基准进行解析。这也意味着 `//foo/bar` 将会被视为 “`foo` 主机下的 `/bar` 路径”。 240 | * 当 URL 中的某些部分不存在时,解析结果中对应的 key 也将不存在。 241 | 242 | *** 243 | 244 | ### `.format(parts)`   245 | 246 | > **别名**: `.composeUrl()` 247 | 248 | > 此接口的行为与 Node.js 内置的 `url` 模块的 `.format()` 接口的功能保持基本一致。 249 | 250 | 根据提供的 URL 各个要素,构造完整的 URL。URL 各个组成部分的名称及含义同 `.parse()` 接口的描述。 251 | 252 | #### 参数 253 | 254 | * `parts` -- 对象。URL 的各个要素的名值对。 255 | 256 | #### 返回值 257 | 258 | 字符串。构造出的完整 URL。 259 | 260 | 当参数不合法时,返回空字符串。 261 | 262 | #### 示例 263 | 264 | ```js 265 | var urlParts = { 266 | protocol: 'http:', 267 | host: 'domain.com', 268 | pathname: '/foo/bar' 269 | } 270 | gearbox.url.format(urlParts) // => 'http://domain.com/foo/bar' 271 | ``` 272 | 273 | #### 注意事项 274 | 275 | * 此接口的实现依赖 DOM,无法用于 Worker。 276 | * `href` 字段将被忽略。 277 | * `path` 字段将被忽略。 278 | * `protocol` 字段如果没有用冒号结尾,则会自动补上。对于这些协议(`http`、`https`、`ftp`、`gopher`、`file`),还会自动补上双斜杠。 279 | * 可以额外使用 `slashes` 字段来强制自动补上双斜杠。 280 | * 如果提供了 `auth` 字段,则会被使用。 281 | * 当存在 `host` 时,将忽略 `hostname` 和 `port` 字段。 282 | * 当存在 `search` 时,将忽略 `query` 字段。 283 | * `pathname` 应以 `/` 开头;不以 `/` 开头则会自动补上。 284 | * `search` 应以 `?` 开头;不以 `?` 开头则会自动补上。 285 | * `hash` 应以 `#` 开头;不以 `#` 开头则会自动补上。 286 | * `host`、`hostname`、`pathname` 中的 `?` 和 `#` 字符会被编码;`search` 中的 `#` 字符会被编码。 287 | * `port` 值在经过 `parseInt(port, 10)` 转换后必须为 `0` 或正整数,否则均视为 `0`。 288 | * URL 的各个要素并不都是必选的。各字段省略时的行为如下(注意,字段值为空字符串并不表示省略): 289 | * `protocol` -- 若省略则取当前页面的 `location.protocol`。 290 | * `auth` -- 若省略则不输出。 291 | * `host` -- 若省略则取 `hostname` 和 `port`。 292 | * `hostname` -- 若省略则取当前页面的 `location.hostname`。 293 | * `port` -- 若省略则不输出。 294 | * `pathname` -- 若省略则取根目录(`/`)。 295 | * `search` -- 若省略则取 `query`。 296 | * `query` -- 若省略则不输出。 297 | * `hash` -- 若省略则不输出。 298 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const gulp = require('gulp') 5 | const gulpfiles = require('gulpfiles') 6 | const rename = require('gulp-rename') 7 | const wrap = require('gulp-wrap') 8 | const replace = require('gulp-replace') 9 | 10 | const streamToPromise = require('gulp-stream-to-promise') 11 | 12 | const myPath = { 13 | temp: './.tmp/', 14 | src: './src/', 15 | dest: './dist/', 16 | } 17 | const FILENAME = 'gearbox' 18 | // const NS = 'gearbox' 19 | 20 | const modules = { 21 | action: './bower_components/action/src/action.js', 22 | template: './bower_components/underscore-template/src/underscore-template.js', 23 | } 24 | 25 | const scripts = { 26 | [FILENAME + '.js']: [ 27 | './src/core.js', 28 | './src/str.js', 29 | './src/root.js', 30 | './src/ua.js', 31 | './src/url.js', 32 | './src/dom.js', 33 | ] 34 | } 35 | // combine external modules 36 | Object.keys(modules).forEach(function (key) { 37 | scripts[FILENAME + '.js'].push(path.join(myPath.temp, key + '.js')) 38 | }) 39 | 40 | gulp.task('clean', gulpfiles.del({ 41 | glob: path.join(myPath.dest, '*.*'), 42 | })) 43 | 44 | gulp.task('clean-temp', gulpfiles.del({ 45 | glob: path.join(myPath.temp, '*.*'), 46 | })) 47 | 48 | gulp.task('prepare-module', function () { 49 | let tasks = [] 50 | Object.keys(modules).forEach(function (key) { 51 | const src = modules[key] 52 | let stream = gulp.src(src) 53 | .pipe(wrap('*/\n<%= contents %>\n/*')) 54 | .pipe(wrap({src: path.join(myPath.src, '_wrapper/mod-' + key + '.js')})) 55 | .pipe(rename(key + '.js')) 56 | .pipe(gulp.dest(myPath.temp)) 57 | tasks.push(streamToPromise(stream)) 58 | }) 59 | return Promise.all(tasks) 60 | }) 61 | 62 | gulp.task('js', gulpfiles.concat({ 63 | rules: scripts, 64 | dest: myPath.dest, 65 | config: { 66 | pipes: [ 67 | { 68 | plugin: 'wrap', 69 | config: '*/\n<%= contents %>\n/*', 70 | }, 71 | { 72 | plugin: 'wrap', 73 | config: {src: path.join(myPath.src, '_wrapper/dist-trad.js')}, 74 | }, 75 | { 76 | plugin: 'replace', 77 | config: [/\/\*\* DEBUG_INFO_START \*\*\//g, '/*'], 78 | }, 79 | { 80 | plugin: 'replace', 81 | config: [/\/\*\* DEBUG_INFO_END \*\*\//g, '*/'], 82 | }, 83 | { 84 | plugin: 'uglify', 85 | rename: FILENAME + '.min.js', 86 | config: { 87 | preserveComments: 'some', 88 | }, 89 | } 90 | ] 91 | }, 92 | })) 93 | 94 | gulp.task('dist', gulp.series([ 95 | 'clean', 96 | 'clean-temp', 97 | 'prepare-module', 98 | 'js', 99 | ])) 100 | gulp.task('default', gulp.series('dist')) 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmui-gearbox", 3 | "version": "0.7.1", 4 | "homepage": "https://github.com/CMUI/gearbox", 5 | "author": "cssmagic ", 6 | "description": "Lightweight JavaScript utilities for web development.", 7 | "main": "dist/gearbox.js", 8 | "keywords": [ 9 | "util", 10 | "utilities", 11 | "web" 12 | ], 13 | "license": "MIT", 14 | "scripts": { 15 | "dist": "gulp", 16 | "prepublish": "npm run dist", 17 | "test": "echo \"See README.md to run tests in browsers.\" && exit 1" 18 | }, 19 | "dependencies": { 20 | "underscore": "^1.6", 21 | "zepto.js": "^1.1" 22 | }, 23 | "devDependencies": { 24 | "gulp": "github:gulpjs/gulp#4.0", 25 | "gulp-stream-to-promise": "^0.1.0", 26 | "gulpfiles": "^0.3.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/_wrapper/dist-trad.js: -------------------------------------------------------------------------------- 1 | /*! Gearbox | MIT License | https://github.com/CMUI/gearbox */ 2 | !function (window, undefined) { 3 | // check conflict 4 | if (window.gearbox) return false 5 | 6 | // shortcut 7 | var _ = window._ 8 | var $ = window.Zepto || window.jQuery || window.$ 9 | 10 | // check dependency 11 | if (!_ || !$) return false 12 | 13 | //////////////////// START: source code //////////////////// 14 | /* <%= contents %> */ 15 | //////////////////// END: source code //////////////////// 16 | 17 | window.gearbox = gearbox 18 | 19 | }(window) 20 | -------------------------------------------------------------------------------- /src/_wrapper/mod-action.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// action //////////////////// 3 | // include and wrap external module: Action 4 | 5 | void function (window, gearbox) { 6 | 'use strict' 7 | 8 | /* ================= START: source code ================= */ 9 | /* <%= contents %> */ 10 | /* ================= END: source code ================= */ 11 | 12 | gearbox.__defineModule('action', action) 13 | 14 | }(window, gearbox) 15 | -------------------------------------------------------------------------------- /src/_wrapper/mod-template.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// template //////////////////// 3 | // include and wrap external module: Underscore-template 4 | 5 | void function (window, gearbox) { 6 | 'use strict' 7 | 8 | /* ================= START: source code ================= */ 9 | /* <%= contents %> */ 10 | /* ================= END: source code ================= */ 11 | 12 | gearbox.__defineModule('template', template) 13 | 14 | }(window, gearbox) 15 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// core //////////////////// 3 | // namespace 4 | var gearbox = {} 5 | 6 | // shortcut 7 | var document = window.document 8 | 9 | void function (window, gearbox) { 10 | 'use strict' 11 | 12 | gearbox.__defineModule = function (moduleName, apiSet) { 13 | if (!moduleName || !_.isString(moduleName) || !apiSet || !_.isObject(apiSet)) return 14 | 15 | if (moduleName === 'root') { 16 | // {apiSet}.xxx => gearbox.xxx 17 | _.each(apiSet, function (value, key) { 18 | gearbox[key] = value 19 | }) 20 | } else { 21 | // {apiSet}.xxx => gearbox.{key}.xxx 22 | gearbox[moduleName] = apiSet 23 | } 24 | 25 | } 26 | 27 | }(window, gearbox) 28 | -------------------------------------------------------------------------------- /src/dom.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// dom //////////////////// 3 | void function (window, gearbox) { 4 | 'use strict' 5 | 6 | // namespace 7 | var dom = {} 8 | 9 | // shortcuts for frequently-used elements 10 | dom.$win = $(window) 11 | dom.$root = $(document.documentElement) 12 | dom.$body = $(document.body) 13 | 14 | // methods 15 | dom.is$Element = function (o) { 16 | if (!o || !_.isObject(o)) return false 17 | var result = false 18 | if ('__proto__' in o) { 19 | result = o.__proto__ === $.fn 20 | } else { 21 | var Class = ($.zepto && $.zepto.Z) || $ 22 | result = o instanceof Class 23 | } 24 | return result 25 | } 26 | 27 | // exports 28 | gearbox.__defineModule('dom', dom) 29 | 30 | }(window, gearbox) 31 | -------------------------------------------------------------------------------- /src/plugin/migrate.js: -------------------------------------------------------------------------------- 1 | 2 | // Gearbox Migrate Plugin 3 | 4 | //////////////////// shim //////////////////// 5 | // shim deprecated APIs 6 | gearbox.isPlainObject = function () { 7 | /** DEBUG_INFO_START **/ 8 | console.warn('[Gearbox] [Migrate] This API `gearbox.isPlainObject()` is deprecated, use `$.isPlainObject()` instead!') 9 | /** DEBUG_INFO_END **/ 10 | 11 | return $.isPlainObject.apply($, arguments) 12 | } 13 | gearbox.str.include = function () { 14 | /** DEBUG_INFO_START **/ 15 | console.warn('[Gearbox] [Migrate] This API `gearbox.str.include()` is deprecated, use `gearbox.str.includes()` instead!') 16 | /** DEBUG_INFO_END **/ 17 | 18 | return gearbox.str.includes.apply(gearbox.str, arguments) 19 | } 20 | 21 | //////////////////// ns //////////////////// 22 | // clone modules on `gearbox` namespace to `_` 23 | 24 | /** DEBUG_INFO_START **/ 25 | console.log('[Gearbox] [Migrate] Start restoring `_.foo` from `gearbox.foo`.') 26 | /** DEBUG_INFO_END **/ 27 | 28 | _.each(gearbox, function (apiSet, moduleName) { 29 | /** DEBUG_INFO_START **/ 30 | console.log('[Gearbox] [Migrate] Handling this key: `%s`.', moduleName) 31 | /** DEBUG_INFO_END **/ 32 | 33 | // if `_` has no same name key 34 | // `gearbox.xxx` => `_.xxx` 35 | if (!(moduleName in _)) { 36 | // skip private keys 37 | if (!gearbox.str.startsWith(moduleName, '__')) { 38 | _[moduleName] = apiSet 39 | } 40 | } 41 | // if `_` already has same name key 42 | // `gearbox.foo.xxx` => `_.foo.xxx` 43 | else { 44 | /** DEBUG_INFO_START **/ 45 | console.warn('[Gearbox] [Migrate] `_` already has this key: `%s`.', moduleName) 46 | /** DEBUG_INFO_END **/ 47 | if (_.isObject(apiSet)) { 48 | _.extend(_[moduleName], apiSet) 49 | } else { 50 | /** DEBUG_INFO_START **/ 51 | console.error('[Gearbox] [Migrate] Fail to overwrite `_.%s`', moduleName) 52 | /** DEBUG_INFO_END **/ 53 | } 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /src/root.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// root //////////////////// 3 | void function (window, gearbox) { 4 | 'use strict' 5 | 6 | var root = { 7 | $: function (input) { 8 | var result 9 | if (_.isElement(input)) { 10 | result = input.__$__ = input.__$__ || $(input) 11 | } else if (gearbox.dom.is$Element(input)) { 12 | result = input 13 | } else { 14 | result = $(input) 15 | } 16 | return result 17 | }, 18 | } 19 | 20 | gearbox.__defineModule('root', root) 21 | 22 | }(window, gearbox) 23 | -------------------------------------------------------------------------------- /src/str.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// str //////////////////// 3 | void function (window, gearbox) { 4 | 'use strict' 5 | 6 | // namespace 7 | var str = {} 8 | 9 | //////////////////// START: alternative to underscore.string //////////////////// 10 | // this section contains apis same as underscore.string's. 11 | // heavily inspired by [underscore.string](https://github.com/epeli/underscore.string) 12 | // source: https://github.com/epeli/underscore.string/blob/master/lib/underscore.string.js 13 | 14 | // util 15 | var nativeTrim = String.prototype.trim 16 | var nativeTrimRight = String.prototype.trimRight 17 | var nativeTrimLeft = String.prototype.trimLeft 18 | 19 | function makeString(object) { 20 | if (object == null) return '' 21 | return '' + object 22 | } 23 | function toPositive(number) { 24 | return number < 0 ? 0 : (+number || 0) 25 | } 26 | 27 | function defaultToWhiteSpace(characters) { 28 | if (characters == null) 29 | return '\\s' 30 | else if (characters.source) 31 | return characters.source 32 | else 33 | return '[' + str.escapeRegExp(characters) + ']' 34 | } 35 | str.escapeRegExp = function (str) { 36 | if (str == null) return '' 37 | return makeString(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') 38 | } 39 | 40 | // trim 41 | str.trim = function (str, characters) { 42 | if (str == null) return '' 43 | if (!characters && nativeTrim) return nativeTrim.call(str) 44 | characters = defaultToWhiteSpace(characters) 45 | return makeString(str).replace(new RegExp('^' + characters + '+|' + characters + '+$', 'g'), '') 46 | } 47 | str.ltrim = function (str, characters) { 48 | if (str == null) return '' 49 | if (!characters && nativeTrimLeft) return nativeTrimLeft.call(str) 50 | characters = defaultToWhiteSpace(characters) 51 | return makeString(str).replace(new RegExp('^' + characters + '+'), '') 52 | } 53 | str.rtrim = function (str, characters) { 54 | if (str == null) return '' 55 | if (!characters && nativeTrimRight) return nativeTrimRight.call(str) 56 | characters = defaultToWhiteSpace(characters) 57 | return makeString(str).replace(new RegExp(characters + '+$'), '') 58 | } 59 | 60 | // sub-string 61 | str.includes = function (str, needle, position) { 62 | if (needle === '') return true 63 | position = position == null ? 0 : Math.min(toPositive(position), str.length) 64 | return makeString(str).slice(position).indexOf(needle) !== -1 65 | } 66 | str.startsWith = function (str, starts, position) { 67 | str = makeString(str) 68 | starts = '' + starts 69 | position = position == null ? 0 : Math.min(toPositive(position), str.length) 70 | return str.lastIndexOf(starts, position) === position 71 | } 72 | str.endsWith = function (str, ends, position) { 73 | str = makeString(str) 74 | ends = '' + ends 75 | if (typeof position == 'undefined') { 76 | position = str.length - ends.length 77 | } else { 78 | position = Math.min(toPositive(position), str.length) - ends.length 79 | } 80 | return position >= 0 && str.indexOf(ends, position) === position 81 | } 82 | 83 | // aliases 84 | str.contains = str.includes 85 | // @DEPRECATED 86 | str.include = str.includes 87 | 88 | //////////////////// END: alternative to underscore.string //////////////////// 89 | 90 | 91 | // shortcuts for frequently-used characters 92 | str.CNY = str.RMB = '\xA5' // CNY(RMB) symbol 93 | str.FULL_WIDTH_CNY = str.FULL_WIDTH_RMB = '\uffe5' // full-width version of CNY(RMB) symbol 94 | 95 | // shortcuts for frequently-used regexp 96 | str.RE_EMAIL = /^(?:[a-z\d]+[_\-\+\.]?)*[a-z\d]+@(?:([a-z\d]+\-?)*[a-z\d]+\.)+([a-z]{2,})+$/i 97 | str.RE_MOBILE = /^1[3456789]\d{9}$/ 98 | str.RE_POSTCODE = /^\d{6}$/ 99 | 100 | // hash 101 | str.isHash = function (str) { 102 | str = gearbox.str.trim(str) 103 | return gearbox.str.startsWith(str, '#') 104 | } 105 | str.stripHash = function (str) { 106 | str = gearbox.str.trim(str) 107 | str = gearbox.str.ltrim(str, '#') 108 | if (gearbox.str.startsWith(str, '!')) str = str.slice(1) 109 | return str 110 | } 111 | 112 | // more `toNumber` methods 113 | str.toFloat = function (str) {return parseFloat(str)} 114 | str.toInt = function (str) { 115 | var n = parseFloat(str) 116 | return n < 0 ? Math.ceil(n) : Math.floor(n) 117 | } 118 | str.toFixed = function (str, i) {return gearbox.str.toFloat(gearbox.str.toFloat(str).toFixed(i || 0))} 119 | 120 | // exports 121 | gearbox.__defineModule('str', str) 122 | 123 | }(window, gearbox) 124 | -------------------------------------------------------------------------------- /src/ua.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// ua //////////////////// 3 | void function (window, gearbox) { 4 | 'use strict' 5 | 6 | // namespace 7 | var ua = {} 8 | 9 | // detect by feature 10 | // we want it to work with chrome's touch device simulator, 11 | // so we don't use `document.createTouch` to detect. 12 | ua.isTouchDevice = ('ontouchstart' in window) && ('ontouchmove' in window) && 13 | ('ontouchend' in window) 14 | 15 | // detect by ua string 16 | ua.str = navigator.userAgent 17 | 18 | function _detect(ua) { 19 | var s = ua.str.toLowerCase() 20 | var _includes = gearbox.str.includes 21 | 22 | ua.isSafari = /\bapple\b/i.test(navigator.vendor) && /\bsafari\b/i.test(s) 23 | ua.isChrome = _includes(s, 'chrome') || 24 | _includes(s, 'crios') // both desktop and mobile version 25 | 26 | // platform version and device 27 | ua.osVersion = '' 28 | ua.isIOS = /\(i(?:phone|pod|pad)\b/.test(s) || /\bios \d+\./.test(s) 29 | if (ua.isIOS) { 30 | ua.isIPad = /\(ipad\b/.test(s) 31 | ua.isIPod = /\(ipod\b/.test(s) 32 | ua.isIPhone = /\(iphone\b/.test(s) 33 | ua.osVersion = (/[\/; i]os[\/: _](\d+(?:[\._]\d+)?)[\._; ]/.exec(s) || [0, ''])[1] 34 | .replace('_', '.') 35 | } else { 36 | var _includeAndroid = _includes(s, 'android') 37 | var _includeAdr = /\badr\b/.test(s) && /\blinux;\s*u;/.test(s) 38 | var _isJUC = /juc\s*\(linux;\s*u;\s*\d+\.\d+/.test(s) 39 | ua.isAndroid = _includeAndroid || _includeAdr || _isJUC 40 | if (_includeAdr || _isJUC) { 41 | ua.osVersion = ( 42 | /\badr[\/: ]?(\d+\.\d)\d*\b/.exec(s) || 43 | /\blinux;\s*u;\s*(\d+\.\d)\d*\b/.exec(s) || [0, ''] 44 | )[1] 45 | } else { 46 | ua.osVersion = (/\bandroid(?:_os)?[\/: ]?(\d+\.\d)\d*\b/.exec(s) || [0, ''])[1] 47 | } 48 | } 49 | // fix - Windows Phone might pretend to be iOS or Android 50 | if (_includes(s, 'windows phone')) { 51 | ua.isIOS = ua.isAndroid = false 52 | ua.osVersion = '' 53 | } 54 | if (ua.osVersion && !_includes(ua.osVersion, '.')) ua.osVersion += '.0' 55 | 56 | // summery 57 | ua.isMobileDevice = !!(ua.isIOS || ua.isAndroid) 58 | 59 | // get browser info 60 | var browser = '' 61 | if (_includes(s, 'micromessenger')) { 62 | browser = 'wechat' 63 | } else if (_includes(s, 'ucbrowser') || _includes(s, 'ucweb') || _includes(s, ' uc applewebkit')) { 64 | browser = 'uc' 65 | } else if (_includes(s, 'baiduhd') || _includes(s, 'baiduboxapp')) { 66 | browser = 'baidu-app' 67 | } else if (_includes(s, 'baidubrowser')) { 68 | browser = 'baidu-browser' 69 | } else if (_includes(s, 'mqqbrowser')) { 70 | browser = 'm-qq-browser' 71 | } else if (_includes(s, 'miuibrowser')) { 72 | browser = 'miui' 73 | } else if (_includes(s, '_weibo_') || _includes(s, ' weibo ')) { 74 | browser = 'weibo' 75 | } else if (_includes(s, 'firefox')) { 76 | browser = 'firefox' 77 | } else if (_includes(s, 'opera')) { 78 | browser = 'opera' 79 | } else if (_includes(s, ' edge/')) { 80 | browser = 'edge' 81 | } else if (_includes(s, 'iemobile')) { 82 | browser = 'ie-mobile' 83 | } 84 | // these two must be the last 85 | else if (ua.isChrome) { 86 | browser = 'chrome' 87 | if (ua.isAndroid && /\bwv\b/.test(s)) browser = 'chrome-webview' 88 | } else if (ua.isSafari) { 89 | browser = 'safari' 90 | } 91 | 92 | // fix - some browsers might be detected as Chrome or Safari 93 | if (browser !== 'chrome') ua.isChrome = false 94 | if (browser !== 'safari') ua.isSafari = false 95 | 96 | // get engine info 97 | var engine = '' 98 | var engineVersion = '' 99 | var testChrome = /chrome[^\d]*([\.\d]*)[ ;\/]/.exec(s) 100 | if (testChrome) { 101 | engine = 'chrome' 102 | engineVersion = _trimVersion(testChrome[1]) 103 | } else { 104 | var testWebKit = /webkit[^\d]*([\.\d]*)\+*[ ;\/]/.exec(s) 105 | if (testWebKit) { 106 | engine = 'webkit' 107 | engineVersion = _trimVersion(testWebKit[1]) 108 | } 109 | } 110 | if (!engine) { 111 | if (_includes(s, 'webkit')) { 112 | engine = 'webkit' 113 | } else if (ua.isIOS) { 114 | engine = 'webkit' 115 | } else if (ua.isAndroid && browser === 'm-qq-browser') { 116 | engine = 'webkit' 117 | } 118 | if (browser === 'firefox' && !ua.isIOS) engine = 'gecko' 119 | if (browser === 'opera' && !ua.isIOS && _includes(s, 'presto')) engine = 'presto' 120 | } 121 | // fix Windows Phone, IE Mobile and Edge 122 | if (browser === 'edge') { 123 | engine = 'edge' 124 | engineVersion = '' 125 | } else if (browser === 'ie-mobile') { 126 | engine = engineVersion = '' 127 | } 128 | 129 | // output 130 | ua.browser = browser 131 | ua.engine = engine 132 | ua.engineVersion = engineVersion 133 | return ua 134 | } 135 | 136 | // TODO: detect size and features of screen 137 | /* 138 | function __detectScreen(ua) {} 139 | */ 140 | 141 | // util 142 | // TODO: implement a stricter API: `gearbox.str.formatVersion(ver, length)`, e.g. ('1.2', 3) -> '1.2.0' 143 | function _trimVersion(ver, length) { 144 | var temp = ver.split('.') 145 | temp.length = length || 2 146 | return _.compact(temp).join('.') 147 | } 148 | 149 | // init 150 | _detect(ua) 151 | 152 | /** DEBUG_INFO_START **/ 153 | // exports for unit test 154 | ua.__detect = _detect 155 | ua.__trimVersion = _trimVersion 156 | /** DEBUG_INFO_END **/ 157 | 158 | // exports 159 | gearbox.__defineModule('ua', ua) 160 | 161 | }(window, gearbox) 162 | -------------------------------------------------------------------------------- /src/url.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////// url //////////////////// 3 | void function (window, gearbox) { 4 | 'use strict' 5 | 6 | // namespace 7 | var url = {} 8 | 9 | // shortcut 10 | var loc = window.location 11 | 12 | // url param processing 13 | url.parseQuery = function (query) { 14 | var data = {} 15 | if (query && _.isString(query)) { 16 | var pairs = query.split('&'), pair, name, value 17 | _.each(pairs, function(n) { 18 | pair = n.split('=') 19 | name = pair[0] 20 | value = pair[1] || '' 21 | if (name) { 22 | data[decodeURIComponent(name).toLowerCase()] = decodeURIComponent(value) 23 | } 24 | }) 25 | } 26 | return data 27 | } 28 | 29 | var _query, _cacheParam = null 30 | function _getQuery() { 31 | return loc.search.slice(1) 32 | } 33 | url.getParam = function (s) { 34 | if (!s || !_.isString(s)) return false 35 | if (typeof _query === 'undefined') { // first run 36 | _query = _getQuery() 37 | } else { 38 | var currentQuery = _getQuery() 39 | if (currentQuery !== _query) { 40 | _cacheParam = null // clear cache to enforce re-parse 41 | _query = currentQuery 42 | } 43 | } 44 | if (!_cacheParam) { 45 | _cacheParam = this.parseQuery(_query) 46 | } 47 | return _cacheParam[s.toLowerCase()] 48 | } 49 | 50 | url.appendParam = function (url, param) { 51 | var s = '' 52 | url = _.isString(url) ? url : '' 53 | url = gearbox.url.removeHashFromUrl(url) 54 | if ($.isPlainObject(param)) { 55 | param = $.param(param) 56 | } else if (_.isString(param)) { 57 | // fix param string 58 | if (gearbox.str.startsWith(param, '&') || gearbox.str.startsWith(param, '?')) { 59 | param = param.slice(1) 60 | } 61 | } else { 62 | param = null 63 | } 64 | // append 65 | s = param ? url + (gearbox.str.includes(url, '?') ? '&' : '?') + param : s 66 | return s || false 67 | } 68 | 69 | // hash processing 70 | url.removeHashFromUrl = function (s) { 71 | return arguments.length ? String(s).split('#')[0] : '' 72 | } 73 | url.getHashFromUrl = function (s) { 74 | var a = String(s).split('#') 75 | a[0] = '' 76 | return a.join('#') 77 | } 78 | 79 | // aliases 80 | url.isHash = gearbox.str.isHash 81 | url.stripHash = gearbox.str.stripHash 82 | 83 | // exports 84 | gearbox.__defineModule('url', url) 85 | 86 | }(window, gearbox) 87 | -------------------------------------------------------------------------------- /test/_sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing Sandbox - UT - Gearbox 6 | 7 | 8 | 9 |

Testing Sandbox

10 | 11 | 12 | 13 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/test-action.js: -------------------------------------------------------------------------------- 1 | describe('Action', function () { 2 | var testKey = '' 3 | var actionSet = {} 4 | beforeEach(function () { 5 | testKey = '' 6 | actionSet = {} 7 | }) 8 | 9 | // for source code testing 10 | before(function () { 11 | if (!gearbox.action) gearbox.action = action 12 | }) 13 | 14 | describe('APIs', function () { 15 | describe('gearbox.action.add()', function () { 16 | it('(this api will be tested in below test cases)', function () { 17 | // 18 | }) 19 | }) 20 | describe('gearbox.action.trigger()', function () { 21 | it('does basic functionality', function () { 22 | var key1 = 'test-foo' 23 | var key2 = 'test-bar' 24 | actionSet = { 25 | foo: function () { 26 | testKey = key1 27 | }, 28 | bar: function () { 29 | testKey = key2 30 | } 31 | } 32 | gearbox.action.add(actionSet) 33 | gearbox.action.trigger('foo') 34 | expect(testKey).to.equal(key1) 35 | gearbox.action.trigger('bar') 36 | expect(testKey).to.equal(key2) 37 | }) 38 | it('calls callback on the specified context', function () { 39 | var context = {} 40 | actionSet = { 41 | foo: function () { 42 | expect(this).to.equal(context) 43 | }, 44 | bar: function () { 45 | expect(this).to.equal(_) 46 | } 47 | } 48 | gearbox.action.add(actionSet) 49 | gearbox.action.trigger('foo', context) 50 | gearbox.action.trigger('bar', _) 51 | }) 52 | }) 53 | }) 54 | describe('DOM binding', function () { 55 | var $wrapper, $link 56 | var actionName, randomKey 57 | before(function () { 58 | $wrapper = $('') 59 | .css({position: 'absolute', top: '-50px'}) 60 | .appendTo('body') 61 | $link = $wrapper.find('a') 62 | }) 63 | after(function () { 64 | $wrapper.remove() 65 | }) 66 | beforeEach(function () { 67 | actionName = new Date().getTime().toString(36) 68 | randomKey = Math.random().toString(36) 69 | }) 70 | it('gets action name from `href`', function (done) { 71 | $link.attr('href', '#' + actionName) 72 | actionSet[actionName] = function () { 73 | testKey = randomKey 74 | } 75 | gearbox.action.add(actionSet) 76 | $link.click() 77 | _.delay(function () { 78 | expect(testKey).to.equal(randomKey) 79 | done() 80 | }, 50) 81 | }) 82 | it('gets action name from `href` - context points to the link', function (done) { 83 | $link.attr('href', '#' + actionName) 84 | actionSet[actionName] = function () { 85 | expect(this).to.equal($link[0]) 86 | done() 87 | } 88 | gearbox.action.add(actionSet) 89 | $link.click() 90 | }) 91 | it('gets action name from `data-action`', function (done) { 92 | $link.attr('href', '#') 93 | $link.attr('data-action', actionName) 94 | actionSet[actionName] = function () { 95 | testKey = randomKey 96 | } 97 | gearbox.action.add(actionSet) 98 | $link.click() 99 | _.delay(function () { 100 | expect(testKey).to.equal(randomKey) 101 | done() 102 | }, 50) 103 | }) 104 | it('gets action name from `data-action` - context points to the link', function (done) { 105 | $link.attr('href', '#') 106 | $link.attr('data-action', actionName) 107 | actionSet[actionName] = function () { 108 | expect(this).to.equal($link[0]) 109 | done() 110 | } 111 | gearbox.action.add(actionSet) 112 | $link.click() 113 | }) 114 | it('accepts `data-action` value as a hash', function (done) { 115 | $link.attr('href', '#') 116 | $link.attr('data-action', '#' + actionName) 117 | actionSet[actionName] = function () { 118 | testKey = randomKey 119 | } 120 | gearbox.action.add(actionSet) 121 | $link.click() 122 | _.delay(function () { 123 | expect(testKey).to.equal(randomKey) 124 | done() 125 | }, 50) 126 | }) 127 | }) 128 | 129 | }) 130 | -------------------------------------------------------------------------------- /test/test-dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UT (Dev) - Gearbox 6 | 7 | 8 | 9 | 10 | 11 |
12 |

UT (Dev) - Gearbox

13 |

View on GitHub

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/test-dist-trad-jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UT (Trad) (jQuery) - Gearbox 6 | 7 | 8 | 9 | 12 | 13 | 14 |
15 |

UT (Trad) (jQuery) - Gearbox

16 |

View on GitHub

17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/test-dist-trad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UT (Trad) - Gearbox 6 | 7 | 8 | 9 | 10 | 11 |
12 |

UT (Trad) - Gearbox

13 |

View on GitHub

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/test-dom.js: -------------------------------------------------------------------------------- 1 | describe('DOM', function () { 2 | describe('Shortcuts', function () { 3 | describe('gearbox.dom.$win', function () { 4 | it('is $collection of `window` object', function () { 5 | expect(gearbox.dom.is$Element(gearbox.dom.$win)).to.equal(true) 6 | expect(gearbox.dom.$win[0]).to.equal(window) 7 | }) 8 | }) 9 | describe('gearbox.dom.$root', function () { 10 | it('is a $collection of `document.documentElement` object', function () { 11 | expect(gearbox.dom.is$Element(gearbox.dom.$root)).to.equal(true) 12 | expect(gearbox.dom.$root[0]).to.equal(document.documentElement) 13 | expect(gearbox.dom.$root[0].tagName.toUpperCase()).to.equal('HTML') 14 | }) 15 | }) 16 | describe('gearbox.dom.$body', function () { 17 | it('is a $collection of `document.body` object', function () { 18 | expect(gearbox.dom.is$Element(gearbox.dom.$body)).to.equal(true) 19 | expect(gearbox.dom.$body[0]).to.equal(document.body) 20 | }) 21 | }) 22 | }) 23 | 24 | describe('Methods', function () { 25 | describe('gearbox.dom.is$Element()', function () { 26 | it('checks if it\'s $collection', function () { 27 | var arg 28 | arg = $() 29 | expect(gearbox.dom.is$Element(arg)).to.equal(true) 30 | arg = $(window) 31 | expect(gearbox.dom.is$Element(arg)).to.equal(true) 32 | }) 33 | it('returns `false` if bad type of param', function () { 34 | var arg 35 | arg = undefined 36 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 37 | arg = null 38 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 39 | arg = 0 40 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 41 | arg = true 42 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 43 | arg = {} 44 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 45 | arg = [] 46 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 47 | arg = document.documentElement 48 | expect(gearbox.dom.is$Element(arg)).to.equal(false) 49 | }) 50 | }) 51 | }) 52 | 53 | }) 54 | 55 | -------------------------------------------------------------------------------- /test/test-root.js: -------------------------------------------------------------------------------- 1 | describe('(Root)', function () { 2 | describe('gearbox.$()', function () { 3 | it('does basic functionality same as $()', function () { 4 | var elem = document.getElementById('mocha') 5 | var obj = gearbox.$(elem) 6 | expect(obj).to.eql($(elem)) 7 | }) 8 | it('returns directly if already $collection', function () { 9 | var obj = $('#mocha') 10 | expect(gearbox.$(obj)).to.equal(obj) 11 | }) 12 | it('doesn\'t generate multiple $collections for same dom element', function () { 13 | var obj = document.getElementById('mocha') 14 | var $a = gearbox.$(obj) 15 | var $b = gearbox.$(obj) 16 | expect($a).to.equal($b) 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/test-str.js: -------------------------------------------------------------------------------- 1 | describe('String', function () { 2 | // heavily inspired by [underscore.string](https://github.com/epeli/underscore.string) 3 | describe('Alternative to Underscore.string', function () { 4 | // ref: https://github.com/epeli/underscore.string/blob/master/test/strings.js 5 | describe('gearbox.str.trim()', function () { 6 | it('does basic functionality', function () { 7 | expect(gearbox.str.trim(123)).to.equal('123') 8 | expect(gearbox.str.trim(' foo')).to.equal('foo') 9 | expect(gearbox.str.trim('foo ')).to.equal('foo') 10 | expect(gearbox.str.trim(' foo ')).to.equal('foo') 11 | expect(gearbox.str.trim(' foo ')).to.equal('foo') 12 | expect(gearbox.str.trim(' foo ', ' ')).to.equal('foo') 13 | expect(gearbox.str.trim('\t foo \t ', /\s/)).to.equal('foo') 14 | 15 | expect(gearbox.str.trim('ffoo', 'f')).to.equal('oo') 16 | expect(gearbox.str.trim('ooff', 'f')).to.equal('oo') 17 | expect(gearbox.str.trim('ffooff', 'f')).to.equal('oo') 18 | 19 | expect(gearbox.str.trim('_-foobar-_', '_-')).to.equal('foobar') 20 | 21 | expect(gearbox.str.trim('http://foo/', '/')).to.equal('http://foo') 22 | expect(gearbox.str.trim('c:\\', '\\')).to.equal('c:') 23 | 24 | expect(gearbox.str.trim(123)).to.equal('123') 25 | expect(gearbox.str.trim(123, 3)).to.equal('12') 26 | expect(gearbox.str.trim('')).to.equal('') 27 | expect(gearbox.str.trim(null)).to.equal('') 28 | expect(gearbox.str.trim(undefined)).to.equal('') 29 | }) 30 | it('doesn\'t remove inner spaces', function () { 31 | expect(gearbox.str.trim(' foo bar ')).to.equal('foo bar') 32 | }) 33 | }) 34 | describe('gearbox.str.ltrim()', function () { 35 | it('does basic functionality', function () { 36 | expect(gearbox.str.ltrim(' foo')).to.equal('foo') 37 | expect(gearbox.str.ltrim(' foo')).to.equal('foo') 38 | expect(gearbox.str.ltrim('foo ')).to.equal('foo ') 39 | expect(gearbox.str.ltrim(' foo ')).to.equal('foo ') 40 | expect(gearbox.str.ltrim('')).to.equal('') 41 | expect(gearbox.str.ltrim(null)).to.equal('') 42 | expect(gearbox.str.ltrim(undefined)).to.equal('') 43 | 44 | expect(gearbox.str.ltrim('ffoo', 'f'), 'oo') 45 | expect(gearbox.str.ltrim('ooff', 'f'), 'ooff') 46 | expect(gearbox.str.ltrim('ffooff', 'f'), 'ooff') 47 | 48 | expect(gearbox.str.ltrim('_-foobar-_', '_-'), 'foobar-_') 49 | 50 | expect(gearbox.str.ltrim(123, 1), '23') 51 | }) 52 | it('doesn\'t remove inner spaces', function () { 53 | expect(gearbox.str.ltrim(' foo bar ')).to.equal('foo bar ') 54 | }) 55 | }) 56 | describe('gearbox.str.rtrim()', function () { 57 | it('does basic functionality', function () { 58 | expect(gearbox.str.rtrim('http://foo/', '/'), 'http://foo') 59 | expect(gearbox.str.rtrim(' foo')).to.equal(' foo') 60 | expect(gearbox.str.rtrim('foo ')).to.equal('foo') 61 | expect(gearbox.str.rtrim('foo ')).to.equal('foo') 62 | expect(gearbox.str.rtrim('foo bar ')).to.equal('foo bar') 63 | expect(gearbox.str.rtrim(' foo ')).to.equal(' foo') 64 | 65 | expect(gearbox.str.rtrim('ffoo', 'f'), 'ffoo') 66 | expect(gearbox.str.rtrim('ooff', 'f'), 'oo') 67 | expect(gearbox.str.rtrim('ffooff', 'f'), 'ffoo') 68 | 69 | expect(gearbox.str.rtrim('_-foobar-_', '_-'), '_-foobar') 70 | 71 | expect(gearbox.str.rtrim(123, 3), '12') 72 | expect(gearbox.str.rtrim('')).to.equal('') 73 | expect(gearbox.str.rtrim(null)).to.equal('') 74 | }) 75 | it('doesn\'t remove inner spaces', function () { 76 | expect(gearbox.str.rtrim(' foo bar ')).to.equal(' foo bar') 77 | }) 78 | }) 79 | 80 | describe('gearbox.str.includes()', function () { 81 | it('does basic functionality', function () { 82 | expect(gearbox.str.includes('foobar', 'bar')).to.equal(true) 83 | expect(!gearbox.str.includes('foobar', 'buzz')).to.equal(true) 84 | expect(gearbox.str.includes(12345, 34)).to.equal(true) 85 | expect(!gearbox.str.includes(12345, 6)).to.equal(true) 86 | expect(!gearbox.str.includes('', 34)).to.equal(true) 87 | expect(!gearbox.str.includes(null, 34)).to.equal(true) 88 | expect(gearbox.str.includes(null, '')).to.equal(true) 89 | }) 90 | it('supports additional param - position', function () { 91 | expect(gearbox.str.includes('foobar', 'bar', 1)).to.equal(true) 92 | expect(gearbox.str.includes('foobar', 'bar', 2)).to.equal(true) 93 | expect(gearbox.str.includes('foobar', 'bar', 3)).to.equal(true) 94 | expect(gearbox.str.includes('foobar', 'bar', 4)).to.equal(false) 95 | }) 96 | }) 97 | describe('gearbox.str.startsWith()', function () { 98 | it('does basic functionality', function () { 99 | expect(gearbox.str.startsWith('foobar', 'foo')).to.equal(true) 100 | expect(!gearbox.str.startsWith('oobar', 'foo')).to.equal(true) 101 | expect(gearbox.str.startsWith('oobar', 'o')).to.equal(true) 102 | expect(gearbox.str.startsWith(12345, 123)).to.equal(true) 103 | expect(!gearbox.str.startsWith(2345, 123)).to.equal(true) 104 | expect(gearbox.str.startsWith('', '')).to.equal(true) 105 | expect(gearbox.str.startsWith(null, '')).to.equal(true) 106 | expect(!gearbox.str.startsWith(null, 'foo')).to.equal(true) 107 | 108 | expect(gearbox.str.startsWith('-foobar', 'foo', 1)).to.equal(true) 109 | expect(gearbox.str.startsWith('foobar', 'foo', 0)).to.equal(true) 110 | expect(!gearbox.str.startsWith('foobar', 'foo', 1)).to.equal(true) 111 | 112 | expect(gearbox.str.startsWith('Äpfel', 'Ä')).to.equal(true) 113 | 114 | expect(gearbox.str.startsWith('hello', 'hell')).to.equal(true) 115 | expect(gearbox.str.startsWith('HELLO', 'HELL')).to.equal(true) 116 | expect(gearbox.str.startsWith('HELLO', 'hell')).to.equal(false) 117 | expect(gearbox.str.startsWith('HELLO', 'hell')).to.equal(false) 118 | expect(gearbox.str.startsWith('hello', 'hell', 0)).to.equal(true) 119 | expect(gearbox.str.startsWith('HELLO', 'HELL', 0)).to.equal(true) 120 | expect(gearbox.str.startsWith('HELLO', 'hell', 0)).to.equal(false) 121 | expect(gearbox.str.startsWith('HELLO', 'hell', 0)).to.equal(false) 122 | expect(gearbox.str.startsWith('HELLO')).to.equal(false) 123 | expect(gearbox.str.startsWith('undefined')).to.equal(true) 124 | expect(gearbox.str.startsWith('null', null)).to.equal(true) 125 | expect(gearbox.str.startsWith('hello', 'hell', -20)).to.equal(true) 126 | expect(gearbox.str.startsWith('hello', 'hell', 1)).to.equal(false) 127 | expect(gearbox.str.startsWith('hello', 'hell', 2)).to.equal(false) 128 | expect(gearbox.str.startsWith('hello', 'hell', 3)).to.equal(false) 129 | expect(gearbox.str.startsWith('hello', 'hell', 4)).to.equal(false) 130 | expect(gearbox.str.startsWith('hello', 'hell', 5)).to.equal(false) 131 | expect(gearbox.str.startsWith('hello', 'hell', 20)).to.equal(false) 132 | }); 133 | }) 134 | describe('gearbox.str.endsWith()', function () { 135 | it('does basic functionality', function () { 136 | expect(gearbox.str.endsWith('foobar', 'bar')).to.equal(true) 137 | expect(gearbox.str.endsWith('foobarfoobar', 'bar')).to.equal(true) 138 | expect(gearbox.str.endsWith('foo', 'o')).to.equal(true) 139 | expect(gearbox.str.endsWith('foobar', 'bar')).to.equal(true) 140 | expect(gearbox.str.endsWith('00018-0000062.Plone.sdh264.1a7264e6912a91aa4a81b64dc5517df7b8875994.mp4', 'mp4')).to.equal(true) 141 | expect(!gearbox.str.endsWith('fooba', 'bar')).to.equal(true) 142 | expect(gearbox.str.endsWith(12345, 45)).to.equal(true) 143 | expect(!gearbox.str.endsWith(12345, 6)).to.equal(true) 144 | expect(gearbox.str.endsWith('', '')).to.equal(true) 145 | expect(gearbox.str.endsWith(null, '')).to.equal(true) 146 | expect(!gearbox.str.endsWith(null, 'foo')).to.equal(true) 147 | 148 | expect(gearbox.str.endsWith('foobar?', 'bar', 6)).to.equal(true) 149 | expect(gearbox.str.endsWith(12345, 34, 4)).to.equal(true) 150 | expect(!gearbox.str.endsWith(12345, 45, 4)).to.equal(true) 151 | 152 | expect(gearbox.str.endsWith('foobä', 'ä')).to.equal(true) 153 | 154 | expect(gearbox.str.endsWith('vader', 'der')).to.equal(true) 155 | expect(gearbox.str.endsWith('VADER', 'DER')).to.equal(true) 156 | expect(gearbox.str.endsWith('VADER', 'der')).to.equal(false) 157 | expect(gearbox.str.endsWith('VADER', 'DeR')).to.equal(false) 158 | expect(gearbox.str.endsWith('VADER')).to.equal(false) 159 | expect(gearbox.str.endsWith('undefined')).to.equal(true) 160 | expect(gearbox.str.endsWith('null', null)).to.equal(true) 161 | expect(gearbox.str.endsWith('vader', 'der', 5)).to.equal(true) 162 | expect(gearbox.str.endsWith('VADER', 'DER', 5)).to.equal(true) 163 | expect(gearbox.str.endsWith('VADER', 'der', 5)).to.equal(false) 164 | expect(gearbox.str.endsWith('VADER', 'DER', 5)).to.equal(true) 165 | expect(gearbox.str.endsWith('VADER', 'der', 5)).to.equal(false) 166 | expect(gearbox.str.endsWith('vader', 'der', -20)).to.equal(false) 167 | expect(gearbox.str.endsWith('vader', 'der', 0)).to.equal(false) 168 | expect(gearbox.str.endsWith('vader', 'der', 1)).to.equal(false) 169 | expect(gearbox.str.endsWith('vader', 'der', 2)).to.equal(false) 170 | expect(gearbox.str.endsWith('vader', 'der', 3)).to.equal(false) 171 | expect(gearbox.str.endsWith('vader', 'der', 4)).to.equal(false) 172 | }); 173 | }) 174 | 175 | }) 176 | 177 | describe('Shortcuts', function () { 178 | describe('RegExp', function () { 179 | describe('gearbox.str.RE_EMAIL', function () { 180 | var CHAR_AT = '@' // to avoid email crawler and spammer 181 | it('matches email', function () { 182 | var arg 183 | // normal email addr 184 | arg = 'dev' + CHAR_AT + 'cmui.net' 185 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 186 | // domain can be any level 187 | arg = 'dev' + CHAR_AT + 'gearbox.by.cmui.net' 188 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 189 | // future proof for unknown domain suffix 190 | arg = 'dev' + CHAR_AT + 'gearbox.rocks' 191 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 192 | // username can be numbers 193 | arg = '007' + CHAR_AT + 'cmui.net' 194 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 195 | // domain name can be numbers 196 | arg = 'username' + CHAR_AT + '126.com' 197 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 198 | // email can be in upper case 199 | arg = 'DEV' + CHAR_AT + 'CMUI.NET' 200 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(true) 201 | }) 202 | it('recognizes bad value', function () { 203 | var arg 204 | // domain suffix need at least 2 letters 205 | arg = 'a' + CHAR_AT + 'a.a' 206 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(false) 207 | // domain suffix cannot be numbers 208 | arg = '007' + CHAR_AT + '007.007' 209 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(false) 210 | // domain cannot be ip addr 211 | arg = 'a' + CHAR_AT + '192.168.10.10' 212 | expect(gearbox.str.RE_EMAIL.test(arg)).to.equal(false) 213 | }) 214 | }) 215 | describe('gearbox.str.RE_MOBILE', function () { 216 | it('matches mobile number', function () { 217 | var arg 218 | arg = '13000000000' 219 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(true) 220 | arg = '13322558899' 221 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(true) 222 | arg = '15966558877' 223 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(true) 224 | arg = '18055668899' 225 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(true) 226 | arg = 18978963214 227 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(true) 228 | }) 229 | it('recognizes bad value', function () { 230 | var arg 231 | arg = '12000000000' 232 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(false) 233 | arg = '1332255889' 234 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(false) 235 | arg = '133-2255-8899' 236 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(false) 237 | arg = 'foobar' 238 | expect(gearbox.str.RE_MOBILE.test(arg)).to.equal(false) 239 | }) 240 | }) 241 | describe('gearbox.str.RE_POSTCODE', function () { 242 | it('matches postcode', function () { 243 | var arg 244 | arg = '000000' 245 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 246 | arg = '001100' 247 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 248 | arg = '220000' 249 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 250 | arg = '000022' 251 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 252 | arg = '336699' 253 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 254 | arg = 114477 255 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(true) 256 | }) 257 | it('recognizes bad value', function () { 258 | var arg 259 | arg = '11111' 260 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(false) 261 | arg = '5555555' 262 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(false) 263 | arg = '22-55-66' 264 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(false) 265 | arg = 'foobar' 266 | expect(gearbox.str.RE_POSTCODE.test(arg)).to.equal(false) 267 | }) 268 | }) 269 | }) 270 | describe('Special Characters', function () { 271 | describe('gearbox.str.CNY', function () { 272 | it('(dummy test)', function () {}) 273 | it('has an alias `gearbox.str.RMB`', function () { 274 | expect(gearbox.str.CNY).to.equal(gearbox.str.RMB) 275 | }) 276 | }) 277 | describe('gearbox.str.FULL_WIDTH_CNY', function () { 278 | it('(dummy test)', function () {}) 279 | it('has an alias `gearbox.str.FULL_WIDTH_RMB`', function () { 280 | expect(gearbox.str.FULL_WIDTH_CNY).to.equal(gearbox.str.FULL_WIDTH_RMB) 281 | }) 282 | }) 283 | }) 284 | 285 | }) 286 | 287 | describe('Hash Handling', function () { 288 | describe('gearbox.str.isHash()', function () { 289 | it('returns `true` if a string starts with `#`', function () { 290 | var arg 291 | arg = 'foo' 292 | expect(gearbox.str.isHash(arg)).to.equal(false) 293 | arg = '#foo' 294 | expect(gearbox.str.isHash(arg)).to.equal(true) 295 | arg = '###bar' 296 | expect(gearbox.str.isHash(arg)).to.equal(true) 297 | arg = '#!foobar' 298 | expect(gearbox.str.isHash(arg)).to.equal(true) 299 | }) 300 | it('ignores initial spaces', function () { 301 | var arg 302 | arg = ' foo ' 303 | expect(gearbox.str.isHash(arg)).to.equal(false) 304 | arg = ' #foo ' 305 | expect(gearbox.str.isHash(arg)).to.equal(true) 306 | arg = ' ###bar ' 307 | expect(gearbox.str.isHash(arg)).to.equal(true) 308 | arg = ' #!foobar ' 309 | expect(gearbox.str.isHash(arg)).to.equal(true) 310 | }) 311 | it('returns `false` if bad type of param', function () { 312 | var arg 313 | arg = undefined 314 | expect(gearbox.str.isHash(arg)).to.equal(false) 315 | arg = null 316 | expect(gearbox.str.isHash(arg)).to.equal(false) 317 | arg = 0 318 | expect(gearbox.str.isHash(arg)).to.equal(false) 319 | arg = true 320 | expect(gearbox.str.isHash(arg)).to.equal(false) 321 | arg = {} 322 | expect(gearbox.str.isHash(arg)).to.equal(false) 323 | arg = [] 324 | expect(gearbox.str.isHash(arg)).to.equal(false) 325 | arg = function () {} 326 | expect(gearbox.str.isHash(arg)).to.equal(false) 327 | }) 328 | }) 329 | describe('gearbox.str.stripHash()', function () { 330 | it('removes all initial `#` characters', function () { 331 | var arg 332 | arg = '#foo' 333 | expect(gearbox.str.stripHash(arg)).to.equal('foo') 334 | arg = '###bar' 335 | expect(gearbox.str.stripHash(arg)).to.equal('bar') 336 | arg = '###foo#bar' 337 | expect(gearbox.str.stripHash(arg)).to.equal('foo#bar') 338 | }) 339 | it('removes first `!` characters after all initial `#` characters', function () { 340 | var arg 341 | arg = '#!foobar' 342 | expect(gearbox.str.stripHash(arg)).to.equal('foobar') 343 | arg = '#!foo!bar' 344 | expect(gearbox.str.stripHash(arg)).to.equal('foo!bar') 345 | }) 346 | it('returns directly if initial character is not `#`', function () { 347 | var arg 348 | arg = 'foobar' 349 | expect(gearbox.str.stripHash(arg)).to.equal(arg) 350 | }) 351 | it('ignores initial and ending spaces', function () { 352 | var arg 353 | arg = ' foo ' 354 | expect(gearbox.str.stripHash(arg)).to.equal('foo') 355 | arg = ' #foo ' 356 | expect(gearbox.str.stripHash(arg)).to.equal('foo') 357 | arg = ' ###bar ' 358 | expect(gearbox.str.stripHash(arg)).to.equal('bar') 359 | arg = ' #!foobar ' 360 | expect(gearbox.str.stripHash(arg)).to.equal('foobar') 361 | }) 362 | }) 363 | }) 364 | 365 | describe('To Number', function () { 366 | describe('gearbox.str.toFloat()', function () { 367 | it('does basic functionality', function () { 368 | expect(gearbox.str.toFloat('0')).to.equal(0) 369 | expect(gearbox.str.toFloat('1.77')).to.equal(1.77) 370 | expect(gearbox.str.toFloat('-1.77')).to.equal(-1.77) 371 | expect(gearbox.str.toFloat('2.3.6')).to.equal(2.3) 372 | expect(gearbox.str.toFloat('-2.3.6')).to.equal(-2.3) 373 | expect(gearbox.str.toFloat('2e3')).to.equal(2000) 374 | expect(gearbox.str.toFloat('-2e3')).to.equal(-2000) 375 | expect(gearbox.str.toFloat('1.23foo')).to.equal(1.23) 376 | expect(gearbox.str.toFloat('-1.23foo')).to.equal(-1.23) 377 | expect(isNaN(gearbox.str.toFloat('foo123'))).to.equal(true) 378 | }) 379 | }) 380 | describe('gearbox.str.toInt()', function () { 381 | it('does basic functionality', function () { 382 | expect(gearbox.str.toInt('0')).to.equal(0) 383 | expect(gearbox.str.toInt('1.77')).to.equal(1) 384 | expect(gearbox.str.toInt('-1.77')).to.equal(-1) 385 | expect(gearbox.str.toInt('2.3.6')).to.equal(2) 386 | expect(gearbox.str.toInt('-2.3.6')).to.equal(-2) 387 | expect(gearbox.str.toInt('2e3')).to.equal(2000) 388 | expect(gearbox.str.toInt('-2e3')).to.equal(-2000) 389 | expect(gearbox.str.toInt('2e100')).to.equal(2e100) 390 | expect(gearbox.str.toInt('-2e100')).to.equal(-2e100) 391 | expect(gearbox.str.toInt('1.23foo')).to.equal(1) 392 | expect(gearbox.str.toInt('-1.23foo')).to.equal(-1) 393 | expect(isNaN(gearbox.str.toInt('foo123'))).to.equal(true) 394 | }) 395 | }) 396 | describe('gearbox.str.toFixed()', function () { 397 | it('does basic functionality', function () { 398 | expect(gearbox.str.toFixed('0')).to.equal(0) 399 | expect(gearbox.str.toFixed('0', 2)).to.equal(0) 400 | expect(gearbox.str.toFixed('1.77')).to.equal(2) 401 | expect(gearbox.str.toFixed('1.77', 1)).to.equal(1.8) 402 | expect(gearbox.str.toFixed('-1.77', 1)).to.equal(-1.8) 403 | expect(gearbox.str.toFixed('2.3.6', 2)).to.equal(2.3) 404 | expect(gearbox.str.toFixed('-2.3.6', 2)).to.equal(-2.3) 405 | expect(gearbox.str.toFixed('2e3', 3)).to.equal(2000) 406 | expect(gearbox.str.toFixed('-2e3', 3)).to.equal(-2000) 407 | expect(gearbox.str.toFixed('1.23foo', 1)).to.equal(1.2) 408 | expect(gearbox.str.toFixed('-1.23foo', 1)).to.equal(-1.2) 409 | expect(isNaN(gearbox.str.toFixed('foo123'))).to.equal(true) 410 | }) 411 | }) 412 | }) 413 | 414 | 415 | }) 416 | -------------------------------------------------------------------------------- /test/test-template.js: -------------------------------------------------------------------------------- 1 | describe('Template', function () { 2 | before(function () { 3 | var _config = { 4 | interpolate: /<%-([\s\S]+?)%>/g, 5 | escape: /<%=([\s\S]+?)%>/g, 6 | variable: 'data' 7 | } 8 | _.extend(_.templateSettings, _config) 9 | 10 | // for source code testing 11 | if (!gearbox.template) gearbox.template = template 12 | }) 13 | 14 | describe('APIs', function () { 15 | // const 16 | var TEMPLATE_ELEM_ID_1 = 'elem-paragraph' 17 | var TEMPLATE_ELEM_ID_2 = 'elem-person' 18 | var TEMPLATE_CODE_ID_1 = 'code-paragraph' 19 | var TEMPLATE_CODE_ID_2 = 'code-person' 20 | var HELLO = 'Hello world!' 21 | var PREFIX = 'template-' 22 | var SCRIPT_TYPE = 'text/template' 23 | 24 | // template code 25 | var templateCode1 = '

<%= data.text %>

' 26 | var templateCode2 = [ 27 | '

' 32 | ].join('\n') 33 | 34 | // template data 35 | var templateData1 = {text: HELLO} 36 | var templateData2 = [ 37 | {name: 'Peter', age: '31'}, 38 | {name: 'Judy', age: '24'} 39 | ] 40 | 41 | // result 42 | var result1 = '

' + HELLO + '

' 43 | var result2 = [ 44 | '

' 48 | ].join('\n') 49 | 50 | // test data 51 | var html1, html2 52 | 53 | describe('gearbox.template.add()', function () { 54 | it('(this api will be tested in below test cases)', function () { 55 | // 56 | }) 57 | }) 58 | describe('gearbox.template.render()', function () { 59 | function _prepareDummyScript() { 60 | var body = document.body 61 | var script1 = document.createElement('script') 62 | script1.type = SCRIPT_TYPE 63 | script1.id = PREFIX + TEMPLATE_ELEM_ID_1 64 | script1.text = templateCode1 65 | body.appendChild(script1) 66 | 67 | var script2 = document.createElement('script') 68 | script2.type = SCRIPT_TYPE 69 | script2.id = PREFIX + TEMPLATE_ELEM_ID_2 70 | script2.text = templateCode2 71 | body.appendChild(script2) 72 | } 73 | function _destroyDummyScript() { 74 | $("#" + PREFIX + TEMPLATE_ELEM_ID_1).remove() 75 | $("#" + PREFIX + TEMPLATE_ELEM_ID_2).remove() 76 | } 77 | function _cleanStr(str) { 78 | str = String(str) 79 | return str.replace(/\s+/g, ' ') 80 | } 81 | 82 | it('gets template from dom, then renders it', function () { 83 | _prepareDummyScript() 84 | 85 | html1 = gearbox.template.render(TEMPLATE_ELEM_ID_1, templateData1) 86 | expect(html1).to.equal(result1) 87 | html2 = gearbox.template.render(TEMPLATE_ELEM_ID_2, templateData2) 88 | expect(_cleanStr(html2)).to.equal(_cleanStr(result2)) 89 | 90 | _destroyDummyScript() 91 | }) 92 | it('adds template manually, then renders it', function () { 93 | // use `add()` api to 94 | gearbox.template.add(TEMPLATE_CODE_ID_1, templateCode1) 95 | gearbox.template.add(TEMPLATE_CODE_ID_2, templateCode2) 96 | 97 | html1 = gearbox.template.render(TEMPLATE_CODE_ID_1, templateData1) 98 | expect(html1).to.equal(result1) 99 | html2 = gearbox.template.render(TEMPLATE_CODE_ID_2, templateData2) 100 | expect(_cleanStr(html2)).to.equal(_cleanStr(result2)) 101 | }) 102 | it('gets template from cache, then renders it', function () { 103 | // notice: after above testing, there have been 4 templates in cache 104 | 105 | html1 = gearbox.template.render(TEMPLATE_ELEM_ID_1, templateData1) 106 | expect(html1).to.equal(result1) 107 | html2 = gearbox.template.render(TEMPLATE_ELEM_ID_2, templateData2) 108 | expect(_cleanStr(html2)).to.equal(_cleanStr(result2)) 109 | 110 | html1 = gearbox.template.render(TEMPLATE_CODE_ID_1, templateData1) 111 | expect(html1).to.equal(result1) 112 | html2 = gearbox.template.render(TEMPLATE_CODE_ID_2, templateData2) 113 | expect(_cleanStr(html2)).to.equal(_cleanStr(result2)) 114 | }) 115 | }) 116 | 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /test/test-ua.js: -------------------------------------------------------------------------------- 1 | describe('UA', function () { 2 | 3 | var _detect = gearbox.ua.__detect 4 | var _trimVersion = gearbox.ua.__trimVersion 5 | 6 | describe('Util', function () { 7 | if (!_trimVersion) { 8 | it('(Skipped)', function () {}) 9 | return 10 | } 11 | describe('_trimVersion()', function () { 12 | it('does basic functionality', function () { 13 | var ver 14 | ver = '1.2.3' 15 | expect(_trimVersion(ver, 1)).to.equal('1') 16 | expect(_trimVersion(ver, 2)).to.equal('1.2') 17 | expect(_trimVersion(ver, 3)).to.equal('1.2.3') 18 | ver = 'x.y.z' 19 | expect(_trimVersion(ver, 1)).to.equal('x') 20 | expect(_trimVersion(ver, 2)).to.equal('x.y') 21 | expect(_trimVersion(ver, 3)).to.equal('x.y.z') 22 | ver = '0.0' 23 | expect(_trimVersion(ver, 1)).to.equal('0') 24 | expect(_trimVersion(ver, 2)).to.equal('0.0') 25 | expect(_trimVersion(ver, 3)).to.equal('0.0') 26 | ver = '0' 27 | expect(_trimVersion(ver, 1)).to.equal('0') 28 | expect(_trimVersion(ver, 2)).to.equal('0') 29 | expect(_trimVersion(ver, 3)).to.equal('0') 30 | }) 31 | it('trim version string to 2 segments by default', function () { 32 | var ver 33 | ver = '1.2.3' 34 | expect(_trimVersion(ver)).to.equal(_trimVersion(ver, 2)) 35 | ver = 'x.y.z' 36 | expect(_trimVersion(ver)).to.equal(_trimVersion(ver, 2)) 37 | ver = '0.0' 38 | expect(_trimVersion(ver)).to.equal(_trimVersion(ver, 2)) 39 | ver = '0' 40 | expect(_trimVersion(ver)).to.equal(_trimVersion(ver, 2)) 41 | }) 42 | }) 43 | }) 44 | 45 | describe('Detect Feature', function () { 46 | describe('gearbox.ua.isTouchDevice', function () { 47 | it('means element has `touch-` event', function () { 48 | // this test case is based on the idea of feature detection. 49 | if (gearbox.ua.isTouchDevice) { 50 | expect('TouchEvent' in window).to.equal(true) 51 | expect('ontouchstart' in window).to.equal(true) 52 | expect('ontouchmove' in window).to.equal(true) 53 | expect('ontouchend' in window).to.equal(true) 54 | } 55 | }) 56 | }) 57 | }) 58 | 59 | describe('Detect UA String', function () { 60 | if (!_detect) { 61 | it('(Skipped)', function () {}) 62 | return 63 | } 64 | // ios safari 65 | var iphone_ios_40 = {str: 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.05 Mobile/8A293 Safari/6531.22.7'} 66 | var ipod_ios_30 = {str: 'Mozilla/5.0 (iPod; U; CPU iPhone OS 3_0 like Mac OS X; zh-cn) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16'} 67 | var ipad_ios_322 = {str: 'Mozilla/5.0 (iPad; U; CPU OS_3_2_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'} 68 | var iphone_ios_60 = {str: 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'} 69 | var iphone_ios_00 = {str: 'Mozilla/5.0 (iPhone; U; CPU iPhone OS x_x_x like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.05 Mobile/8A293 Safari/6531.22.7'} 70 | var ipod_ios_99900 = {str: 'Mozilla/5.0 (iPod; U; CPU iPhone OS_999 like Mac OS X; zh-cn) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16'} 71 | var ipad_ios_00 = {str: 'Mozilla/5.0 (iPad; U; CPU OS ?_?_? like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10'} 72 | // ios uc 73 | var uc_ios_421 = {str: 'IUC(U;iOS 4.2.1;Zh-cn;320*480;)/UCWEB8.1.2.111/41/800'} 74 | var uc_ios_501 = {str: 'IUC(U;iOS 5.0.1;Zh-cn;320*480;)/UCWEB8.4.1.169/42/997'} 75 | // ios qq 76 | var qq_ios_511 = {str: 'MQQBrowser/34 Mozilla/5.0 (iPhone 4; CPU iPhone OS 5_1_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B206 Safari/7534.48.3'} 77 | // ios chrome 78 | var gc_ios_511 = {str: 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 7_0_1 like Mac OS X; zh-cn) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3'} 79 | 80 | describe('iOS Safari', function () { 81 | it('recognizes iphone_ios_40', function () { 82 | var ua = _detect(iphone_ios_40) 83 | expect(ua.isMobileDevice).to.equal(true) 84 | expect(!!ua.isIOS).to.equal(true) 85 | expect(!!ua.isAndroid).to.equal(false) 86 | expect(!!ua.isIPhone).to.equal(true) 87 | expect(!!ua.isIPod).to.equal(false) 88 | expect(!!ua.isIPad).to.equal(false) 89 | expect(ua.osVersion).to.equal('4.0') 90 | //expect(ua.browser).to.equal('safari') 91 | expect(ua.engine).to.equal('webkit') 92 | expect(ua.engineVersion).to.equal('532.9') 93 | }) 94 | it('recognizes ipod_ios_30', function () { 95 | var ua = _detect(ipod_ios_30) 96 | expect(ua.isMobileDevice).to.equal(true) 97 | expect(!!ua.isIOS).to.equal(true) 98 | expect(!!ua.isAndroid).to.equal(false) 99 | expect(!!ua.isIPhone).to.equal(false) 100 | expect(!!ua.isIPod).to.equal(true) 101 | expect(!!ua.isIPad).to.equal(false) 102 | expect(ua.osVersion).to.equal('3.0') 103 | //expect(ua.browser).to.equal('safari') 104 | expect(ua.engine).to.equal('webkit') 105 | expect(ua.engineVersion).to.equal('528.18') 106 | }) 107 | it('recognizes ipad_ios_322', function () { 108 | var ua = _detect(ipad_ios_322) 109 | expect(ua.isMobileDevice).to.equal(true) 110 | expect(!!ua.isIOS).to.equal(true) 111 | expect(!!ua.isAndroid).to.equal(false) 112 | expect(!!ua.isIPhone).to.equal(false) 113 | expect(!!ua.isIPod).to.equal(false) 114 | expect(!!ua.isIPad).to.equal(true) 115 | expect(ua.osVersion).to.equal('3.2') 116 | //expect(ua.browser).to.equal('safari') 117 | expect(ua.engine).to.equal('webkit') 118 | expect(ua.engineVersion).to.equal('531.21') 119 | }) 120 | it('recognizes iphone_ios_60', function () { 121 | var ua = _detect(iphone_ios_60) 122 | expect(ua.isMobileDevice).to.equal(true) 123 | expect(!!ua.isIOS).to.equal(true) 124 | expect(!!ua.isAndroid).to.equal(false) 125 | expect(!!ua.isIPhone).to.equal(true) 126 | expect(!!ua.isIPod).to.equal(false) 127 | expect(!!ua.isIPad).to.equal(false) 128 | expect(ua.osVersion).to.equal('6.0') 129 | //expect(ua.browser).to.equal('safari') 130 | expect(ua.engine).to.equal('webkit') 131 | expect(ua.engineVersion).to.equal('536.26') 132 | }) 133 | it('recognizes iphone_ios_00', function () { 134 | var ua = _detect(iphone_ios_00) 135 | expect(ua.osVersion).to.equal('') 136 | }) 137 | it('recognizes ipod_ios_99900', function () { 138 | var ua = _detect(ipod_ios_99900) 139 | expect(ua.osVersion).to.equal('999.0') 140 | }) 141 | it('recognizes ipad_ios_00', function () { 142 | var ua = _detect(ipad_ios_00) 143 | expect(ua.osVersion).to.equal('') 144 | }) 145 | }) 146 | describe('iOS Third-Party Browser', function () { 147 | it('recognizes uc_ios_421', function () { 148 | var ua = _detect(uc_ios_421) 149 | expect(ua.isMobileDevice).to.equal(true) 150 | expect(!!ua.isIOS).to.equal(true) 151 | expect(!!ua.isAndroid).to.equal(false) 152 | expect(ua.osVersion).to.equal('4.2') 153 | expect(ua.browser).to.equal('uc') 154 | expect(ua.engine).to.equal('webkit') 155 | expect(ua.engineVersion).to.equal('') 156 | }) 157 | it('recognizes uc_ios_501', function () { 158 | var ua = _detect(uc_ios_501) 159 | expect(ua.isMobileDevice).to.equal(true) 160 | expect(!!ua.isIOS).to.equal(true) 161 | expect(!!ua.isAndroid).to.equal(false) 162 | expect(ua.osVersion).to.equal('5.0') 163 | expect(ua.browser).to.equal('uc') 164 | expect(ua.engine).to.equal('webkit') 165 | expect(ua.engineVersion).to.equal('') 166 | }) 167 | it('recognizes qq_ios_511', function () { 168 | var ua = _detect(qq_ios_511) 169 | expect(ua.isMobileDevice).to.equal(true) 170 | expect(!!ua.isIOS).to.equal(true) 171 | expect(!!ua.isAndroid).to.equal(false) 172 | expect(!!ua.isIPhone).to.equal(true) 173 | expect(!!ua.isIPod).to.equal(false) 174 | expect(!!ua.isIPad).to.equal(false) 175 | expect(ua.osVersion).to.equal('5.1') 176 | expect(ua.browser).to.equal('m-qq-browser') 177 | expect(ua.engine).to.equal('webkit') 178 | expect(ua.engineVersion).to.equal('534.46') 179 | }) 180 | it('recognizes gc_ios_511', function () { 181 | var ua = _detect(gc_ios_511) 182 | expect(!!ua.isChrome).to.equal(true) 183 | expect(ua.isMobileDevice).to.equal(true) 184 | expect(!!ua.isIOS).to.equal(true) 185 | expect(!!ua.isAndroid).to.equal(false) 186 | expect(!!ua.isIPhone).to.equal(true) 187 | expect(!!ua.isIPod).to.equal(false) 188 | expect(!!ua.isIPad).to.equal(false) 189 | expect(ua.osVersion).to.equal('7.0') 190 | expect(ua.browser).to.equal('chrome') 191 | expect(ua.engine).to.equal('webkit') 192 | expect(ua.engineVersion).to.equal('534.46') 193 | }) 194 | }) 195 | 196 | // android 197 | var adr_21 = {str: 'Mozilla/5.0 (Linux; U; Android 2.1-update1; zh-cn; SCH-i909 Build/ECLAIR) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17'} 198 | var adr_22 = {str: 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1'} 199 | var adr_234 = {str: 'Mozilla/5.0 (Linux; U; Android 2.3.4; fr-fr; Nexus S Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1'} 200 | var adr_403 = {str: 'sprd-SunupG20/1.0 Linux/2.6.35.7 Android/Android4.0.3 Release/08.16.2012 Browser/AppleWebKit533.1 (KHTML, like Gecko) Mozilla/5.0 Mobile'} 201 | // android gc 202 | var gc_adr_404 = {str: 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19'} 203 | // android uc 204 | var uc_adr_233 = {str: 'ucweb/2.0 (linux; u; adr 2.3.3; zh-cn; htc incredible s) u2/1.0.0 ucbrowser/9.0.1.294 u2/1.0.0 mobile'} 205 | var uc_adr_234 = {str: 'juc(linux;u;2.3.4;zh_cn;adr6400l;480*800;)ucweb7.8.1.96/139/800'} 206 | var uc_adr_404 = {str: 'juc (linux; u; 4.0.4; zh-cn; adr6400l; 480*800) ucweb7.9.1.100/139/800'} 207 | var uc_adr_00 = {str: 'Mozilla/5.0 (Linux; U; Android VillainROM7.0.0; zh-cn; HTC Hero Build/ERE27) UC AppleWebKit/530+ (KHTML, like Gecko) Mobile Safari/530'} 208 | // android qq 209 | var qq_adr_422 = {str: 'mqqbrowser/3.6/adr (linux; u; 4.2.2; zh-cn; t9508 build/t9508_v2.00;480*854)'} 210 | // android firefox 211 | var ff_adr_00 = {str: 'Mozilla/5.0 (Android; Linux armv7l; rv:10.0.2) Gecko/20120215 Firefox/10.0.2 Fennec/10.0.2'} 212 | // android opera 213 | var op_adr_404 = {str: 'opera/9.80 (android 4.0.4; linux; opera mobi/oupenghd-1.6/adr-1301101034) presto/2.11.355 version/12.10'} 214 | // android other 215 | var xx_adr_235 = {str: 'coolpad8070_cmcc_td/1.08 linux/2.6.35 android/2.3.5 release/03.29.2012 mozilla/5.0 applewebkit/533.1 version/4.0 mobile safari/533.1 parameters/{scr=533_320,cm=1,ql=l} tiantian(securitypay) tiantian(tenpay)'} 216 | var xx_adr_22 = {str: 'coship_coship t71_td/1.0 android_os/2.2 marvell_pxa918/beta10 release/7.11.2011 browser/skybrowser1.0.6 profile/midp-2.0 configuration/cldc-1.1 parameters/{scr=480_320,cm=1,ql=l} tiantian(securitypay)'} 217 | 218 | describe('Android Browser', function () { 219 | it('recognizes adr_21', function () { 220 | var ua = _detect(adr_21) 221 | expect(ua.isMobileDevice).to.equal(true) 222 | expect(!!ua.isIOS).to.equal(false) 223 | expect(!!ua.isAndroid).to.equal(true) 224 | expect(ua.osVersion).to.equal('2.1') 225 | expect(ua.browser).to.equal('') 226 | expect(ua.engine).to.equal('webkit') 227 | expect(ua.engineVersion).to.equal('530.17') 228 | }) 229 | it('recognizes adr_22', function () { 230 | var ua = _detect(adr_22) 231 | expect(ua.isMobileDevice).to.equal(true) 232 | expect(!!ua.isIOS).to.equal(false) 233 | expect(!!ua.isAndroid).to.equal(true) 234 | expect(ua.osVersion).to.equal('2.2') 235 | expect(ua.browser).to.equal('') 236 | expect(ua.engine).to.equal('webkit') 237 | expect(ua.engineVersion).to.equal('533.1') 238 | }) 239 | it('recognizes adr_234', function () { 240 | var ua = _detect(adr_234) 241 | expect(ua.isMobileDevice).to.equal(true) 242 | expect(!!ua.isIOS).to.equal(false) 243 | expect(!!ua.isAndroid).to.equal(true) 244 | expect(ua.osVersion).to.equal('2.3') 245 | expect(ua.browser).to.equal('') 246 | expect(ua.engine).to.equal('webkit') 247 | expect(ua.engineVersion).to.equal('533.1') 248 | }) 249 | it('recognizes adr_403', function () { 250 | var ua = _detect(adr_403) 251 | expect(ua.isMobileDevice).to.equal(true) 252 | expect(!!ua.isIOS).to.equal(false) 253 | expect(!!ua.isAndroid).to.equal(true) 254 | expect(ua.osVersion).to.equal('4.0') 255 | expect(ua.browser).to.equal('') 256 | expect(ua.engine).to.equal('webkit') 257 | expect(ua.engineVersion).to.equal('533.1') 258 | }) 259 | 260 | it('recognizes gc_adr_404', function () { 261 | var ua = _detect(gc_adr_404) 262 | expect(ua.isMobileDevice).to.equal(true) 263 | expect(!!ua.isIOS).to.equal(false) 264 | expect(!!ua.isAndroid).to.equal(true) 265 | expect(ua.osVersion).to.equal('4.0') 266 | expect(ua.browser).to.equal('chrome') 267 | expect(ua.engine).to.equal('chrome') 268 | expect(ua.engineVersion).to.equal('18.0') 269 | }) 270 | }) 271 | 272 | describe('Android Third-Party Browser', function () { 273 | it('recognizes uc_adr_233', function () { 274 | var ua = _detect(uc_adr_233) 275 | expect(ua.isMobileDevice).to.equal(true) 276 | expect(!!ua.isIOS).to.equal(false) 277 | expect(!!ua.isAndroid).to.equal(true) 278 | expect(ua.osVersion).to.equal('2.3') 279 | expect(ua.browser).to.equal('uc') 280 | expect(ua.engine).to.equal('') 281 | expect(ua.engineVersion).to.equal('') 282 | }) 283 | it('recognizes uc_adr_234', function () { 284 | var ua = _detect(uc_adr_234) 285 | expect(ua.isMobileDevice).to.equal(true) 286 | expect(!!ua.isIOS).to.equal(false) 287 | expect(!!ua.isAndroid).to.equal(true) 288 | expect(ua.osVersion).to.equal('2.3') 289 | expect(ua.browser).to.equal('uc') 290 | expect(ua.engine).to.equal('') 291 | expect(ua.engineVersion).to.equal('') 292 | }) 293 | it('recognizes uc_adr_404', function () { 294 | var ua = _detect(uc_adr_404) 295 | expect(ua.isMobileDevice).to.equal(true) 296 | expect(!!ua.isIOS).to.equal(false) 297 | expect(!!ua.isAndroid).to.equal(true) 298 | expect(ua.osVersion).to.equal('4.0') 299 | expect(ua.browser).to.equal('uc') 300 | expect(ua.engine).to.equal('') 301 | expect(ua.engineVersion).to.equal('') 302 | }) 303 | it('recognizes uc_adr_00', function () { 304 | var ua = _detect(uc_adr_00) 305 | expect(ua.isMobileDevice).to.equal(true) 306 | expect(!!ua.isIOS).to.equal(false) 307 | expect(!!ua.isAndroid).to.equal(true) 308 | expect(ua.osVersion).to.equal('') 309 | expect(ua.browser).to.equal('uc') 310 | expect(ua.engine).to.equal('webkit') 311 | expect(ua.engineVersion).to.equal('530') 312 | }) 313 | 314 | it('recognizes qq_adr_422', function () { 315 | var ua = _detect(qq_adr_422) 316 | expect(ua.isMobileDevice).to.equal(true) 317 | expect(!!ua.isIOS).to.equal(false) 318 | expect(!!ua.isAndroid).to.equal(true) 319 | expect(ua.osVersion).to.equal('4.2') 320 | expect(ua.browser).to.equal('m-qq-browser') 321 | expect(ua.engine).to.equal('webkit') 322 | expect(ua.engineVersion).to.equal('') 323 | }) 324 | 325 | it('recognizes ff_adr_00', function () { 326 | var ua = _detect(ff_adr_00) 327 | expect(ua.isMobileDevice).to.equal(true) 328 | expect(!!ua.isIOS).to.equal(false) 329 | expect(!!ua.isAndroid).to.equal(true) 330 | expect(ua.osVersion).to.equal('') 331 | expect(ua.browser).to.equal('firefox') 332 | expect(ua.engine).to.equal('gecko') 333 | expect(ua.engineVersion).to.equal('') 334 | }) 335 | 336 | it('recognizes op_adr_404', function () { 337 | var ua = _detect(op_adr_404) 338 | expect(ua.isMobileDevice).to.equal(true) 339 | expect(!!ua.isIOS).to.equal(false) 340 | expect(!!ua.isAndroid).to.equal(true) 341 | expect(ua.osVersion).to.equal('4.0') 342 | expect(ua.browser).to.equal('opera') 343 | expect(ua.engine).to.equal('presto') 344 | expect(ua.engineVersion).to.equal('') 345 | }) 346 | 347 | it('recognizes xx_adr_235', function () { 348 | var ua = _detect(xx_adr_235) 349 | expect(ua.isMobileDevice).to.equal(true) 350 | expect(!!ua.isIOS).to.equal(false) 351 | expect(!!ua.isAndroid).to.equal(true) 352 | expect(ua.osVersion).to.equal('2.3') 353 | expect(ua.browser).to.equal('') 354 | expect(ua.engine).to.equal('webkit') 355 | expect(ua.engineVersion).to.equal('533.1') 356 | }) 357 | it('recognizes xx_adr_22', function () { 358 | var ua = _detect(xx_adr_22) 359 | expect(ua.isMobileDevice).to.equal(true) 360 | expect(!!ua.isIOS).to.equal(false) 361 | expect(!!ua.isAndroid).to.equal(true) 362 | expect(ua.osVersion).to.equal('2.2') 363 | expect(ua.browser).to.equal('') 364 | expect(ua.engine).to.equal('') 365 | expect(ua.engineVersion).to.equal('') 366 | }) 367 | }) 368 | 369 | // webview 370 | var wv_adr_511 = {str: 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/537.36'} 371 | describe('Android Chrome WebView', function () { 372 | it('recognizes wv_adr_511', function () { 373 | var ua = _detect(wv_adr_511) 374 | expect(ua.isMobileDevice).to.equal(true) 375 | expect(!!ua.isIOS).to.equal(false) 376 | expect(!!ua.isAndroid).to.equal(true) 377 | expect(ua.osVersion).to.equal('5.1') 378 | expect(ua.browser).to.equal('chrome-webview') 379 | expect(ua.engine).to.equal('chrome') 380 | expect(ua.engineVersion).to.equal('43.0') 381 | }) 382 | }) 383 | 384 | // other 385 | var ff_mac = {str: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:41.0) Gecko/20100101 Firefox/41.0'} 386 | var chrome_mac = {str: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36'} 387 | var ie_wp81 = {str: 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 520) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537'} 388 | var edge_wp100 = {str: 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; DEVICE INFO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.xx'} 389 | describe('Other UA', function () { 390 | it('recognizes ff_mac', function () { 391 | var ua = _detect(ff_mac) 392 | expect(ua.isMobileDevice).to.equal(false) 393 | expect(!!ua.isIOS).to.equal(false) 394 | expect(!!ua.isAndroid).to.equal(false) 395 | expect(ua.osVersion).to.equal('') 396 | expect(ua.browser).to.equal('firefox') 397 | expect(ua.engine).to.equal('gecko') 398 | expect(ua.engineVersion).to.equal('') 399 | }) 400 | it('recognizes chrome_mac', function () { 401 | var ua = _detect(chrome_mac) 402 | expect(ua.isMobileDevice).to.equal(false) 403 | expect(!!ua.isIOS).to.equal(false) 404 | expect(!!ua.isAndroid).to.equal(false) 405 | expect(ua.osVersion).to.equal('') 406 | expect(ua.browser).to.equal('chrome') 407 | expect(ua.engine).to.equal('chrome') 408 | expect(ua.engineVersion).to.equal('47.0') 409 | }) 410 | it('recognizes ie_wp81', function () { 411 | var ua = _detect(ie_wp81) 412 | expect(ua.isMobileDevice).to.equal(false) 413 | expect(!!ua.isIOS).to.equal(false) 414 | expect(!!ua.isAndroid).to.equal(false) 415 | expect(ua.osVersion).to.equal('') 416 | expect(ua.browser).to.equal('ie-mobile') 417 | expect(ua.engine).to.equal('') 418 | expect(ua.engineVersion).to.equal('') 419 | }) 420 | it('recognizes edge_wp100', function () { 421 | var ua = _detect(edge_wp100) 422 | expect(ua.isMobileDevice).to.equal(false) 423 | expect(!!ua.isIOS).to.equal(false) 424 | expect(!!ua.isAndroid).to.equal(false) 425 | expect(ua.osVersion).to.equal('') 426 | expect(ua.browser).to.equal('edge') 427 | expect(ua.engine).to.equal('edge') 428 | expect(ua.engineVersion).to.equal('') 429 | }) 430 | }) 431 | }) 432 | }) 433 | 434 | -------------------------------------------------------------------------------- /test/test-url.js: -------------------------------------------------------------------------------- 1 | describe('URL', function () { 2 | describe('Query String', function () { 3 | describe('gearbox.url.parseQuery()', function () { 4 | it('parses empty str to empty object', function () { 5 | var query = '' 6 | expect(gearbox.url.parseQuery(query)).to.eql({}) 7 | }) 8 | it('parses key/value pairs to object', function () { 9 | var query = 'foo=1&bar=2&alice=&bob&chris=3' 10 | expect(gearbox.url.parseQuery(query)).to.eql({ 11 | foo: '1', 12 | bar: '2', 13 | alice: '', 14 | bob: '', 15 | chris: '3' 16 | }) 17 | }) 18 | it('decodes keys and values in query string', function () { 19 | var query = 'foo=%20&bar=%2B&blah%3Dblah=1' 20 | expect(gearbox.url.parseQuery(query)).to.eql({ 21 | foo: ' ', 22 | bar: '+', 23 | 'blah=blah': '1' 24 | }) 25 | }) 26 | it('returns empty object if bad type of param', function () { 27 | var arg 28 | arg = undefined 29 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 30 | arg = null 31 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 32 | arg = 0 33 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 34 | arg = true 35 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 36 | arg = {} 37 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 38 | arg = [] 39 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 40 | arg = it 41 | expect(gearbox.url.parseQuery(arg)).to.eql({}) 42 | }) 43 | }) 44 | 45 | describe('gearbox.url.getParam()', function () { 46 | var registeredTests = {} 47 | var SANDBOX_FILE = '_sandbox.html' 48 | var DUMMY_SRC = 'about:blank' 49 | var $sandbox 50 | 51 | function _getRandomStr() { 52 | return (new Date().getTime() + Math.random()).toString(36) 53 | } 54 | 55 | // old IE don't support `history.replaceState()`, so we need a sandbox to run tests. 56 | function _initSandbox() { 57 | $sandbox = $('') 58 | .attr({ 59 | src: DUMMY_SRC, 60 | id: 'sandbox', 61 | frameborder: 0 62 | }) 63 | .css({ 64 | display: 'block', 65 | visibility: 'visible', 66 | height: 0 67 | }) 68 | .appendTo(document.body) 69 | } 70 | function _resetSandbox() { 71 | $sandbox.attr('src', DUMMY_SRC) 72 | } 73 | function _destroySandbox() { 74 | $sandbox.remove() 75 | } 76 | function _getSandboxWindow() { 77 | return $sandbox[0].contentWindow 78 | } 79 | function _startSandboxTest(queryString, keysToBeTested, fn) { 80 | var testId = _getRandomStr() 81 | registeredTests[testId] = fn 82 | // set url to sandbox, this url will be tested when sandbox is ready 83 | $sandbox.attr('src', SANDBOX_FILE + '?' + queryString) 84 | // send test conditions to sandbox via `window.name` 85 | _getSandboxWindow().name = JSON.stringify({ 86 | keysToBeTested: keysToBeTested, 87 | testId: testId 88 | }) 89 | } 90 | function _listenSandboxMessage() { 91 | var handler = function (ev) { 92 | // recover complete data 93 | var data = JSON.parse(ev.data || '{}') 94 | var result = data.result || {} 95 | _.each(data.emptyKeys || [], function (item) { 96 | result[item] = undefined 97 | }) 98 | 99 | var fn = registeredTests[data.testId] 100 | if (_.isFunction(fn)) fn(result) 101 | } 102 | // note: `typeof window.attachEvent` returns 'object' 103 | if ('attachEvent' in window) { 104 | window.attachEvent('onmessage', handler) 105 | } else { 106 | window.addEventListener('message', handler, false) 107 | } 108 | } 109 | 110 | before(function () { 111 | _initSandbox() 112 | _listenSandboxMessage() 113 | }) 114 | after(function () { 115 | _destroySandbox() 116 | }) 117 | afterEach(function () { 118 | _resetSandbox() 119 | }) 120 | 121 | it('does basic functionality', function (done) { 122 | this.timeout(5000) 123 | var queryString = 'foo=1&bar=2&alice=&bob&chris=3' 124 | var expectedResult = { 125 | 'foo': '1', 126 | 'bar': '2', 127 | 'alice': '', 128 | 'bob': '', 129 | 'chris': '3' 130 | } 131 | var keysToBeTested = _.keys(expectedResult) 132 | _startSandboxTest(queryString, keysToBeTested, function (result) { 133 | expect(result).to.eql(expectedResult) 134 | done() 135 | }) 136 | }) 137 | it('returns `undefined` if query string is empty', function (done) { 138 | this.timeout(5000) 139 | var queryString = '' 140 | var expectedResult = { 141 | "foo": undefined, 142 | "bar": undefined 143 | } 144 | var keysToBeTested = _.keys(expectedResult) 145 | _startSandboxTest(queryString, keysToBeTested, function (result) { 146 | expect(result).to.eql(expectedResult) 147 | done() 148 | }) 149 | }) 150 | it('returns `undefined` if getting a missing param key', function (done) { 151 | this.timeout(5000) 152 | var queryString = 'bar=1' 153 | var expectedResult = { 154 | "blah": undefined 155 | } 156 | var keysToBeTested = _.keys(expectedResult) 157 | _startSandboxTest(queryString, keysToBeTested, function (result) { 158 | expect(result).to.eql(expectedResult) 159 | done() 160 | }) 161 | }) 162 | it('returns `false` if bad type of param', function () { 163 | var arg 164 | arg = undefined 165 | expect(gearbox.url.getParam(arg)).to.equal(false) 166 | arg = null 167 | expect(gearbox.url.getParam(arg)).to.equal(false) 168 | arg = 0 169 | expect(gearbox.url.getParam(arg)).to.equal(false) 170 | arg = true 171 | expect(gearbox.url.getParam(arg)).to.equal(false) 172 | arg = {} 173 | expect(gearbox.url.getParam(arg)).to.equal(false) 174 | arg = [] 175 | expect(gearbox.url.getParam(arg)).to.equal(false) 176 | arg = it 177 | expect(gearbox.url.getParam(arg)).to.equal(false) 178 | }) 179 | it('re-parses if url changed', function () { 180 | // skip this test case on ie9- 181 | if (!('replaceState' in history)) return 182 | 183 | var _state = history.state || null 184 | var _url = location.href 185 | 186 | var url 187 | url = '?' + 'foo=%20&bar=%2B&blah%3Dblah=1' 188 | history.replaceState(_state, null, url) 189 | expect(gearbox.url.getParam('foo')).to.equal(' ') 190 | expect(gearbox.url.getParam('bar')).to.equal('+') 191 | expect(gearbox.url.getParam('blah=blah')).to.equal('1') 192 | url = '?' 193 | history.replaceState(_state, null, url) 194 | expect(gearbox.url.getParam('foo')).to.be.undefined 195 | expect(gearbox.url.getParam('bar')).to.be.undefined 196 | expect(gearbox.url.getParam('blah=blah')).to.be.undefined 197 | url = '?' + 'foo=%20&bar=%2B&blah%3Dblah=1' 198 | history.replaceState(_state, null, url) 199 | expect(gearbox.url.getParam('foo')).to.equal(' ') 200 | expect(gearbox.url.getParam('bar')).to.equal('+') 201 | expect(gearbox.url.getParam('blah=blah')).to.equal('1') 202 | 203 | // restore url 204 | history.replaceState(_state, null, _url) 205 | }) 206 | }) 207 | 208 | describe('gearbox.url.appendParam()', function () { 209 | it('does basic functionality', function () { 210 | var baseUrl = 'http://domain.com/path/file' 211 | var url1 = gearbox.url.appendParam(baseUrl, {foo: 'bar'}) 212 | var url2 = gearbox.url.appendParam(url1, {test: 1}) 213 | expect(url1).to.equal(baseUrl + '?foo=bar') 214 | expect(url2).to.equal(baseUrl + '?foo=bar&test=1') 215 | 216 | var testUrl = baseUrl + '?key=value' 217 | var url3 = gearbox.url.appendParam(testUrl, {foo: 'bar'}) 218 | var url4 = gearbox.url.appendParam(url3, {test: 1}) 219 | expect(url3).to.equal(baseUrl + '?key=value&foo=bar') 220 | expect(url4).to.equal(baseUrl + '?key=value&foo=bar&test=1') 221 | }) 222 | }) 223 | }) 224 | 225 | describe('Hash Handling', function () { 226 | describe('gearbox.url.removeHashFromUrl()', function () { 227 | it('does basic functionality', function () { 228 | var url 229 | url = gearbox.url.removeHashFromUrl('http://domain.com/path/file?query=1#hash') 230 | expect(url).to.equal('http://domain.com/path/file?query=1') 231 | url = gearbox.url.removeHashFromUrl('http://domain.com/path/file#hash') 232 | expect(url).to.equal('http://domain.com/path/file') 233 | url = gearbox.url.removeHashFromUrl('//domain.com/path/file#hash') 234 | expect(url).to.equal('//domain.com/path/file') 235 | url = gearbox.url.removeHashFromUrl('/path/file#hash') 236 | expect(url).to.equal('/path/file') 237 | url = gearbox.url.removeHashFromUrl('file#hash') 238 | expect(url).to.equal('file') 239 | url = gearbox.url.removeHashFromUrl('#hash') 240 | expect(url).to.equal('') 241 | url = gearbox.url.removeHashFromUrl('http://domain.com/path/file#hash#foo') 242 | expect(url).to.equal('http://domain.com/path/file') 243 | }) 244 | it('converts param to string if it\'s not a string', function () { 245 | var url 246 | url = gearbox.url.removeHashFromUrl(null) 247 | expect(url).to.equal('null') 248 | url = gearbox.url.removeHashFromUrl(undefined) 249 | expect(url).to.equal('undefined') 250 | url = gearbox.url.removeHashFromUrl(3.1415) 251 | expect(url).to.equal('3.1415') 252 | url = gearbox.url.removeHashFromUrl(false) 253 | expect(url).to.equal('false') 254 | }) 255 | it('returns empty string if no param', function () { 256 | var url 257 | url = gearbox.url.removeHashFromUrl() 258 | expect(url).to.equal('') 259 | }) 260 | }) 261 | describe('gearbox.url.getHashFromUrl()', function () { 262 | it('does basic functionality', function () { 263 | var url 264 | url = gearbox.url.getHashFromUrl('http://domain.com/path/file?query=1#hash') 265 | expect(url).to.equal('#hash') 266 | url = gearbox.url.getHashFromUrl('http://domain.com/path/file#hash') 267 | expect(url).to.equal('#hash') 268 | url = gearbox.url.getHashFromUrl('//domain.com/path/file#hash') 269 | expect(url).to.equal('#hash') 270 | url = gearbox.url.getHashFromUrl('/path/file#hash') 271 | expect(url).to.equal('#hash') 272 | url = gearbox.url.getHashFromUrl('file#hash') 273 | expect(url).to.equal('#hash') 274 | url = gearbox.url.getHashFromUrl('#hash') 275 | expect(url).to.equal('#hash') 276 | url = gearbox.url.getHashFromUrl('http://domain.com/path/file#hash#foo') 277 | expect(url).to.equal('#hash#foo') 278 | }) 279 | it('converts param to string if it\'s not a string', function () { 280 | var url 281 | url = gearbox.url.getHashFromUrl(null) 282 | expect(url).to.equal('') 283 | url = gearbox.url.getHashFromUrl(undefined) 284 | expect(url).to.equal('') 285 | url = gearbox.url.getHashFromUrl(3.1415) 286 | expect(url).to.equal('') 287 | url = gearbox.url.getHashFromUrl(false) 288 | expect(url).to.equal('') 289 | }) 290 | it('returns empty string if no param', function () { 291 | var url 292 | url = gearbox.url.getHashFromUrl() 293 | expect(url).to.equal('') 294 | }) 295 | }) 296 | }) 297 | 298 | /* 299 | describe('Parse URL', function () { 300 | describe('gearbox.url.parseUrl()', function () { 301 | it('(dummy test)', function () { 302 | // 303 | }) 304 | }) 305 | }) 306 | */ 307 | 308 | }) 309 | --------------------------------------------------------------------------------