├── .gitignore ├── .npmrc ├── .travis.yml ├── .zuul.yml ├── LICENSE ├── README.md ├── local-links.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # tests 61 | .source.* 62 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: node_js 3 | node_js: 4 | - 6 5 | addons: 6 | apt: 7 | packages: 8 | - xvfb 9 | install: 10 | - export DISPLAY=':99.0' 11 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 12 | - npm install 13 | script: npm run lint && npm run validate && npm run test-travis 14 | env: 15 | global: 16 | - secure: Un33iUemAw5wmLHIzGCnnhMKIh0Q9XhTaiPI4ATjBVMSeDFkqe9rgtMYNiuK0hfbGX/sXS8Uj2EquprKScJAAK0eYa88AZlmg6QBOWP/xgwKa2yssndXDaaotj3Fz8OIi6BVGJO3ncaBbTJ1jaWvd9xAt2797uHqIrIN9LMtc3k= 17 | - secure: obyvEZIlPTGQ83NnDZQTCCnRi3eyyFUSjEnsg2U2sG7z7iJ4aaG8mii8O55ITGsip/kT7fTK9pZwaa04LhUWV/brphLiEpij1Vnc/M2N09R+5wIpefvDAD0acDqBTPXgCzXliQNnk+RH3LxOY1eAxcbI1EAaqBmJchkF3ntMWBk= 18 | - secure: mpP00cq7xP+XGbdwfpL2X9k3PwfVuL1RnsnrrM+PqbYuWW3R7L/Gd3WmjfXEwrp8X474w4YQMgUJHuFNbPrcWF0XBrbtkUFQDl7uIForfNE8z5X3wOoT2WRcbtpML4RpmfC291leJg1uJfITUJUzB8nvVgY6U4W+VcIEKRCF5UI= 19 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | browsers: 3 | - name: chrome 4 | version: latest 5 | platform: [Mac 10.10, Windows 10, Linux] 6 | - name: microsoftedge 7 | version: latest 8 | - name: firefox 9 | version: latest 10 | platform: [Mac 10.10, Windows 10, Linux] 11 | - name: safari 12 | version: latest 13 | - name: ie 14 | version: 9..latest 15 | concurrency: 2 16 | tunnel: 17 | type: ngrok 18 | bind_tls: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Luke Karrys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | local-links 2 | =========== 3 | 4 | Determine cross-browser if an event or anchor element should be handled locally. 5 | 6 | [![NPM](https://nodei.co/npm/local-links.png)](https://nodei.co/npm/local-links/) 7 | [![Build Status](https://travis-ci.org/lukekarrys/local-links.png?branch=master)](https://travis-ci.org/lukekarrys/local-links) 8 | [![Greenkeeper badge](https://badges.greenkeeper.io/lukekarrys/local-links.svg)](https://greenkeeper.io/) 9 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/lukekarrys-loclinks.svg)](https://saucelabs.com/u/lukekarrys-loclinks) 10 | 11 | 12 | ## Install 13 | 14 | `npm install local-links --save` 15 | 16 | ## Why? 17 | 18 | Browsers have quirks. Knowing if a link is local should be easy, since we 19 | just want to know if the hosts are the same. But this can be difficult because 20 | of the aforementioned browser quirks. A few of them: 21 | 22 | - IE9 will add `:80` to the host of an anchor, but not the window 23 | - IE9 wont put a leading slash on the pathname of an anchor, but will on the window 24 | - Chrome 36 will report anchor.hash as '' if it has `href="#"` 25 | - More? Please report test cases! 26 | 27 | Because of that and a few other things I was doing all the time, such as 28 | finding the closest anchor to an element based on an event object, I decided 29 | it would be a good module (that at least I would use all the time). 30 | 31 | ## Usage 32 | 33 | ```html 34 | Local 35 | Local 36 | Google 37 | ``` 38 | 39 | ```js 40 | var local = require('local-links'); 41 | 42 | // `pathname()` will return the pathname as a string 43 | // if the link is local, otherwise it will return null 44 | local.pathname(document.getElementById('local')) // '/page2' 45 | local.pathname(document.getElementById('hash')) // null 46 | local.pathname(document.getElementById('google')) // null 47 | 48 | // `hash()` will return the hash as a string 49 | // if the hash is to this page, otherwise it will return null 50 | local.hash(document.getElementById('local')) // null 51 | local.hash(document.getElementById('hash')) // '#hash' 52 | local.hash(document.getElementById('google')) // null 53 | ``` 54 | 55 | 56 | ## API 57 | 58 | 59 | ### Methods 60 | 61 | #### `getLocalPathname(Event or HTMLElement [, HTMLElement])` 62 | 63 | Returns the pathname if it is a non-hash local link, or null if it is not. 64 | Always includes the leading `/`. 65 | 66 | *Alias: `pathname`* 67 | 68 | #### `getLocalHash(Event or HTMLElement [, HTMLElement])` 69 | 70 | Returns the hash if it is an in-page hash link, or null if it is not. Always 71 | includes the leading `#`. 72 | 73 | *Alias: `hash`* 74 | 75 | #### `isActive(Event or HTMLElement [, String comparePath])` 76 | 77 | Returns true/false depending on if the anchor pathname is equal to the `comparePath` 78 | (which defaults to `window.location.pathname`). Calls `pathname()` internally. 79 | 80 | *Alias: `active`* 81 | 82 | #### `isLocal(event, anchor, [, Boolean lookForHash])` 83 | 84 | Returns the pathname (or hash if `lookForHash` is true) for local links, or null 85 | if it is not. This is used by `pathname()` and `hash()` under the hood. The main 86 | difference here is that you need to specify the `event` and `anchor` yourself, and 87 | the `anchor` wont be looked up from `event.target` like it would from the other methods. 88 | 89 | 90 | #### Supply either Event or HTMLElement 91 | 92 | The above methods will accept an `Event` object, like the one you get from 93 | click event handlers, or any `HTMLElement`. You can also supply an `Event` object 94 | and a different `HTMLElement` as the second parameter and it will take precedence. 95 | 96 | If only an `Event` object is supplied, the `HTMLElement` will be found from 97 | `Event.target`. 98 | 99 | 100 | #### Nested HTML Elements 101 | 102 | In the case that any `HTMLElement` your provide is not an anchor 103 | element, the module will look up `parentNodes` until an anchor is found. 104 | 105 | 106 | #### Events 107 | 108 | If an `Event` object is supplied, all methods will return `null` if any of the following 109 | are true `altKey`, `ctrlKey`, `metaKey`, `shiftKey`. This is because you almost always 110 | want to treat modified click events as external page clicks. 111 | 112 | 113 | #### `target="_blank"` 114 | 115 | If the anchor has target="_blank" it will return `null` for both the `pathname()` and 116 | `hash()` methods. 117 | 118 | 119 | #### Hash links 120 | 121 | Using the `pathname` method will return null for hash links that do not point 122 | to a different page. To get the hash for one of these links use the `hash()` method. 123 | 124 | 125 | ### Tests 126 | 127 | Run `npm start` and open [`http://localhost:3000`](http://localhost:3000) to run the tests in your browser. 128 | 129 | It is also a good idea to run `sudo npm run start-80` (requires admin) which will run the tests on [`http://localhost`](http://localhost) 130 | because there can be unexpected behavior when the host has no port in [IE9](https://github.com/lukekarrys/local-links/blob/master/local-links.js#L26) and [IE10](https://github.com/lukekarrys/local-links/blob/master/local-links.js#L28). 131 | 132 | To run the tests in the cli, just run `npm test`. 133 | 134 | 135 | #### License 136 | 137 | MIT 138 | -------------------------------------------------------------------------------- /local-links.js: -------------------------------------------------------------------------------- 1 | function isHTMLElement (obj) { 2 | return obj && 3 | (typeof obj === 'object') && 4 | (obj.nodeType === 1) && 5 | (typeof obj.style === 'object') && 6 | (typeof obj.ownerDocument === 'object') 7 | } 8 | 9 | function isA (obj) { 10 | return isHTMLElement(obj) && obj.tagName === 'A' 11 | } 12 | 13 | function closestA (checkNode) { 14 | do { 15 | if (isA(checkNode)) { 16 | return checkNode 17 | } 18 | } while ((checkNode = checkNode.parentNode)) 19 | } 20 | 21 | function normalizeLeadingSlash (pathname) { 22 | if (pathname.charAt(0) !== '/') { 23 | pathname = '/' + pathname 24 | } 25 | return pathname 26 | } 27 | 28 | function isRelativeUrl (href) { 29 | var r = /^https?:\/\/|^\/\//i 30 | return !r.test(href) 31 | } 32 | 33 | function isSecondaryButton (event) { 34 | return (typeof event === 'object') && ('button' in event) && event.button !== 0 35 | } 36 | 37 | // [1] http://blogs.msdn.com/b/ieinternals/archive/2011/02/28/internet-explorer-window-location-pathname-missing-slash-and-host-has-port.aspx 38 | // [2] https://github.com/substack/catch-links/blob/7aee219cdc2c845c78caad6070886a9380b90e4c/index.js#L13-L17 39 | // [3] IE10 (and possibly later) report that anchor.port is the default port 40 | // but dont append it to the hostname, so if the host doesnt end with the port 41 | // append it to the anchor host as well 42 | 43 | function isLocal (event, anchor, lookForHash) { 44 | event || (event = {}) 45 | 46 | // Skip modifier events 47 | if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { 48 | return null 49 | } 50 | 51 | // Skip non-primary clicks 52 | if (isSecondaryButton(event)) { 53 | return null 54 | } 55 | 56 | // If we have an anchor but its not an A tag 57 | // try to find the closest one 58 | if (anchor && !isA(anchor)) { 59 | anchor = closestA(anchor) 60 | } 61 | 62 | // Only test anchor elements 63 | if (!anchor || !isA(anchor)) { 64 | return null 65 | } 66 | 67 | // Dont test anchors with target=_blank 68 | if (anchor.target === '_blank') { 69 | return null 70 | } 71 | 72 | // IE9 doesn't put a leading slash on anchor.pathname [1] 73 | var aPathname = normalizeLeadingSlash(anchor.pathname) 74 | var wPathname = normalizeLeadingSlash(window.location.pathname) 75 | var aHost = anchor.host 76 | var aPort = anchor.port 77 | var wHost = window.location.host 78 | var wPort = window.location.port 79 | 80 | // In some cases, IE will have a blank host property when the href 81 | // is a relative URL. We can check for relativeness via the achor's 82 | // href attribute and then set the anchor's host to the window's host. 83 | if (aHost === '' && 84 | 'attributes' in anchor && 85 | 'href' in anchor.attributes && 86 | 'value' in anchor.attributes.href && 87 | isRelativeUrl(anchor.attributes.href.value)) { 88 | aHost = wHost 89 | } 90 | 91 | // Some browsers (Chrome 36) return an empty string for anchor.hash 92 | // even when href="#", so we also check the href 93 | var aHash = anchor.hash || (anchor.href.indexOf('#') > -1 ? '#' + anchor.href.split('#')[1] : null) 94 | var inPageHash 95 | 96 | // Window has no port, but anchor has the default port 97 | if (!wPort && aPort && (aPort === '80' || aPort === '443')) { 98 | // IE9 sometimes includes the default port (80 or 443) on anchor.host 99 | // so we append the default port to the window host in this case 100 | // so they will match for the host equality check [1] 101 | wHost += ':' + aPort 102 | aHost += aHost.indexOf(aPort, aHost.length - aPort.length) === -1 ? ':' + aPort : '' // [3] 103 | } 104 | 105 | // Hosts are the same, its a local link 106 | if (aHost === wHost) { 107 | // If everything else is the same 108 | // and hash exists, then it is an in-page hash [2] 109 | inPageHash = 110 | aPathname === wPathname && 111 | anchor.search === window.location.search && 112 | aHash 113 | 114 | if (lookForHash === true) { 115 | // If we are looking for the hash then this will 116 | // only return a truthy value if the link 117 | // is an *in-page* hash link 118 | return inPageHash 119 | } else { 120 | // If this is an in page hash link 121 | // then ignore it because we werent looking for hash links 122 | return inPageHash 123 | ? null 124 | : aPathname + (anchor.search || '') + (aHash || '') 125 | } 126 | } 127 | 128 | return null 129 | } 130 | 131 | // Take two arguments and return an ordered array of [event, anchor] 132 | function getEventAndAnchor (arg1, arg2) { 133 | var ev = null 134 | var anchor = null 135 | 136 | if (arguments.length === 2) { 137 | // Two arguments will come in this order 138 | ev = arg1 139 | anchor = arg2 140 | } else if (isHTMLElement(arg1)) { 141 | // If our first arg is an element 142 | // then use that as our anchor 143 | anchor = arg1 144 | } else { 145 | // Otherwise our argument is an event 146 | ev = arg1 147 | } 148 | 149 | // If there is no anchor, but we have an event 150 | // then use event.target 151 | if (!anchor && ev && ev.target) { 152 | anchor = ev.target 153 | } 154 | 155 | // Return an array so that it can be used with Function.apply 156 | return [ev, anchor] 157 | } 158 | 159 | // Functions to be used in exports. Defined here for alias purposes 160 | function pathname () { 161 | return isLocal.apply(null, getEventAndAnchor.apply(null, arguments)) 162 | } 163 | 164 | function hash () { 165 | return isLocal.apply(null, getEventAndAnchor.apply(null, arguments).concat(true)) 166 | } 167 | 168 | function active () { 169 | var args = Array.prototype.slice.call(arguments) 170 | var last = args[args.length - 1] 171 | var checkPath = window.location.pathname 172 | 173 | if (typeof last === 'string') { 174 | checkPath = last 175 | args = args.slice(0, -1) 176 | } 177 | 178 | return pathname.apply(null, args) === normalizeLeadingSlash(checkPath) 179 | } 180 | 181 | module.exports = { 182 | isLocal: isLocal, 183 | pathname: pathname, 184 | getLocalPathname: pathname, 185 | hash: hash, 186 | getLocalHash: hash, 187 | active: active, 188 | isActive: active 189 | } 190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local-links", 3 | "description": "Determine cross-browser if an event or anchor element should be handled locally.", 4 | "version": "1.4.1", 5 | "author": "Luke Karrys ", 6 | "bugs": { 7 | "url": "https://github.com/lukekarrys/local-links/issues" 8 | }, 9 | "devDependencies": { 10 | "browserify": "^14.5.0", 11 | "electron": "^1.7.10", 12 | "git-validate": "^2.2.2", 13 | "jquery": "^3.2.1", 14 | "lodash.partial": "^4.2.1", 15 | "run-browser": "^2.0.2", 16 | "standard": "^10.0.3", 17 | "tap-spec": "^4.1.1", 18 | "tape": "^4.8.0", 19 | "tape-run": "^3.0.1", 20 | "zuul": "^3.11.1", 21 | "zuul-ngrok": "^4.0.0" 22 | }, 23 | "homepage": "https://github.com/lukekarrys/local-links", 24 | "keywords": [ 25 | "IE", 26 | "links", 27 | "local" 28 | ], 29 | "license": "MIT", 30 | "main": "local-links.js", 31 | "pre-commit": [ 32 | "lint", 33 | "test", 34 | "validate" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git@github.com:lukekarrys/local-links.git" 39 | }, 40 | "scripts": { 41 | "clean": "rm .source.* || true", 42 | "lint": "standard", 43 | "start": "run-browser test/index.js", 44 | "start-80": "run-browser test/index.js --port 80", 45 | "test": "browserify test/index.js | tape-run -b electron | tap-spec && npm run clean", 46 | "test-travis": "npm test && npm run zuul", 47 | "validate": "npm ls", 48 | "zuul": "zuul --ui tape -- test/index.js", 49 | "zuul-local": "zuul --local 8080 --ui tape -- test/index.js" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var localLinks = require('../local-links') 3 | var jq = require('jquery') 4 | var partial = require('lodash.partial') 5 | var path = require('path') 6 | 7 | function $ (id) { 8 | return document.getElementById(id) 9 | } 10 | 11 | function e (id) { 12 | return {target: $(id)} 13 | } 14 | 15 | function setup () { 16 | var container = document.createElement('div') 17 | container.id = 'container' 18 | container.innerHTML = [ 19 | 'Local', 20 | 'Local2', 21 | 'Local3', 22 | 'Search', 23 | 'Relative', 24 | 'Global', 25 | 'Nested', 26 | 'Empty Hash', 27 | 'Hash', 28 | 'Out of Page hash', 29 | 'Global Hash', 30 | 'Active', 31 | 'No anchor', 32 | 'Local Blank', 33 | 'Local Blank Hash' 34 | ].join('') 35 | document.body.appendChild(container) 36 | } 37 | 38 | function triggerClick (el, modified, button) { 39 | var ev 40 | if (button === undefined) { 41 | button = 0 /* left */ 42 | } 43 | if (document.createEvent) { 44 | ev = document.createEvent('MouseEvent') 45 | ev.initMouseEvent( 46 | 'click', 47 | true /* bubble */, 48 | true /* cancelable */, 49 | window, null, 50 | 0, 0, 0, 0, /* coordinates */ 51 | !!modified, false, false, false, /* modifier keys */ 52 | button, 53 | null 54 | ) 55 | el.dispatchEvent(ev) 56 | } else if (document.createEventObject) { 57 | ev = document.createEventObject() 58 | ev.ctrlKey = !!modified 59 | ev.button = button 60 | el.dispatchEvent('onclick', ev) 61 | } 62 | } 63 | 64 | function attachClick (el, fn) { 65 | if (el.addEventListener) { 66 | el.addEventListener('click', fn, false) 67 | } else if (el.attachEvent) { 68 | el.attachEvent('onclick', fn) 69 | } 70 | } 71 | 72 | jq(function () { 73 | setup() 74 | 75 | var windowDir = path.dirname(window.location.pathname) 76 | 77 | function _pathnameTest (method, t) { 78 | var a = $('local') 79 | var search = $('local-search') 80 | var outHash = $('out-of-page-hash') 81 | var span = $('local-nested') 82 | var global = $('global') 83 | var relative = $('relative') 84 | var noAnchor = $('no-anchor') 85 | var localBlank = $('local-blank') 86 | 87 | t.plan(8) 88 | 89 | t.equal(localLinks[method](a), '/local/page/1') 90 | t.equal(localLinks[method](span), '/local/page/1') 91 | t.equal(localLinks[method](search), '/local/page/1?param=2') 92 | t.equal(localLinks[method](outHash), '/local/page/1#two') 93 | t.equal(localLinks[method](global), null) 94 | t.equal(localLinks[method](relative), (windowDir === '/' ? '' : windowDir) + '/page-2') 95 | t.equal(localLinks[method](noAnchor), null) 96 | t.equal(localLinks[method](localBlank), null) 97 | 98 | t.end() 99 | } 100 | // Use this test for both pathname alias functions 101 | test('HTML elements return pathname or null', partial(_pathnameTest, 'pathname')) 102 | test('HTML elements return pathname or null', partial(_pathnameTest, 'getLocalPathname')) 103 | 104 | test('Can be called with different context', function (t) { 105 | var pathname = localLinks.pathname 106 | var hash = localLinks.hash 107 | var active = localLinks.active 108 | t.plan(3) 109 | t.equal(pathname($('local')), '/local/page/1') 110 | t.equal(hash($('in-page-hash')), '#modal') 111 | t.equal(active($('active')), true) 112 | t.end() 113 | }) 114 | 115 | test('Works if the argument is an event with a target', function (t) { 116 | var ev = e('local') 117 | var nestedEvent = e('local-nested') 118 | var globalEvent = e('global') 119 | 120 | t.plan(3) 121 | 122 | t.equal(localLinks.pathname(ev), '/local/page/1') 123 | t.equal(localLinks.pathname(nestedEvent), '/local/page/1') 124 | t.equal(localLinks.pathname(globalEvent), null) 125 | 126 | t.end() 127 | }) 128 | 129 | test('Ignores modified events for a valid anchor', function (t) { 130 | var ev = e('local') 131 | ev.shiftKey = true 132 | 133 | t.plan(1) 134 | 135 | t.equal(localLinks.pathname(ev, $('local')), null) 136 | 137 | t.end() 138 | }) 139 | 140 | test('Will use anchor from second arg', function (t) { 141 | var globalEvent = e(global) 142 | var globalA = $('global') 143 | var localA = $('local-nested') 144 | 145 | t.plan(3) 146 | 147 | t.equal(localLinks.pathname(globalEvent, localA), '/local/page/1') 148 | t.equal(localLinks.pathname(globalA, localA), '/local/page/1') 149 | t.equal(localLinks.pathname(localA, globalA), null) 150 | 151 | t.end() 152 | }) 153 | 154 | function _hashTest (method, t) { 155 | var hash = $('in-page-hash') 156 | var emptyHash = $('empty-in-page-hash') 157 | var globalHash = $('global-hash') 158 | var targetBlankHash = $('local-blank-hash') 159 | 160 | t.plan(7) 161 | 162 | t.equal(localLinks.pathname(hash), null) 163 | t.equal(localLinks.pathname(emptyHash), null) 164 | t.equal(localLinks.pathname(targetBlankHash), null) 165 | 166 | t.equal(localLinks[method](hash), '#modal') 167 | t.equal(localLinks[method](emptyHash), '#') 168 | t.equal(localLinks[method](targetBlankHash), null) 169 | t.equal(localLinks[method](globalHash), null) 170 | 171 | t.end() 172 | } 173 | // Use this test for both hash alias functions 174 | test('Test hash links', partial(_hashTest, 'hash')) 175 | test('Test hash links', partial(_hashTest, 'getLocalHash')) 176 | 177 | function _activeTest (method, t) { 178 | t.plan(5) 179 | 180 | t.equal(localLinks[method]($('active')), true) 181 | t.equal(localLinks[method]($('global')), false) 182 | t.equal(localLinks[method]($('local')), false) 183 | t.equal(localLinks[method]($('local'), '/local/page/1'), true) 184 | t.equal(localLinks[method]($('in-page-hash')), false) 185 | 186 | t.end() 187 | } 188 | // Use this test for both active alias functions 189 | test('Active returns boolean based on current page', partial(_activeTest, 'active')) 190 | test('Active returns boolean based on current page', partial(_activeTest, 'isActive')) 191 | 192 | test('Return null for garbage', function (t) { 193 | t.plan(8) 194 | 195 | t.equal(localLinks.pathname(null), null) 196 | t.equal(localLinks.pathname($('whoops')), null) 197 | t.equal(localLinks.pathname({}, {}, {}, true), null) 198 | t.equal(localLinks.pathname('hey'), null) 199 | t.equal(localLinks.pathname(false), null) 200 | t.equal(localLinks.hash({}), null) 201 | t.equal(localLinks.hash('hey'), null) 202 | t.equal(localLinks.hash(false), null) 203 | 204 | t.end() 205 | }) 206 | 207 | test('Works on link clicks', function (t) { 208 | var local = $('local') 209 | var global = $('global') 210 | var plan = 2 211 | var count = 0 212 | var end = function () { 213 | count++ 214 | if (count === plan) { 215 | t.end() 216 | } 217 | } 218 | 219 | t.plan(plan) 220 | 221 | attachClick(local, function (event) { 222 | event.preventDefault() 223 | t.equal(localLinks.pathname(event), '/local/page/1') 224 | end() 225 | }) 226 | 227 | attachClick(global, function (event) { 228 | event.preventDefault() 229 | t.equal(localLinks.pathname(event), null) 230 | end() 231 | }) 232 | 233 | triggerClick(local) 234 | triggerClick(global) 235 | }) 236 | 237 | test('Works on modified link clicks', function (t) { 238 | var local = $('local2') 239 | var plan = 1 240 | var count = 0 241 | var end = function () { 242 | count++ 243 | if (count === plan) { 244 | t.end() 245 | } 246 | } 247 | 248 | t.plan(plan) 249 | 250 | attachClick(local, function (event) { 251 | event.preventDefault() 252 | t.equal(localLinks.pathname(event), null) 253 | end() 254 | }) 255 | 256 | triggerClick(local, true) 257 | }) 258 | 259 | test('Ignores middle clicks', function (t) { 260 | var local = $('local3') 261 | var plan = 1 262 | var count = 0 263 | var end = function () { 264 | count++ 265 | if (count === plan) { 266 | t.end() 267 | } 268 | } 269 | 270 | t.plan(plan) 271 | 272 | attachClick(local, function (event) { 273 | event.preventDefault() 274 | t.equal(localLinks.pathname(event), null, 'should ignore middle-button clicks') 275 | end() 276 | }) 277 | 278 | triggerClick(local, false, 1) 279 | }) 280 | }) 281 | --------------------------------------------------------------------------------