Skip to content

Instantly share code, notes, and snippets.

@fr1n63
Last active August 29, 2015 14:10
Show Gist options
  • Save fr1n63/0a4a88515016cd8f403d to your computer and use it in GitHub Desktop.
Save fr1n63/0a4a88515016cd8f403d to your computer and use it in GitHub Desktop.
Angular Sticky Default State enforcer
(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;
}
})();
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