Last active
August 29, 2015 14:10
-
-
Save fr1n63/0a4a88515016cd8f403d to your computer and use it in GitHub Desktop.
Angular Sticky Default State enforcer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () | |
{ | |
'use strict'; | |
angular | |
.module('common.providers') | |
.provider('StickyDefaultState', StickyDefaultState); | |
function StickyDefaultState() | |
{ | |
var provider = {}; | |
var Enforcer = function (modalState, parentState, defaultState) | |
{ | |
function handleChange($state, $match, $stickyState) | |
{ | |
// Check the target state exists | |
if (!$state.get(modalState + '.' + $match.path)) | |
{ | |
// No state, return false | |
return false; | |
} | |
else | |
{ | |
if ($state.includes(parentState)) | |
{ | |
// Normal transition from within shared state. | |
$state.transitionTo(modalState + '.' + $match.path, $match, false) | |
.then(function () | |
{ | |
// Check if there are no sticky states, invalid if there are no inactive states. | |
// This happens if you cross link from one modal state, to another modal | |
// state with different parent parameters | |
if ($stickyState.getInactiveStates().length === 0) | |
{ | |
stickyStateInstantiate($state, $match); | |
} | |
} | |
); | |
} | |
else | |
{ | |
// Transitioning from an entirely separate state, there won't be any | |
// relevant sticky states instantiated yet. | |
stickyStateInstantiate($state, $match); | |
} | |
return true; | |
} | |
} | |
// Force a state reload including the default state | |
function stickyStateInstantiate($state, $match) | |
{ | |
// Transition to the default sticky state | |
$state.transitionTo(defaultState, $match, { | |
location: false, | |
inherit : true, | |
relative: $state.$current, | |
notify : false | |
}).then(function () | |
{ | |
// Transition back to the original modal state | |
$state.transitionTo(modalState + '.' + $match.path, $match, { | |
location: false, | |
inherit : false, | |
relative: $state.$current, | |
notify : true | |
}); | |
}); | |
} | |
return handleChange; | |
}; | |
provider.enforcer = function (modalState, parentState, defaultState) | |
{ | |
return new Enforcer(modalState, parentState, defaultState); | |
}; | |
provider.$get = function () | |
{ | |
return { | |
enforcer: provider.enforcer | |
}; | |
}; | |
return provider; | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe( | |
'Test Sticky Default States Provider', function () | |
{ | |
'use strict'; | |
var $stateProvider; | |
var StickyDefaultStateProvider; | |
var rootScope; | |
var location; | |
var scope; | |
var state; | |
var modalUrl = '/accounts/{accountId:[0-9]{1,8}}/actions/{path:.*}'; | |
var modalState = 'accounts.transactions.actions'; | |
var parentState = 'accounts.transactions'; | |
var defaultState = 'accounts.transactions.list'; | |
beforeEach(module('common.providers')); | |
beforeEach(module(function (_$stateProvider_, $urlRouterProvider) | |
{ | |
// Make the state machine available | |
$stateProvider = _$stateProvider_; | |
// Create the enforcer and annotate it | |
var enforcer = StickyDefaultStateProvider.enforcer(modalState, parentState, defaultState); | |
enforcer.$inject = [ | |
'$state', | |
'$match', | |
'$stickyState' | |
]; | |
// Wire the url router provider with the enforcer | |
$urlRouterProvider.when(modalUrl, enforcer); | |
// Mock out the representative state machine | |
$stateProvider | |
.state('accounts', {}) | |
.state('accounts.list', {}) | |
.state('accounts.transactions', {abstract: true}) | |
.state('accounts.transactions.list', {sticky: true}) | |
.state('accounts.transactions.detail', {sticky: true}) | |
.state('accounts.transactions.actions', {abstract: true}) | |
.state('accounts.transactions.actions.export', {}) | |
.state('accounts.transactions.actions.calendar', {}); | |
})); | |
beforeEach( | |
function () | |
{ | |
// Initialize the service provider | |
// by injecting it to a fake module's config block | |
angular | |
.module('common.providers') | |
.config( | |
function (_StickyDefaultStateProvider_) | |
{ | |
StickyDefaultStateProvider = _StickyDefaultStateProvider_; | |
}); | |
// Initialize common.providers injector | |
module('common.providers'); | |
// Kickstart the injectors previously registered | |
// with calls to angular.mock.module | |
inject( | |
function () | |
{ | |
}); | |
} | |
); | |
beforeEach( | |
inject(function ($rootScope, $location, $state) | |
{ | |
rootScope = $rootScope; | |
location = $location; | |
state = $state; | |
scope = $rootScope.$new(); | |
} | |
) | |
); | |
it( | |
'the providers api should exist', function () | |
{ | |
expect(StickyDefaultStateProvider).toBeDefined(); | |
expect(StickyDefaultStateProvider.$get()).toBeDefined(); | |
expect(StickyDefaultStateProvider.enforcer).toBeDefined(); | |
} | |
); | |
it('should be in the default state if transitioning from no state, deep link test', inject(function () | |
{ | |
location.path('/accounts/2/actions/calendar'); | |
rootScope.$apply(); | |
expect(state.current.name).toEqual('accounts.transactions.actions.calendar'); | |
expect(location.path()).toEqual('/accounts/2/actions/calendar'); | |
})); | |
it('should be in modal state after transitioning from sticky state with same accountId', inject(function () | |
{ | |
state.go('accounts.transactions.list', {accountId: 2}); | |
scope.$apply(); | |
location.path('/accounts/2/actions/calendar'); | |
rootScope.$apply(); | |
expect(state.current.name).toEqual('accounts.transactions.actions.calendar'); | |
expect(location.path()).toEqual('/accounts/2/actions/calendar'); | |
})); | |
it('should be in modal state after transitioning from unrelated state', inject(function () | |
{ | |
state.go('accounts'); | |
scope.$apply(); | |
location.path('/accounts/2/actions/export'); | |
rootScope.$apply(); | |
expect(state.current.name).toEqual('accounts.transactions.actions.export'); | |
expect(location.path()).toEqual('/accounts/2/actions/export'); | |
})); | |
it('should be false if transitioning from accounts.details.transaction to account.details.actions', inject(function () | |
{ | |
state.go('accounts.transactions.actions.calendar', {accountId: 2}); | |
scope.$apply(); | |
location.path('/accounts/4/actions/calendar'); | |
rootScope.$apply(); | |
expect(state.current.name).toEqual('accounts.transactions.actions.calendar'); | |
expect(location.path()).toEqual('/accounts/4/actions/calendar'); | |
})); | |
it('should default to root state if invalid state', inject(function () | |
{ | |
location.path('/accounts/1/actions/notavalidstate'); | |
rootScope.$apply(); | |
expect(state.current.name).toEqual(''); | |
expect(location.path()).toEqual('/'); | |
})); | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment