diff --git a/README.md b/README.md index 891af08..ba45286 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The library just exposes the `addressbar`. It is a single entity in your app whe addressbar.value // "http://www.example.com" // Change addressbar value does NOT trigger route change -addressbar.value = "http://wwww.example.com/test"; +addressbar.value = "http://wwww.example.com/test"; // Prevent route changes on hyperlinks addressbar.addEventListener('change', function (event) { @@ -21,4 +21,15 @@ addressbar.addEventListener('change', function (event) { }); ``` -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. +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. + +## Under the hood +Addressbar listens to `popstate` events and handles hyperlinks. It basically has logic to simulate how an input works, also handling a few edge cases. + +## Tests +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**. + +Run tests: +- `npm install` +- `npm start` (fires up a python webservice) +- `npm test` (Runs tests) diff --git a/build/index.html b/build/index.html deleted file mode 100644 index 65a1220..0000000 --- a/build/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - -
- - - - - - - - - diff --git a/index.js b/index.js index 931bcff..714a665 100644 --- a/index.js +++ b/index.js @@ -13,10 +13,9 @@ module.exports = (function () { var origin = location.origin; var isPreventingDefault = false; - var isSilent = false; - var index = 0; var doReplace = false; - var prevUrl = null; + var initialUrl = location.href; + var prevUrl = ''; var linkClicked = false; var isEmitting = false; var setSyncUrl = false; @@ -33,21 +32,15 @@ module.exports = (function () { }); }; - var hasHash = function (url) { - return location.href.indexOf('#') !== -1; - }; - var onUrlChange = function (type) { return function (event) { - console.log('got event', type, location.href, prevUrl); if (location.href === prevUrl) { - console.log('retuning!'); return; } - // If going back or if using trailing slash - if (event.state && event.state.index < index || location.href[location.href.length - 1] === '/') { + // Fixes bug where trailing slash is converted to normal url + if (location.href[location.href.length - 1] === '/') { doReplace = true; } @@ -55,11 +48,10 @@ module.exports = (function () { emitChange(); isEmitting = false; - if (!setSyncUrl) { - history.replaceState({url: prevUrl, index: index}, '', prevUrl.replace(origin, '')); + if (!setSyncUrl && isPreventingDefault) { + history.replaceState({}, '', (prevUrl || initialUrl).replace(origin, '')); } - isSilent = false; prevUrl = location.href; isPreventingDefault = false; setSyncUrl = false; @@ -76,27 +68,29 @@ module.exports = (function () { }, set: function (value) { + // If emitting a change we flag that we are setting + // a url based on the event being emitted if (isEmitting) { setSyncUrl = true; } + // Ensure full url if (value.indexOf(origin) === -1) { value = origin + value; } + // If it is same url, forget about it if (value === location.href) { return; } - if (isPreventingDefault && !isEmitting && !linkClicked) { - return; - } - - if (!doReplace) { - history.pushState({url: value, index: index++}, '', value.replace(origin, '')); - } else { - history.replaceState({url: value, index: index}, '', value.replace(origin, '')); + // We might need to replace the url if we are fixing + // for example trailing slash issue + if (doReplace) { + history.replaceState({}, '', value.replace(origin, '')); doReplace = false; + } else { + history.pushState({}, '', value.replace(origin, '')); } isPreventingDefault = false; @@ -104,6 +98,11 @@ module.exports = (function () { } }); + /* + This code is from the Page JS source code. Amazing work on handling all + kinds of scenarios with hyperlinks, thanks! + */ + var isSameOrigin = function (href) { var origin = location.protocol + '//' + location.hostname; if (location.port) origin += ':' + location.port; @@ -151,7 +150,6 @@ module.exports = (function () { if (isPreventingDefault) { linkClicked = false; } - isSilent = false; prevUrl = href; isPreventingDefault = false; } diff --git a/package.json b/package.json index bd37a5d..9b33ae7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "addressbar", - "version": "0.1.23", + "version": "0.2.0", "description": "Makes the addressbar of the browser work just like a normal input", "main": "index.js", "scripts": { - "start": "webpack-dev-server --devtool eval --progress --colors --content-base build", - "test": "echo \"Error: no test specified\" && exit 1" + "start": "python -m SimpleHTTPServer 3001", + "test": "webpack && nodeunit test.js" }, "repository": { "type": "git", @@ -23,7 +23,8 @@ }, "homepage": "https://github.com/christianalfoni/addressbar#readme", "devDependencies": { - "webpack": "^1.12.0", - "webpack-dev-server": "^1.10.1" + "nodeunit": "^0.9.1", + "selenium-webdriver": "^2.46.1", + "webpack": "^1.12.1" } } diff --git a/test.js b/test.js new file mode 100644 index 0000000..b15e864 --- /dev/null +++ b/test.js @@ -0,0 +1,198 @@ +var webdriver = require('selenium-webdriver'); +var by = webdriver.By; +var baseUrl = 'http://localhost:3001/'; +var preventUrl = baseUrl + 'tests/preventdefault/'; +var setUrl = baseUrl + 'tests/set/'; +var popstateUrl = baseUrl + 'tests/popstate/'; +var hashUrl = baseUrl + 'tests/hash/'; +var trailingUrl = baseUrl + 'tests/trailing/'; + +exports['should display current url'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(preventUrl); + driver.getCurrentUrl().then(function (url) { + test.equal(url, preventUrl); + }); + driver.quit().then(test.done); + +}; + +exports['should not allow going to a new url'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(preventUrl); + driver.navigate().to(preventUrl + '#/foo'); + driver.getCurrentUrl().then(function (url) { + test.equal(url, preventUrl); + }); + driver.quit().then(test.done); + +}; + +exports['should set url manually when prevented'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(setUrl); + driver.navigate().to(setUrl + '#/foo'); + driver.getCurrentUrl().then(function (url) { + test.equal(url, setUrl + '#/foo'); + }); + driver.quit().then(test.done); + +}; + +exports['should go to popstate url'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(popstateUrl); + + driver.findElement(by.id('messages')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + 'messages'); + }); + + driver.findElement(by.id('message')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + 'messages/123'); + }); + + driver.quit().then(test.done); + +}; + +exports['should handle back and forward with popstate'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(popstateUrl); + + driver.findElement(by.id('messages')).click(); + driver.findElement(by.id('message')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + 'messages/123'); + }); + + driver.navigate().back(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + 'messages'); + }); + + driver.navigate().forward(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + 'messages/123'); + }); + + driver.quit().then(test.done); + +}; + +exports['should go to hash url'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(hashUrl); + + driver.findElement(by.id('messages')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages'); + }); + + driver.findElement(by.id('message')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages/123'); + }); + + driver.quit().then(test.done); + +}; + +exports['should handle back and forward with hash'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(hashUrl); + + driver.findElement(by.id('messages')).click(); + driver.findElement(by.id('message')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages/123'); + }); + + driver.navigate().back(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages'); + }); + + driver.navigate().forward(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages/123'); + }); + + driver.quit().then(test.done); + +}; + +exports['should handle trailing slash convertion'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(trailingUrl + '#/messages/123'); + driver.navigate().to(trailingUrl + '#/messages/'); + driver.getCurrentUrl().then(function (url) { + test.equal(url, trailingUrl + '#/messages'); + }); + + driver.navigate().back(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, trailingUrl + '#/messages/123'); + }); + + driver.navigate().forward(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, trailingUrl + '#/messages'); + }); + + driver.quit().then(test.done); + +}; + +exports['should resume history when changing url when on "back" url'] = function (test) { + + var driver = new webdriver.Builder(). + withCapabilities(webdriver.Capabilities.chrome()). + build(); + driver.get(hashUrl); + + driver.findElement(by.id('messages')).click(); + driver.findElement(by.id('message')).click(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages/123'); + }); + + driver.navigate().back(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages'); + }); + + driver.navigate().to(baseUrl + '#/messages/456'); + driver.navigate().forward(); + driver.getCurrentUrl().then(function (url) { + test.equal(url, baseUrl + '#/messages/456'); + }); + + driver.quit().then(test.done); + +}; diff --git a/tests/hash/index.html b/tests/hash/index.html new file mode 100644 index 0000000..1ec4e8c --- /dev/null +++ b/tests/hash/index.html @@ -0,0 +1,17 @@ + + + + + Messages + Message 123 + + + + diff --git a/tests/popstate/index.html b/tests/popstate/index.html new file mode 100644 index 0000000..d2446ec --- /dev/null +++ b/tests/popstate/index.html @@ -0,0 +1,15 @@ + + + + + Messages + Message 123 + + + + diff --git a/tests/preventdefault/index.html b/tests/preventdefault/index.html new file mode 100644 index 0000000..43d5011 --- /dev/null +++ b/tests/preventdefault/index.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/tests/set/index.html b/tests/set/index.html new file mode 100644 index 0000000..4eee340 --- /dev/null +++ b/tests/set/index.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/tests/trailing/index.html b/tests/trailing/index.html new file mode 100644 index 0000000..5dc9fe8 --- /dev/null +++ b/tests/trailing/index.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/webpack.config.js b/webpack.config.js index c4dd8d7..20639ae 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,12 +1,16 @@ +var Webpack = require('webpack'); var path = require('path'); -var node_modules = path.resolve(__dirname, 'node_modules'); +var nodeModulesPath = path.resolve(__dirname, 'node_modules'); var buildPath = path.resolve(__dirname, 'build'); var config = { - entry: path.resolve(__dirname, 'index.js'), + context: __dirname, devtool: 'eval-source-map', + entry: [ + path.resolve(__dirname, 'index.js')], output: { - filename: 'bundle.js', + path: buildPath, + filename: 'addressbar.js', libraryTarget: 'umd', library: 'addressbar' }