diff --git a/.travis.yml b/.travis.yml index c2c8b1e..02b1ac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,7 @@ node_js: before_install: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" -script: "npm test && karma start karma.conf-ci.js" \ No newline at end of file +script: "npm test" +env: + global: + - SAUCE_USERNAME=cherrytree diff --git a/CHANGELOG.md b/CHANGELOG.md index 15bc789..9ba043d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Make params, query and route array immutable between transitions, i.e. modifying those directly on the transition only affects that transition * Replace `paramNames` with `params` in the route descriptor * Drop the `ancestors` attribute from the route descriptor +* Drop out of the box support for ES3 environments (IE8), to use Cherrytree - es5 polyfills for native `map`, `reduce` and `forEach` need to be used now. This was possible always the case since usage of babel requires an es5 environment. ### v2.0.0-alpha.12 diff --git a/README.md b/README.md index 9d1eb53..49f259d 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,18 @@ In an AMD environment, require the standalone UMD build - this version has all o require('cherrytree/standalone') +# Browser Support + +[![Sauce Test Status](https://saucelabs.com/browser-matrix/cherrytree.svg)](https://saucelabs.com/u/cherrytree) + +Cherrytree works in all modern browsers. It requires es5 environment and es6 promises. Use polyfills for those if you have to support older browsers, e.g.: + +* https://github.com/es-shims/es5-shim +* https://github.com/jakearchibald/es6-promise + # Size -The size excluding all deps is ~10.96 kB gzipped and the standalone build with all deps is ~12.82 kB gzipped. +The size excluding all deps is ~4.9kB gzipped and the standalone build with all deps is ~8.7kB gzipped. # Usage diff --git a/karma.conf-ci.js b/karma.conf-ci.js index c2aa1d8..5160677 100644 --- a/karma.conf-ci.js +++ b/karma.conf-ci.js @@ -1,6 +1,8 @@ var fs = require('fs') -var _ = require('lodash') var config = require('./karma.conf').config +var yargs = require('yargs') + +var browsers = (yargs.argv.b || '').split(',') // Use ENV vars on CI and sauce.json locally to get credentials if (!process.env.SAUCE_USERNAME) { @@ -14,45 +16,39 @@ if (!process.env.SAUCE_USERNAME) { } } -var customLaunchers = { - 'SL_Chrome': { - base: 'SauceLabs', - browserName: 'chrome' - }, - 'SL_Firefox': { - base: 'SauceLabs', - browserName: 'firefox' - }, - 'SL_Safari': { - base: 'SauceLabs', - browserName: 'safari', - platform: 'OS X 10.9', - version: '7' - }, - 'SL_IE_9': { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 2008', - version: '9' - }, - 'SL_IE_10': { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 2012', - version: '10' - }, - 'SL_IE_11': { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 8.1', - version: '11' +var platforms = [ + ['android', '5.1', 'Linux'], + ['chrome', '32', 'Windows 8.1'], + ['chrome', '43', 'Linux'], + ['chrome', 'beta', 'OS X 10.11'], + ['firefox', '26', 'Windows 8.1'], + ['firefox', '40', 'Windows 8.1'], + ['safari', '6', 'OS X 10.8'], + ['safari', '7', 'OS X 10.9'], + ['internet explorer', '9', 'Windows 7'], + ['internet explorer', '10', 'Windows 8'], + ['internet explorer', '11', 'Windows 8.1'] +] + +var customLaunchers = platforms.reduce(function (memo, platform, i) { + if (!browsers || browsers.indexOf(platform[0]) > -1) { + memo['SL_' + i + '_' + platform[0] + platform[1]] = { + base: 'SauceLabs', + platform: platform[2], + browserName: platform[0], + version: platform[1] + } } -} + return memo +}, {}) module.exports = function (c) { - c.set(_.extend(config, { + c.set(Object.assign(config, { sauceLabs: { - testName: 'Cherrytree Tests' + testName: 'Cherrytree', + build: process.env.CI_BUILD_NUMBER, + recordVideo: false, + recordScreenshots: false }, customLaunchers: customLaunchers, browsers: Object.keys(customLaunchers), @@ -60,7 +56,7 @@ module.exports = function (c) { singleRun: true, browserDisconnectTimeout: 10000, // default 2000 browserDisconnectTolerance: 1, // default 0 - browserNoActivityTimeout: 4 * 60 * 1000, // default 10000 - captureTimeout: 4 * 60 * 1000 // default 60000 + browserNoActivityTimeout: 3 * 60 * 1000, // default 10000 + captureTimeout: 3 * 60 * 1000 // default 60000 })) } diff --git a/karma.conf.js b/karma.conf.js index bb8d623..6307bb1 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,4 +1,3 @@ -var _ = require('lodash') var webpackConfig = require('./webpack.config') var config = { @@ -19,12 +18,12 @@ var config = { // this watcher watches when bundled files are updated autoWatch: true, - webpack: _.extend(webpackConfig, { + webpack: Object.assign(webpackConfig, { entry: undefined, // this watcher watches when source files are updated watch: true, devtool: 'inline-source-map', - module: _.extend(webpackConfig.module, { + module: Object.assign(webpackConfig.module, { postLoaders: [{ test: /\.js/, exclude: /(test|node_modules)/, diff --git a/lib/dash.js b/lib/dash.js index 2dd63ad..94014f9 100644 --- a/lib/dash.js +++ b/lib/dash.js @@ -1,11 +1,28 @@ -module.exports = { - map: require('lodash/collection/map'), - each: require('lodash/collection/each'), - reduce: require('lodash/collection/reduce'), - pluck: require('lodash/collection/pluck'), - extend: require('lodash/object/assign'), - pick: require('lodash/object/pick'), - toArray: require('lodash/lang/toArray'), - clone: require('lodash/lang/clone'), - isEqual: require('lodash/lang/isEqual') +let toString = Object.prototype.toString +let keys = Object.keys +let assoc = (obj, attr, val) => { obj[attr] = val; return obj } +let isArray = obj => toString.call(obj) === '[object Array]' + +export let clone = obj => + isArray(obj) + ? obj.slice(0) + : extend({}, obj) + +export let pick = (obj, attrs) => + attrs.reduce((acc, attr) => + assoc(acc, attr, obj[attr]), {}) + +export let isEqual = (obj1, obj2) => + keys(obj1).length === keys(obj2).length && + keys(obj1).reduce((acc, key) => acc && obj2[key] === obj1[key], true) + +export let extend = (obj, ...rest) => { + rest.forEach(source => { + if (source) { + for (var prop in source) { + obj[prop] = source[prop] + } + } + }) + return obj } diff --git a/lib/path.js b/lib/path.js index be48d30..49b87ac 100644 --- a/lib/path.js +++ b/lib/path.js @@ -1,4 +1,3 @@ -var _ = require('./dash') var invariant = require('./invariant') var merge = require('qs/lib/utils').merge var qs = require('qs') @@ -17,7 +16,7 @@ function compilePattern (pattern) { _compiledPatterns[pattern] = { matcher: re, - paramNames: _.pluck(paramNames, 'name') + paramNames: paramNames.map(p => p.name) } } diff --git a/lib/router.js b/lib/router.js index 7c8bbc9..f2fcf1b 100644 --- a/lib/router.js +++ b/lib/router.js @@ -74,7 +74,7 @@ Cherrytree.prototype.map = function (routes) { eachBranch({routes: this.routes}, [], function (routes) { // concatenate the paths of the list of routes - var path = _.reduce(routes, function (memo, r) { + var path = routes.reduce(function (memo, r) { // reset if there's a leading slash, otherwise concat // and keep resetting the trailing slash return (r.path[0] === '/' ? r.path : memo + '/' + r.path).replace(/\/$/, '') @@ -92,7 +92,7 @@ Cherrytree.prototype.map = function (routes) { }) function eachBranch (node, memo, fn) { - _.each(node.routes, function (route) { + node.routes.forEach(function (route) { if (!route.routes || route.routes.length === 0) { fn.call(null, memo.concat(route)) } else { @@ -132,11 +132,11 @@ Cherrytree.prototype.listen = function (location) { * * @api public */ -Cherrytree.prototype.transitionTo = function () { +Cherrytree.prototype.transitionTo = function (...args) { if (this.state.activeTransition) { - return this.replaceWith.apply(this, arguments) + return this.replaceWith.apply(this, args) } - return this.doTransition('setURL', _.toArray(arguments)) + return this.doTransition('setURL', args) } /** @@ -149,8 +149,8 @@ Cherrytree.prototype.transitionTo = function () { * * @api public */ -Cherrytree.prototype.replaceWith = function () { - return this.doTransition('replaceURL', _.toArray(arguments)) +Cherrytree.prototype.replaceWith = function (...args) { + return this.doTransition('replaceURL', args) } /** @@ -169,7 +169,7 @@ Cherrytree.prototype.generate = function (name, params, query) { params = params || {} query = query || {} - _.each(this.matchers, function (m) { + this.matchers.forEach(function (m) { if (m.name === name) { matcher = m } @@ -264,7 +264,7 @@ Cherrytree.prototype.match = function (path) { var query var routes = [] var pathWithoutQuery = Path.withoutQuery(path) - _.each(this.matchers, function (matcher) { + this.matchers.forEach(function (matcher) { if (!found) { params = Path.extractParams(matcher.path, pathWithoutQuery) if (params) { @@ -275,7 +275,7 @@ Cherrytree.prototype.match = function (path) { } }) return { - routes: _.map(routes, descriptor), + routes: routes.map(descriptor), params: params || {}, query: query || {} } diff --git a/lib/transition.js b/lib/transition.js index 9313dd4..c49f762 100644 --- a/lib/transition.js +++ b/lib/transition.js @@ -19,7 +19,7 @@ module.exports = function transition (options, Promise) { let startTime = Date.now() log('---') log('Transition #' + id, 'to', path) - log('Transition #' + id, 'routes:', _.pluck(routes, 'name')) + log('Transition #' + id, 'routes:', routes.map(r => r.name)) log('Transition #' + id, 'params:', params) log('Transition #' + id, 'query:', query) diff --git a/package.json b/package.json index d620415..e43e613 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ }, "dependencies": { "location-bar": "^2.0.0", - "lodash": "^3.3.0", "path-to-regexp": "^1.0.3", "qs": "^2.3.3" }, @@ -40,10 +39,11 @@ "bro-size": "^1.0.0", "cli-color": "^0.3.3", "co": "^4.5.1", + "es5-shim": "^4.1.11", "es6-promise": "^2.0.1", "istanbul-instrumenter-loader": "^0.1.3", "jquery": "^2.1.3", - "karma": "^0.13.3", + "karma": "^0.13.9", "karma-chrome-launcher": "~0.1.0", "karma-cli": "0.0.4", "karma-coverage": "^0.5.0", @@ -61,7 +61,8 @@ "standard": "^4.5.4", "underscore": "^1.7.0", "webpack": "^1.4.13", - "webpack-dev-server": "^1.6.6" + "webpack-dev-server": "^1.6.6", + "yargs": "^3.23.0" }, "standard": { "ignore": [ @@ -70,4 +71,4 @@ "build/**" ] } -} \ No newline at end of file +} diff --git a/tasks/build.sh b/tasks/build.sh index 9fd5380..c45d69c 100755 --- a/tasks/build.sh +++ b/tasks/build.sh @@ -11,5 +11,5 @@ cp package.json build webpack index.js build/standalone.js echo "\nnpm build including deps is\n `bro-size build`" -echo "\nnpm build excluding deps is\n `bro-size build -u location-bar -u lodash/** -u qs -u path-to-regexp`" +echo "\nnpm build excluding deps is\n `bro-size build -u location-bar -u qs -u path-to-regexp`" echo "\nstandalone build including deps is\n `bro-size build/standalone.js`" diff --git a/tests/routerTest.js b/tests/routerTest.js index d28c165..2e24672 100644 --- a/tests/routerTest.js +++ b/tests/routerTest.js @@ -1,9 +1,9 @@ -let _ = require('lodash') let Promise = require('es6-promise').Promise let co = require('co') let {assert} = require('referee') let {suite, test, beforeEach, afterEach} = window let cherrytree = require('..') +let extend = require('../lib/dash').extend let delay = (t) => new Promise((resolve) => setTimeout(resolve, t)) @@ -40,7 +40,8 @@ test('#use registers middleware', () => { test('#use middleware gets passed a transition object', (done) => { let m = (transition) => { - let t = _.omit(transition, ['catch', 'then', 'redirectTo', 'cancel', 'retry', 'followRedirects']) + let t = extend({}, transition) + ;['catch', 'then', 'redirectTo', 'cancel', 'retry', 'followRedirects'].forEach(attr => delete t[attr]) let et = { id: 3, prev: { @@ -113,7 +114,7 @@ test('#use middleware gets passed a transition object', (done) => { test('#map registers the routes', () => { router.map(routes) // check that the internal matchers object is created - assert.equals(_.pluck(router.matchers, 'path'), [ + assert.equals(router.matchers.map(m => m.path), [ '/application', '/application/notifications', '/application/messages', @@ -215,7 +216,7 @@ test('#match matches a path against the routes', () => { user: 'KidkArolis', id: '42' }) - assert.equals(_.pluck(match.routes, 'name'), ['application', 'status']) + assert.equals(match.routes.map(r => r.name), ['application', 'status']) }) test('#match matches a path with query params', () => { @@ -313,7 +314,7 @@ test('routes with name "index" or that end int ".index" default to an empty path route('bar.index') }) }) - assert.equals(_.pluck(router.matchers, 'path'), [ + assert.equals(router.matchers.map(m => m.path), [ '/', '/foo', '/bar' @@ -341,7 +342,7 @@ test('a complex route map', () => { }) }) // check that the internal matchers object is created - assert.equals(_.pluck(router.matchers, 'path'), [ + assert.equals(router.matchers.map(m => m.path), [ '/application', '/application/notifications', '/application/messages/unread/priority', diff --git a/tests/unit/dashTest.js b/tests/unit/dashTest.js new file mode 100644 index 0000000..d10a35b --- /dev/null +++ b/tests/unit/dashTest.js @@ -0,0 +1,46 @@ +let {assert, refute} = require('referee') +let {suite, test} = window +let {clone, pick, isEqual, extend} = require('../../lib/dash') + +suite('dash') + +test('clone arrays', () => { + let a = [1, 2, 3] + let b = clone(a) + b.push(4) + assert.equals(a, [1, 2, 3]) + assert.equals(b, [1, 2, 3, 4]) +}) + +test('clone objects', () => { + let a = {a: 1, b: 2} + let b = clone(a) + b.c = 3 + assert.equals(a, {a: 1, b: 2}) + assert.equals(b, {a: 1, b: 2, c: 3}) +}) + +test('pick', () => { + assert.equals(pick({a: 1, b: 2, c: 3}, ['a', 'c']), {a: 1, c: 3}) +}) + +test('isEqual', () => { + let arr = [] + assert(isEqual({a: 1, b: 2}, {a: 1, b: 2})) + assert(isEqual({a: 1, b: arr}, {a: 1, b: arr})) + refute(isEqual({a: 1, b: 2}, {a: 1, b: '2'})) + refute(isEqual({a: 1, b: 2}, {a: 1})) + refute(isEqual({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}})) +}) + +test('extend', () => { + assert.equals(extend({}, {a: 1, b: 2}, null, {c: 3}), {a: 1, b: 2, c: 3}) + + let obj = {d: 4} + let target = {} + extend(target, obj) + target.a = 1 + obj.b = 2 + assert.equals(obj, {b: 2, d: 4}) + assert.equals(target, {a: 1, d: 4}) +})