UNPKG

abstract-state-router

Version:

The basics of a client-side state router ala the AngularJS ui-router, but without any DOM interactions

460 lines (393 loc) 10.9 kB
var test = require('tape-catch') var assertingRendererFactory = require('./helpers/asserting-renderer-factory') var getTestState = require('./helpers/test-state-factory') var mockRendererFacotry = require('./helpers/renderer-mock') test('Emitting errors when attempting to navigate to invalid states', function(t) { function testGoingTo(description, invalidStateName) { t.test(description, function(t) { var renderer = assertingRendererFactory(t, []) var state = getTestState(t, renderer) var stateRouter = state.stateRouter var assertsBelow = 1 var renderAsserts = renderer.expectedAssertions t.plan(assertsBelow + renderAsserts) stateRouter.addState({ name: 'valid', route: '/valid', template: null, activate: function(context) { t.fail('Should never activate the parent\'s state') } }) stateRouter.addState({ name: 'valid.valid', route: '/valid', template: null, activate: function(context) { t.fail('Should never activate the child\'s state') } }) stateRouter.on('stateChangeError', function(e) { t.notEqual(e.message.indexOf(invalidStateName), -1, 'invalid state name is in the error message') t.end() }) stateRouter.go(invalidStateName, {}) }) } testGoingTo('All states invalid', 'invalid.also-invalid') testGoingTo('Only the child state invalid', 'valid.invalid') }) test('Emitting stateChangeStart and stateChangeEnd', function(t) { var parent1Template = {} var child1Template = {} var parent2Template = {} var child2Template = {} var firstProperties = { one: 'wat' } var secondProperties = { two: 'wat' } var renderer = assertingRendererFactory(t, [ parent1Template, child1Template, parent2Template, child2Template ]) var state = getTestState(t, renderer) var stateRouter = state.stateRouter var assertsBelow = 24 var renderAsserts = renderer.expectedAssertions t.plan(assertsBelow + renderAsserts) var firstParentActivate = false var firstChildActivate = false var secondParentActivate = false var secondChildActivate = false stateRouter.addState({ name: 'valid1', route: '/valid1', template: parent1Template, activate: function(context) { firstParentActivate = true } }) stateRouter.addState({ name: 'valid1.valid', route: '/valid1', template: child1Template, activate: function(context) { firstChildActivate = true } }) stateRouter.addState({ name: 'valid2', route: '/valid2', template: parent2Template, activate: function(context) { secondParentActivate = true } }) stateRouter.addState({ name: 'valid2.valid', route: '/valid2', template: child2Template, activate: function(context) { secondChildActivate = true } }) stateRouter.once('stateChangeStart', function(state, properties) { t.equal(state.name, 'valid1.valid') t.deepEqual(properties, firstProperties) t.notOk(firstParentActivate) t.notOk(firstChildActivate) t.notOk(secondParentActivate) t.notOk(secondChildActivate) }) stateRouter.once('stateChangeEnd', function(state, properties) { t.equal(state.name, 'valid1.valid') t.deepEqual(properties, firstProperties) t.ok(firstParentActivate) t.ok(firstChildActivate) t.notOk(secondParentActivate) t.notOk(secondChildActivate) stateRouter.once('stateChangeStart', function(state, properties) { t.equal(state.name, 'valid2.valid') t.deepEqual(properties, secondProperties) t.ok(firstParentActivate) t.ok(firstChildActivate) t.notOk(secondParentActivate) t.notOk(secondChildActivate) }) stateRouter.once('stateChangeEnd', function(state, properties) { t.equal(state.name, 'valid2.valid') t.deepEqual(properties, secondProperties) t.ok(firstParentActivate) t.ok(firstChildActivate) t.ok(secondParentActivate) t.ok(secondChildActivate) t.end() }) stateRouter.go('valid2.valid', secondProperties) }) stateRouter.go('valid1.valid', firstProperties) }) test('emitting stateChangeError', function(t) { var parent1Template = {} var child1Template = {} var renderer = assertingRendererFactory(t, [ ]) var state = getTestState(t, renderer) var stateRouter = state.stateRouter var assertsBelow = 1 var renderAsserts = renderer.expectedAssertions var error1 = new Error('first') var error2 = new Error('second') t.plan(assertsBelow + renderAsserts) stateRouter.addState({ name: 'valid1', route: '/valid1', template: parent1Template, resolve: function() { throw error1 }, activate: function(context) { t.fail('should not activate') } }) stateRouter.addState({ name: 'valid1.valid', route: '/valid1', template: child1Template, resolve: function() { throw error2 }, activate: function(context) { t.fail('should not activate') } }) stateRouter.on('stateChangeError', function(e) { t.equal(e, error1) t.end() }) stateRouter.go('valid1.valid') }) test('emitting dom api create', function(t) { var originalDomApi = {} var renderCalled = false var beforeEventFired = false var afterEventFired = false t.plan(16) var state = getTestState(t, function() { return { render: function(context, cb) { t.ok(beforeEventFired) renderCalled = true t.notOk(afterEventFired) cb(null, originalDomApi) }, reset: function(context, cb) { cb(null) }, destroy: function(renderedTemplateApi, cb) { cb(null) }, getChildElement: function getChildElement(renderedTemplateApi, cb) { cb(null, {}) } } }) var stateRouter = state.stateRouter var originalStateObject = { name: 'state', route: '/state', template: {}, querystringParameters: [ 'wat', 'much' ], defaultQuerystringParameters: { wat: 'lol', much: 'neat' }, resolve: function(data, params, cb) { cb(null, { value: 'legit' }) } } stateRouter.addState(originalStateObject) stateRouter.on('beforeCreateState', function(context) { t.notOk(renderCalled) t.notOk(afterEventFired) t.notOk(beforeEventFired) beforeEventFired = true t.equal(context.state, originalStateObject) t.equal(context.content.value, 'legit') t.equal(context.parameters.thingy, 'yes') t.notOk(context.domApi) }) stateRouter.on('afterCreateState', function(context) { t.ok(beforeEventFired) t.ok(renderCalled) t.notOk(afterEventFired) afterEventFired = true t.equal(context.state, originalStateObject) t.equal(context.content.value, 'legit') t.equal(context.parameters.thingy, 'yes') t.equal(context.domApi, originalDomApi) t.end() }) stateRouter.go('state', { thingy: 'yes' }) }) test('emitting dom api destroy', function(t) { var originalDomApi = {} var beforeEventFired = false var afterEventFired = false var destroyCalled = false var state = getTestState(t, function() { return { render: function(context, cb) { cb(null, originalDomApi) }, reset: function(context, cb) { cb(null) }, destroy: function(renderedTemplateApi, cb) { t.ok(beforeEventFired) t.notOk(afterEventFired) destroyCalled = true cb(null) }, getChildElement: function getChildElement(renderedTemplateApi, cb) { cb(null, {}) } } }) var stateRouter = state.stateRouter t.plan(11) var originalStateObject = { name: 'state', route: '/state', template: {}, activate: function() { stateRouter.go('second-state', {}) } } stateRouter.addState(originalStateObject) stateRouter.addState({ name: 'second-state', route: '/second', template: {}, activate: function(context) { t.ok(afterEventFired) t.end() } }) stateRouter.on('beforeDestroyState', function(context) { t.notOk(destroyCalled) t.notOk(afterEventFired) beforeEventFired = true t.equal(context.state, originalStateObject) t.equal(context.domApi, originalDomApi) }) stateRouter.on('afterDestroyState', function(context) { t.ok(beforeEventFired) t.ok(destroyCalled) afterEventFired = true t.equal(context.state, originalStateObject) t.notOk(context.domApi) }) stateRouter.go('state', {}) }) test('emitting dom api reset', function(t) { var originalDomApi = {} var secondDomApi = {} var domApis = [originalDomApi, secondDomApi] var beforeEventFired = false var afterEventFired = false var resetCalled = false t.plan(16) var state = getTestState(t, function() { return { render: function(context, cb) { cb(null, domApis.shift()) }, reset: function(context, cb) { if (!resetCalled) { t.ok(beforeEventFired) t.notOk(afterEventFired) resetCalled = true } cb(null) }, destroy: function(renderedTemplateApi, cb) { cb(null) }, getChildElement: function getChildElement(renderedTemplateApi, cb) { cb(null, {}) } } }) var stateRouter = state.stateRouter var originalStateObject = { name: 'state', route: '/state', template: {}, querystringParameters: [ 'wat' ], resolve: function(data, params, cb) { cb(null, { value: 'legit' }) }, activate: function() { setTimeout(function() { stateRouter.go('state', { wat: '20' }) }, 10) } } stateRouter.addState(originalStateObject) stateRouter.on('beforeResetState', function(context) { t.notOk(beforeEventFired) t.notOk(resetCalled) t.notOk(afterEventFired) beforeEventFired = true t.equal(context.state, originalStateObject) t.equal(context.domApi, originalDomApi) t.equal(context.content.value, 'legit') t.equal(context.parameters.wat, '20') }) stateRouter.on('afterResetState', function(context) { t.ok(beforeEventFired) t.ok(resetCalled) t.notOk(afterEventFired) afterEventFired = true t.equal(context.state, originalStateObject) t.equal(context.domApi, originalDomApi) t.equal(context.content.value, 'legit') t.equal(context.parameters.wat, '20') t.end() }) stateRouter.go('state', { wat: '10' }) }) test('emitting routeNotFound', function(t) { var renderer = assertingRendererFactory(t, []) var state = getTestState(t, renderer) var stateRouter = state.stateRouter var assertsBelow = 2 var renderAsserts = renderer.expectedAssertions t.plan(assertsBelow + renderAsserts) stateRouter.addState({ name: 'valid', route: '/valid', template: null, activate: function(context) { t.fail('Should never activate the parent\'s state') } }) stateRouter.addState({ name: 'valid.valid', route: '/valid', template: null, activate: function(context) { t.fail('Should never activate the child\'s state') } }) stateRouter.on('stateChangeError', function(e) { t.fail('Should not emit a normal error') }) stateRouter.on('stateError', function(e) { t.fail('Should not emit a normal error') }) stateRouter.on('routeNotFound', function(route, parameters) { t.equal(route, '/nonexistent') t.equal(parameters.thingy, 'stuff') t.end() }) state.hashRouter.location.go('/nonexistent?thingy=stuff') })