├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── package.json ├── tests ├── hash │ └── index.html ├── index.js ├── popstate │ └── index.html ├── preventdefault │ └── index.html ├── replace │ └── index.html ├── set │ └── index.html ├── trailing │ └── index.html ├── uichange │ └── index.html └── uri │ └── index.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '4' 10 | before_install: 11 | - npm i -g npm@^2.0.0 12 | before_script: 13 | - npm prune 14 | - npm start& 15 | script: 16 | - npm test 17 | after_success: 18 | - npm run semantic-release 19 | branches: 20 | except: 21 | - "/^v\\d+\\.\\d+\\.\\d+$/" 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cerebral 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue, assessing 9 | changes, and helping you finalize your pull requests. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](#bugs), 15 | [features requests](#features) and [submitting pull 16 | requests](#pull-requests), but please respect the following restrictions: 17 | 18 | * Please **do not** use the issue tracker for personal support requests. Use 19 | the [Cerebral Chat](https://discord.gg/0kIweV4bd2bwwsvH). 20 | 21 | * Please **do not** derail or troll issues. Keep the discussion on topic and 22 | respect the opinions of others. 23 | 24 | 25 | ## Bug reports 26 | 27 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 28 | Good bug reports are extremely helpful - thank you! 29 | 30 | Guidelines for bug reports: 31 | 32 | 1. **Use the GitHub issue search** — check if the issue has already been 33 | reported. 34 | 35 | 2. **Check if the issue has been fixed** — try to reproduce it using the 36 | latest `master` or `next` branch in the repository. 37 | 38 | 3. **Isolate the problem** — ideally create a reduced test case. 39 | 40 | A good bug report shouldn't leave others needing to chase you up for more 41 | information. Please try to be as detailed as possible in your report. What is 42 | your environment? What steps will reproduce the issue? What OS experiences the 43 | problem? What would you expect to be the outcome? All these details will help 44 | people to fix any potential bugs. 45 | 46 | Example: 47 | 48 | > Short and descriptive example bug report title 49 | > 50 | > A summary of the issue and the browser/OS environment in which it occurs. If 51 | > suitable, include the steps required to reproduce the bug. 52 | > 53 | > 1. This is the first step 54 | > 2. This is the second step 55 | > 3. Further steps, etc. 56 | > 57 | > `` - a link to the reduced test case 58 | > 59 | > Any other information you want to share that is relevant to the issue being 60 | > reported. This might include the lines of code that you have identified as 61 | > causing the bug, and potential solutions (and your opinions on their 62 | > merits). 63 | 64 | 65 | ## Feature requests 66 | 67 | Feature requests are welcome. But take a moment to find out whether your idea 68 | fits with the scope and aims of the project. It's up to *you* to make a strong 69 | case to convince the project's developers of the merits of this feature. Please 70 | provide as much detail and context as possible. 71 | 72 | 73 | ## Pull requests 74 | 75 | Good pull requests - patches, improvements, new features - are a fantastic 76 | help. They should remain focused in scope and avoid containing unrelated 77 | commits. 78 | 79 | **Please ask first** before embarking on any significant pull request (e.g. 80 | implementing features, refactoring code), otherwise you risk spending a lot of 81 | time working on something that the project's developers might not want to merge 82 | into the project. 83 | 84 | Please adhere to [JavaScript Standard Style](https://github.com/feross/standard) 85 | and any other requirements (such as test coverage). 86 | 87 | Adhering to the following this process is the best way to get your work 88 | included in the project: 89 | 90 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 91 | and configure the remotes: 92 | 93 | ```bash 94 | # Clone your fork of the repo into the current directory 95 | git clone https://github.com// 96 | # Navigate to the newly cloned directory 97 | cd 98 | # Assign the original repo to a remote called "upstream" 99 | git remote add upstream https://github.com/cerebral/ 100 | ``` 101 | 102 | 2. If you cloned a while ago, get the latest changes from upstream: 103 | 104 | ```bash 105 | git checkout master 106 | git pull upstream master 107 | ``` 108 | 109 | 3. Create a new topic branch (off the main project development branch) to 110 | contain your feature, change, or fix: 111 | 112 | ```bash 113 | git checkout -b 114 | ``` 115 | 116 | 4. Make sure to update, or add to the tests when appropriate. Patches and 117 | features will not be accepted without tests. Run `npm test` to check that 118 | all tests pass after you've made changes. 119 | 120 | 5. Commit your changes in logical chunks. Please adhere to these [git commit 121 | message guidelines][commit-message-conventions] 122 | or your code can't be merged into the main project. Use Git's 123 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 124 | feature to tidy up your commits before making them public. 125 | 126 | 6. Locally merge (or rebase) the upstream development branch into your topic branch: 127 | 128 | ```bash 129 | git pull [--rebase] upstream master 130 | ``` 131 | 132 | 7. Push your topic branch up to your fork: 133 | 134 | ```bash 135 | git push origin 136 | ``` 137 | 138 | 8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 139 | with a clear title and description. 140 | 141 | 9. If you are asked to amend your changes before they can be merged in, please 142 | use `git commit --amend` (or rebasing for multi-commit Pull Requests) and 143 | force push to your remote feature branch. You may also be asked to squash 144 | commits to follow our commit conventions, as they are used by 145 | [semantic-release](https://github.com/semantic-release/semantic-release) to 146 | automatically determine the new version and release to npm. In a nutshell: 147 | 148 | #### Commit Message Conventions 149 | 150 | - Commit test files with `test: ...` or `test(scope): ...` prefix 151 | - Commit bug fixes with `fix: ...` or `fix(scope): ...` prefix 152 | - Commit breaking changes by adding `BREAKING CHANGE: ` in the commit body 153 | (not the subject line) 154 | - Commit changes to `package.json`, `.gitignore` and other meta files with 155 | `chore(filenamewithoutext): ...` 156 | - Commit changes to README files or comments with `docs: ...` 157 | 158 | We follow [Angular’s Commit Message Conventions][commit-message-conventions]. 159 | But don’t worry about it, we are happy to help :) 160 | 161 | 162 | **IMPORTANT**: By submitting a patch, you agree to license your work under the 163 | same license as that used by the project. 164 | 171 | ## Maintainers 172 | 173 | If you have commit access, please follow this process for merging patches and cutting new releases. 174 | 175 | ### Reviewing changes 176 | 177 | 1. Check that a change is within the scope and philosophy of the component. 178 | 2. Check that a change has any necessary tests and a well formed, descriptive commit message. 179 | 3. Checkout the change and test it locally. 180 | 4. If the change is good, and authored by someone who cannot commit to 181 | `master`, please try to avoid using GitHub's merge button. Apply the change 182 | to `master` locally (feel free to amend any minor problems in the author's 183 | original commit if necessary). 184 | 5. If the change is good, and authored by another maintainer/collaborator, give 185 | them a "+1" comment and let them handle the merge. 186 | 187 | ### Submitting changes 188 | 189 | 1. All non-trivial changes should be put up for review using GitHub Pull 190 | Requests. 191 | 2. Your change should not be merged into `master` (or another feature branch), 192 | without at least one "+1" comment from another maintainer/collaborator 193 | on the project. 194 | 3. Try to avoid using GitHub's merge button. Locally rebase your change onto 195 | `master` and then push to GitHub. 196 | 4. Once a feature branch has been merged into its target branch, please delete 197 | the feature branch from the remote repository. 198 | 199 | [commit-message-conventions]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y 200 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christian Alfoni 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # addressbar 2 | Makes the addressbar of the browser work just like a normal input 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![Build status][travis-image]][travis-url] 6 | [![Commitizen friendly][commitizen-image]][commitizen-url] 7 | [![Semantic Release][semantic-release-image]][semantic-release-url] 8 | [![js-standard-style][standard-image]][standard-url] 9 | 10 | ## What is this thing? 11 | How would you handle URLs if the addressbar was just an input? An input you could listen to changes, `preventDefault()` on and manually set the value without any sideeffect? What if you could think about changing the url as an event in your app, which you reacted to, instead of letting a route library swallow your view layer and mess around with it in a strongly opinionated way? What if you could have the freedom to make the URL mean whatever you wanted? Not just changes in what views to display? 12 | 13 | The library just exposes the `addressbar`. It is a single entity in your app where you can: 14 | 15 | ```js 16 | // At http://www.example.com 17 | 18 | addressbar.value // "http://www.example.com" 19 | 20 | // Change addressbar value does NOT trigger route change 21 | addressbar.value = "http://wwww.example.com/test"; 22 | 23 | // You can force a replace of the url setting an object as value 24 | addressbar.value = { 25 | value: "http://www.example.com/test", 26 | replace: true 27 | }; 28 | 29 | // You have access to location properties 30 | addressbar.origin // "http://www.example.com" 31 | addressbar.port // "" 32 | addressbar.protocol // "http:" 33 | addressbar.hostname // "www.example.com" 34 | addressbar.pathname // "/" 35 | addressbar.hash // "" 36 | 37 | // Prevent route changes on hyperlinks 38 | addressbar.addEventListener('change', function (event) { 39 | event.preventDefault(); 40 | event.target.value // The value of the addressbar 41 | }); 42 | ``` 43 | 44 | This is low level code, so there is no routing logic here. Please check out [url-mapper](https://github.com/christianalfoni/url-mapper) which can be used to create routing logic. 45 | 46 | ## Under the hood 47 | Addressbar listens to `popstate` events and handles hyperlinks. It basically has logic to simulate how an input works, also handling a few edge cases. 48 | 49 | ## Tests 50 | Addressbar is running with selenium-driver and nodeunit to test live in Chrome. Requires [selenium chrome driver](https://sites.google.com/a/chromium.org/chromedriver/downloads) to be installed and added to **PATH**. 51 | 52 | Run tests: 53 | - `npm install` 54 | - `npm start` (fires up a python webservice) 55 | - `npm test` (Runs tests) 56 | 57 | [npm-image]: https://img.shields.io/npm/v/addressbar.svg?style=flat 58 | [npm-url]: https://npmjs.org/package/addressbar 59 | [travis-image]: https://img.shields.io/travis/cerebral/addressbar.svg?style=flat 60 | [travis-url]: https://travis-ci.org/cerebral/addressbar 61 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg 62 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 63 | [semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square 64 | [semantic-release-url]: https://github.com/semantic-release/semantic-release 65 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg 66 | [standard-url]: http://standardjs.com/ 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global history */ 2 | 3 | var URL = require('url-parse') 4 | var EventEmitter = require('eventemitter3').EventEmitter 5 | var instance = null 6 | 7 | // Check if IE history polyfill is added 8 | var location = window.history.location || window.location 9 | 10 | module.exports = (function () { 11 | if (instance) { 12 | return instance 13 | } 14 | 15 | var eventEmitter = new EventEmitter() 16 | 17 | eventEmitter.addEventListener = eventEmitter.addListener 18 | eventEmitter.removeEventListener = eventEmitter.removeListener 19 | 20 | var initialUrl = location.href 21 | var uri = URL(initialUrl) 22 | var origin = uri.protocol + '//' + uri.host 23 | var isPreventingDefault = false 24 | var doReplace = false 25 | var prevUrl = '' 26 | // var linkClicked = false 27 | var isEmitting = false 28 | var setSyncUrl = false 29 | 30 | var emitChange = function (url, event) { 31 | eventEmitter.emit('change', { 32 | preventDefault: function () { 33 | event && event.preventDefault() 34 | isPreventingDefault = true 35 | }, 36 | target: { 37 | value: url ? origin + url : location.href 38 | } 39 | }) 40 | } 41 | 42 | var onUrlChange = function (type) { 43 | return function (event) { 44 | if (location.href === prevUrl) { 45 | return 46 | } 47 | 48 | // Fixes bug where trailing slash is converted to normal url 49 | if (location.href[location.href.length - 1] === '/') { 50 | doReplace = true 51 | } 52 | 53 | isEmitting = true 54 | emitChange() 55 | isEmitting = false 56 | 57 | if (!setSyncUrl && isPreventingDefault) { 58 | history.replaceState({}, '', (prevUrl || initialUrl).replace(origin, '')) 59 | } 60 | 61 | prevUrl = location.href 62 | isPreventingDefault = false 63 | setSyncUrl = false 64 | doReplace = false 65 | } 66 | } 67 | 68 | // this hack resolves issue with safari 69 | // see issue from Page JS for reference https://github.com/visionmedia/page.js/issues/213 70 | // see also https://github.com/visionmedia/page.js/pull/240 71 | if (document.readyState !== 'complete') { 72 | // load event has not fired 73 | global.addEventListener('load', function () { 74 | setTimeout(function () { 75 | global.addEventListener('popstate', onUrlChange('pop'), false) 76 | }, 0) 77 | }, false) 78 | } else { 79 | // load event has fired 80 | global.addEventListener('popstate', onUrlChange('pop'), false) 81 | } 82 | 83 | Object.defineProperty(eventEmitter, 'value', { 84 | get: function () { 85 | return location.href 86 | }, 87 | set: function (value) { 88 | if (typeof value !== 'string') { 89 | doReplace = Boolean(value.replace) 90 | value = value.value 91 | } 92 | 93 | // If emitting a change we flag that we are setting 94 | // a url based on the event being emitted 95 | if (isEmitting) { 96 | setSyncUrl = true 97 | } 98 | 99 | // Ensure full url 100 | if (value.indexOf(origin) === -1) { 101 | value = origin + value 102 | } 103 | 104 | // If it is same url, forget about it 105 | if (value === location.href) { 106 | return 107 | } 108 | 109 | // We might need to replace the url if we are fixing 110 | // for example trailing slash issue 111 | if (doReplace) { 112 | history.replaceState({}, '', value.replace(origin, '')) 113 | doReplace = false 114 | } else { 115 | history.pushState({}, '', value.replace(origin, '')) 116 | } 117 | 118 | prevUrl = location.href 119 | isPreventingDefault = false 120 | } 121 | }) 122 | 123 | // expose URLUtils like API https://developer.mozilla.org/en-US/docs/Web/API/URLUtils 124 | // thanks https://github.com/cofounders/urlutils for reference 125 | Object.defineProperty(eventEmitter, 'origin', { 126 | get: function () { 127 | var uri = URL(location.href) 128 | return uri.protocol + '//' + uri.host 129 | } 130 | }) 131 | 132 | Object.defineProperty(eventEmitter, 'protocol', { 133 | get: function () { 134 | return URL(location.href).protocol 135 | } 136 | }) 137 | 138 | Object.defineProperty(eventEmitter, 'port', { 139 | get: function () { 140 | return URL(location.href).port 141 | } 142 | }) 143 | 144 | Object.defineProperty(eventEmitter, 'hostname', { 145 | get: function () { 146 | return URL(location.href).hostname 147 | } 148 | }) 149 | 150 | Object.defineProperty(eventEmitter, 'pathname', { 151 | get: function () { 152 | return URL(location.href).pathname 153 | } 154 | }) 155 | 156 | Object.defineProperty(eventEmitter, 'hash', { 157 | get: function () { 158 | return URL(location.href).hash 159 | } 160 | }) 161 | 162 | /* 163 | This code is from the Page JS source code. Amazing work on handling all 164 | kinds of scenarios with hyperlinks, thanks! 165 | */ 166 | 167 | var isSameOrigin = function (href) { 168 | return (href && (href.indexOf(origin) === 0)) 169 | } 170 | 171 | var getClickedHref = function (event) { 172 | // check which button 173 | if ((event.which === null ? event.button : event.which) !== 1) { return false } 174 | 175 | // check for modifiers 176 | if (event.metaKey || event.ctrlKey || event.shiftKey) { return false } 177 | if (event.defaultPrevented) { return false } 178 | 179 | // ensure link 180 | var element = event.target 181 | while (element && element.nodeName !== 'A') { element = element.parentNode } 182 | if (!element || element.nodeName !== 'A') { return false } 183 | 184 | // Ignore if tag has 185 | // 1. "download" attribute 186 | // 2. rel="external" attribute 187 | if (element.hasAttribute('download') || element.getAttribute('rel') === 'external') { return false } 188 | 189 | // Check for mailto: in the href 190 | var href = element.getAttribute('href') 191 | if (href && href.indexOf('mailto:') > -1) { return false } 192 | 193 | // check target 194 | if (element.target) { return false } 195 | 196 | // x-origin 197 | if (!isSameOrigin(element.href)) { return false } 198 | 199 | return href 200 | } 201 | 202 | global.addEventListener(document.ontouchstart ? 'touchstart' : 'click', function (event) { 203 | var href = getClickedHref(event) 204 | if (href) { 205 | // linkClicked = true 206 | isEmitting = true 207 | emitChange(href, event) 208 | isEmitting = false 209 | if (isPreventingDefault) { 210 | // linkClicked = false 211 | } 212 | prevUrl = href 213 | isPreventingDefault = false 214 | } 215 | }) 216 | 217 | instance = eventEmitter 218 | 219 | return eventEmitter 220 | }()) 221 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "addressbar", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Makes the addressbar of the browser work just like a normal input", 5 | "main": "index.js", 6 | "scripts": { 7 | "prestart": "npm run build", 8 | "start": "python -m SimpleHTTPServer 3001", 9 | "pretest": "npm run lint && npm run build", 10 | "test": "nodeunit tests/index.js", 11 | "build": "webpack", 12 | "lint": "standard", 13 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/cerebral/addressbar.git" 18 | }, 19 | "keywords": [ 20 | "router", 21 | "history", 22 | "url", 23 | "reactive" 24 | ], 25 | "author": "Christian Alfoni ", 26 | "contributors": [ 27 | "Aleksey Guryanov " 28 | ], 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/cerebral/addressbar/issues" 32 | }, 33 | "homepage": "https://github.com/cerebral/addressbar#readme", 34 | "devDependencies": { 35 | "commitizen": "^2.5.0", 36 | "cz-customizable": "^2.7.0", 37 | "ghooks": "^1.0.3", 38 | "nodeunit": "^0.9.1", 39 | "selenium-webdriver": "^2.46.1", 40 | "semantic-release": "^4.3.5", 41 | "standard": "^5.4.1", 42 | "validate-commit-msg": "^1.0.0", 43 | "webpack": "^1.12.1" 44 | }, 45 | "dependencies": { 46 | "eventemitter3": "^2.0.2", 47 | "url-parse": "^1.0.5" 48 | }, 49 | "config": { 50 | "commitizen": { 51 | "path": "node_modules/cz-customizable" 52 | }, 53 | "ghooks": { 54 | "commit-msg": "validate-commit-msg" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/hash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Messages 6 | Message 123 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | var webdriver = require('selenium-webdriver') 2 | var by = webdriver.By 3 | var baseUrl = 'http://localhost:3001/' 4 | var preventUrl = baseUrl + 'tests/preventdefault/' 5 | var setUrl = baseUrl + 'tests/set/' 6 | var popstateUrl = baseUrl + 'tests/popstate/' 7 | var hashUrl = baseUrl + 'tests/hash/' 8 | var trailingUrl = baseUrl + 'tests/trailing/' 9 | var uichange = baseUrl + 'tests/uichange/' 10 | var replace = baseUrl + 'tests/replace/' 11 | var uri = baseUrl + 'tests/uri/' 12 | 13 | exports['should display current url'] = function (test) { 14 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 15 | driver.get(preventUrl) 16 | driver.getCurrentUrl().then(function (url) { 17 | test.equal(url, preventUrl) 18 | }) 19 | driver.quit().then(test.done) 20 | } 21 | 22 | exports['should not allow going to a new url'] = function (test) { 23 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 24 | driver.get(preventUrl) 25 | driver.navigate().to(preventUrl + '#/foo') 26 | driver.getCurrentUrl().then(function (url) { 27 | test.equal(url, preventUrl) 28 | }) 29 | driver.quit().then(test.done) 30 | } 31 | 32 | exports['should set url manually when prevented'] = function (test) { 33 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 34 | driver.get(setUrl) 35 | driver.navigate().to(setUrl + '#/foo') 36 | driver.getCurrentUrl().then(function (url) { 37 | test.equal(url, setUrl + '#/foo') 38 | }) 39 | driver.quit().then(test.done) 40 | } 41 | 42 | exports['should go to popstate url'] = function (test) { 43 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 44 | driver.get(popstateUrl) 45 | 46 | driver.findElement(by.id('messages')).click() 47 | driver.getCurrentUrl().then(function (url) { 48 | test.equal(url, baseUrl + 'messages') 49 | }) 50 | 51 | driver.findElement(by.id('message')).click() 52 | driver.getCurrentUrl().then(function (url) { 53 | test.equal(url, baseUrl + 'messages/123') 54 | }) 55 | 56 | driver.quit().then(test.done) 57 | } 58 | 59 | exports['should handle back and forward with popstate'] = function (test) { 60 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 61 | driver.get(popstateUrl) 62 | 63 | driver.findElement(by.id('messages')).click() 64 | driver.findElement(by.id('message')).click() 65 | driver.getCurrentUrl().then(function (url) { 66 | test.equal(url, baseUrl + 'messages/123') 67 | }) 68 | 69 | driver.navigate().back() 70 | driver.getCurrentUrl().then(function (url) { 71 | test.equal(url, baseUrl + 'messages') 72 | }) 73 | 74 | driver.navigate().forward() 75 | driver.getCurrentUrl().then(function (url) { 76 | test.equal(url, baseUrl + 'messages/123') 77 | }) 78 | 79 | driver.quit().then(test.done) 80 | } 81 | 82 | exports['should go to hash url'] = function (test) { 83 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 84 | driver.get(hashUrl) 85 | 86 | driver.findElement(by.id('messages')).click() 87 | driver.getCurrentUrl().then(function (url) { 88 | test.equal(url, baseUrl + '#/messages') 89 | }) 90 | 91 | driver.findElement(by.id('message')).click() 92 | driver.getCurrentUrl().then(function (url) { 93 | test.equal(url, baseUrl + '#/messages/123') 94 | }) 95 | 96 | driver.quit().then(test.done) 97 | } 98 | 99 | exports['should handle back and forward with hash'] = function (test) { 100 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 101 | driver.get(hashUrl) 102 | 103 | driver.findElement(by.id('messages')).click() 104 | driver.findElement(by.id('message')).click() 105 | driver.getCurrentUrl().then(function (url) { 106 | test.equal(url, baseUrl + '#/messages/123') 107 | }) 108 | 109 | driver.navigate().back() 110 | driver.getCurrentUrl().then(function (url) { 111 | test.equal(url, baseUrl + '#/messages') 112 | }) 113 | 114 | driver.navigate().forward() 115 | driver.getCurrentUrl().then(function (url) { 116 | test.equal(url, baseUrl + '#/messages/123') 117 | }) 118 | 119 | driver.quit().then(test.done) 120 | } 121 | 122 | exports['should handle trailing slash convertion'] = function (test) { 123 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 124 | driver.get(trailingUrl + '#/messages/123') 125 | driver.navigate().to(trailingUrl + '#/messages/') 126 | driver.getCurrentUrl().then(function (url) { 127 | test.equal(url, trailingUrl + '#/messages') 128 | }) 129 | 130 | driver.navigate().back() 131 | driver.getCurrentUrl().then(function (url) { 132 | test.equal(url, trailingUrl + '#/messages/123') 133 | }) 134 | 135 | driver.navigate().forward() 136 | driver.getCurrentUrl().then(function (url) { 137 | test.equal(url, trailingUrl + '#/messages') 138 | }) 139 | 140 | driver.quit().then(test.done) 141 | } 142 | 143 | exports['should resume history when changing url when on "back" url'] = function (test) { 144 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 145 | driver.get(hashUrl) 146 | 147 | driver.findElement(by.id('messages')).click() 148 | driver.findElement(by.id('message')).click() 149 | driver.getCurrentUrl().then(function (url) { 150 | test.equal(url, baseUrl + '#/messages/123') 151 | }) 152 | 153 | driver.navigate().back() 154 | driver.getCurrentUrl().then(function (url) { 155 | test.equal(url, baseUrl + '#/messages') 156 | }) 157 | 158 | driver.navigate().to(baseUrl + '#/messages/456') 159 | driver.navigate().forward() 160 | driver.getCurrentUrl().then(function (url) { 161 | test.equal(url, baseUrl + '#/messages/456') 162 | }) 163 | 164 | driver.quit().then(test.done) 165 | } 166 | 167 | exports['should be able to go forward and backwards twice'] = function (test) { 168 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 169 | driver.get(uichange) 170 | 171 | driver.findElement(by.id('messages')).click() 172 | driver.getCurrentUrl().then(function (url) { 173 | test.equal(url, baseUrl + '#/messages') 174 | }) 175 | driver.findElement(by.id('url')).getText().then(function (text) { 176 | test.equal(text, baseUrl + '#/messages') 177 | }) 178 | 179 | driver.navigate().back() 180 | driver.getCurrentUrl().then(function (url) { 181 | test.equal(url, baseUrl + 'tests/uichange/') 182 | }) 183 | driver.findElement(by.id('url')).getText().then(function (text) { 184 | test.equal(text, baseUrl + 'tests/uichange/') 185 | }) 186 | 187 | driver.findElement(by.id('messages')).click() 188 | driver.getCurrentUrl().then(function (url) { 189 | test.equal(url, baseUrl + '#/messages') 190 | }) 191 | driver.findElement(by.id('url')).getText().then(function (text) { 192 | test.equal(text, baseUrl + '#/messages') 193 | }) 194 | 195 | driver.navigate().back() 196 | driver.getCurrentUrl().then(function (url) { 197 | test.equal(url, baseUrl + 'tests/uichange/') 198 | }) 199 | driver.findElement(by.id('url')).getText().then(function (text) { 200 | test.equal(text, baseUrl + 'tests/uichange/') 201 | }) 202 | 203 | driver.quit().then(test.done) 204 | } 205 | 206 | exports['should be able to move back and forward with mix of url and setting manually and still use back button'] = function (test) { 207 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 208 | driver.get(uichange) 209 | 210 | driver.findElement(by.id('home')).click() 211 | driver.getCurrentUrl().then(function (url) { 212 | test.equal(url, baseUrl + '#/') 213 | }) 214 | driver.findElement(by.id('url')).getText().then(function (text) { 215 | test.equal(text, baseUrl + '#/') 216 | }) 217 | 218 | driver.findElement(by.id('messages')).click() 219 | driver.getCurrentUrl().then(function (url) { 220 | test.equal(url, baseUrl + '#/messages') 221 | }) 222 | driver.findElement(by.id('url')).getText().then(function (text) { 223 | test.equal(text, baseUrl + '#/messages') 224 | }) 225 | 226 | driver.findElement(by.id('home')).click() 227 | driver.getCurrentUrl().then(function (url) { 228 | test.equal(url, baseUrl + '#/') 229 | }) 230 | driver.findElement(by.id('url')).getText().then(function (text) { 231 | test.equal(text, baseUrl + '#/') 232 | }) 233 | 234 | driver.findElement(by.id('messages')).click() 235 | driver.getCurrentUrl().then(function (url) { 236 | test.equal(url, baseUrl + '#/messages') 237 | }) 238 | driver.findElement(by.id('url')).getText().then(function (text) { 239 | test.equal(text, baseUrl + '#/messages') 240 | }) 241 | 242 | driver.navigate().back() 243 | driver.getCurrentUrl().then(function (url) { 244 | test.equal(url, baseUrl + '#/') 245 | }) 246 | driver.findElement(by.id('url')).getText().then(function (text) { 247 | test.equal(text, baseUrl + '#/') 248 | }) 249 | 250 | driver.navigate().back() 251 | driver.getCurrentUrl().then(function (url) { 252 | test.equal(url, baseUrl + '#/messages') 253 | }) 254 | driver.findElement(by.id('url')).getText().then(function (text) { 255 | test.equal(text, baseUrl + '#/messages') 256 | }) 257 | 258 | driver.navigate().back() 259 | driver.getCurrentUrl().then(function (url) { 260 | test.equal(url, baseUrl + '#/') 261 | }) 262 | driver.findElement(by.id('url')).getText().then(function (text) { 263 | test.equal(text, baseUrl + '#/') 264 | }) 265 | 266 | driver.navigate().back() 267 | driver.getCurrentUrl().then(function (url) { 268 | test.equal(url, baseUrl + 'tests/uichange/') 269 | }) 270 | driver.findElement(by.id('url')).getText().then(function (text) { 271 | test.equal(text, baseUrl + 'tests/uichange/') 272 | }) 273 | 274 | driver.quit().then(test.done) 275 | } 276 | 277 | exports['should be able to replace the set url'] = function (test) { 278 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 279 | driver.get(replace) 280 | 281 | driver.findElement(by.id('home')).click() 282 | driver.getCurrentUrl().then(function (url) { 283 | test.equal(url, baseUrl + '#/home') 284 | }) 285 | 286 | driver.findElement(by.id('messagesReplace')).click() 287 | driver.getCurrentUrl().then(function (url) { 288 | test.equal(url, baseUrl + '#/messages') 289 | }) 290 | 291 | driver.navigate().back() 292 | driver.getCurrentUrl().then(function (url) { 293 | test.equal(url, 'http://localhost:3001/tests/replace/') 294 | }) 295 | driver.findElement(by.id('url')).getText().then(function (text) { 296 | test.equal(text, 'http://localhost:3001/tests/replace/') 297 | }) 298 | 299 | driver.findElement(by.id('home')).click() 300 | driver.getCurrentUrl().then(function (url) { 301 | test.equal(url, baseUrl + '#/home') 302 | }) 303 | 304 | driver.findElement(by.id('messages')).click() 305 | driver.getCurrentUrl().then(function (url) { 306 | test.equal(url, baseUrl + '#/messages') 307 | }) 308 | 309 | driver.navigate().back() 310 | driver.getCurrentUrl().then(function (url) { 311 | test.equal(url, 'http://localhost:3001/#/home') 312 | }) 313 | driver.findElement(by.id('url')).getText().then(function (text) { 314 | test.equal(text, 'http://localhost:3001/#/home') 315 | }) 316 | 317 | driver.quit().then(test.done) 318 | } 319 | 320 | exports['should expose origin, protocol, port and hostname as properties'] = function (test) { 321 | var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.phantomjs()).build() 322 | driver.get(uri) 323 | 324 | driver.getCurrentUrl().then(function (url) { 325 | test.equal(url, 'http://localhost:3001/tests/uri/') 326 | }) 327 | driver.findElement(by.id('origin')).getText().then(function (text) { 328 | test.equal(text, 'http://localhost:3001') 329 | }) 330 | driver.findElement(by.id('protocol')).getText().then(function (text) { 331 | test.equal(text, 'http:') 332 | }) 333 | driver.findElement(by.id('port')).getText().then(function (text) { 334 | test.equal(text, '3001') 335 | }) 336 | driver.findElement(by.id('hostname')).getText().then(function (text) { 337 | test.equal(text, 'localhost') 338 | }) 339 | driver.findElement(by.id('pathname')).getText().then(function (text) { 340 | test.equal(text, '/tests/uri/') 341 | }) 342 | driver.findElement(by.id('hash')).getText().then(function (text) { 343 | test.equal(text, '') 344 | }) 345 | 346 | driver.findElement(by.id('messages')).click() 347 | driver.findElement(by.id('pathname')).getText().then(function (text) { 348 | test.equal(text, '/') 349 | }) 350 | driver.findElement(by.id('hash')).getText().then(function (text) { 351 | test.equal(text, '#/messages') 352 | }) 353 | 354 | driver.quit().then(test.done) 355 | } 356 | -------------------------------------------------------------------------------- /tests/popstate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Messages 6 | Message 123 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/preventdefault/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/replace/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 18 | 19 |
20 | 21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/set/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/trailing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/uichange/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | Home 12 | Messages 13 |
14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/uri/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | messages 12 | 13 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var buildPath = path.resolve(__dirname, 'build') 3 | 4 | var config = { 5 | context: __dirname, 6 | devtool: 'eval-source-map', 7 | entry: [ 8 | path.resolve(__dirname, 'index.js')], 9 | output: { 10 | path: buildPath, 11 | filename: 'addressbar.js', 12 | libraryTarget: 'umd', 13 | library: 'addressbar' 14 | } 15 | } 16 | 17 | module.exports = config 18 | --------------------------------------------------------------------------------