├── .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 | [](https://nodei.co/npm/local-links/)
7 | [](https://travis-ci.org/lukekarrys/local-links)
8 | [](https://greenkeeper.io/)
9 | [](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 |
--------------------------------------------------------------------------------