statemachinejs
Version:
Implementation of State Machine Pattern in JavaScript.
988 lines (848 loc) • 36.6 kB
JavaScript
(function(global) {
global.STATEMACHINE_EXPORT_EXTRA = true;
describe('StateMachine', function() {
it('is defined in global namespace', function() {
expect(StateMachine).toBeDefined();
});
it('contains all extra functions', function() {
expect(StateMachine.isObject).toBeDefined();
expect(StateMachine.isFunction).toBeDefined();
expect(StateMachine.isString).toBeDefined();
expect(StateMachine.isNumber).toBeDefined();
expect(StateMachine.isBoolean).toBeDefined();
expect(StateMachine.isArray).toBeDefined();
expect(StateMachine.isArrayLike).toBeDefined();
expect(StateMachine.arrayFrom).toBeDefined();
expect(StateMachine.functionFrom).toBeDefined();
expect(StateMachine.functionApply).toBeDefined();
expect(StateMachine.nop).toBeDefined();
expect(StateMachine.initFsm).toBeDefined();
expect(StateMachine.initState).toBeDefined();
expect(StateMachine.handleStateTriggerFactory).toBeDefined();
expect(StateMachine.getTransitions).toBeDefined();
expect(StateMachine.getState).toBeDefined();
expect(StateMachine.pushCurrentState).toBeDefined();
expect(StateMachine.popCurrentState).toBeDefined();
expect(StateMachine.changeState).toBeDefined();
expect(StateMachine.doEntryAction).toBeDefined();
expect(StateMachine.doExitAction).toBeDefined();
});
/* ----- Helpers ----- */
describe('isObject', function() {
it('returns true is value is object', function() {
expect(StateMachine.isObject({})).toBe(true);
});
it('returns false is value is not object', function() {
expect(StateMachine.isObject('123')).toBe(false);
});
it('returns false is value is null', function() {
expect(StateMachine.isObject(null)).toBe(false);
});
});
describe('isFunction', function() {
it('returns true is value is function', function() {
expect(StateMachine.isFunction(function() {})).toBe(true);
});
it('returns false is value is not function', function() {
expect(StateMachine.isFunction('123')).toBe(false);
});
});
describe('isString', function() {
it('returns true is value is string', function() {
expect(StateMachine.isString('123')).toBe(true);
});
it('returns false is value is not string', function() {
expect(StateMachine.isString(123)).toBe(false);
});
});
describe('isNumber', function() {
it('returns true is value is number', function() {
expect(StateMachine.isNumber(123)).toBe(true);
});
it('returns false is value is not number', function() {
expect(StateMachine.isNumber('123')).toBe(false);
});
});
describe('isBoolean', function() {
it('returns true is value is boolean', function() {
expect(StateMachine.isBoolean(false)).toBe(true);
});
it('returns false is value is not boolean', function() {
expect(StateMachine.isBoolean('false')).toBe(false);
});
});
describe('isArray', function() {
it('returns true is value is array', function() {
expect(StateMachine.isArray([1, 2, 3])).toBe(true);
});
it('returns false is value is not array', function() {
expect(StateMachine.isArray(123)).toBe(false);
});
});
describe('isArrayLike', function() {
it('returns true is value is array', function() {
expect(StateMachine.isArrayLike([1, 2, 3])).toBe(true);
});
it('returns true is value is arguments', function() {
expect(StateMachine.isArrayLike(arguments)).toBe(true);
});
it('returns false is value is not array', function() {
expect(StateMachine.isArrayLike(123)).toBe(false);
});
it('returns false is value is object', function() {
expect(StateMachine.isArrayLike({length: 1})).toBe(false);
});
});
describe('arrayFrom', function() {
it('returns an empty array if value is undefined or null', function() {
expect(StateMachine.arrayFrom(undefined)).toEqual([]);
expect(StateMachine.arrayFrom(null)).toEqual([]);
});
it('returns the value if value is array', function() {
var a = [1, 2, 3];
expect(StateMachine.arrayFrom(a)).toBe(a);
});
it('returns an array if value is array-like', function() {
(function() {
expect(StateMachine.arrayFrom(arguments)).toEqual([1, 2, 3]);
})(1, 2, 3);
});
});
describe('functionFrom', function() {
it('returns the same function if value is function', function() {
var func = function() {
return true;
};
expect(StateMachine.functionFrom(func)).toBe(func);
});
it('returns the function if value is the name of a function of scope', function() {
var scope = {
func: function() {
return true;
}
};
expect(StateMachine.functionFrom('func', scope)).toBe(scope.func);
});
it('returns nop if value is not the name of a function in scope', function() {
var scope = {
func: function() {
return true;
}
};
expect(StateMachine.functionFrom('xfunc', scope)).toBe(StateMachine.nop);
});
it('returns nop if value is the name of a non-function property in scope', function() {
var scope = {
func: function() {
return true;
},
xfunc: 123
};
expect(StateMachine.functionFrom('xfunc', scope)).toBe(StateMachine.nop);
});
it('returns nop if value is not a name of any property in scope', function() {
var scope = {
func: function() {
return true;
},
xfunc: 123
};
expect(StateMachine.functionFrom('xfuncx', scope)).toBe(StateMachine.nop);
});
it('returns nop if value is string but scope is not an object', function() {
expect(StateMachine.functionFrom('func', 123)).toBe(StateMachine.nop);
});
it('returns nop if value is not string or function', function() {
expect(StateMachine.functionFrom(123)).toBe(StateMachine.nop);
});
});
describe('functionApply', function() {
it('calls the function and returns the result produced by the function', function() {
var obj = {
func: function() {
return 123;
}
};
var ret;
spyOn(obj, 'func').andCallThrough();
ret = StateMachine.functionApply(obj.func);
expect(obj.func).toHaveBeenCalled();
expect(ret).toEqual(123);
});
it('calls the function with supplied scope', function() {
var scope = {};
var func = function() {
return this;
};
expect(StateMachine.functionApply(func, scope)).toBe(scope);
});
it('calls the function with supplied arguments', function() {
var obj = {
func: function() {}
};
spyOn(obj, 'func');
StateMachine.functionApply(obj.func, obj, [123, '123']);
expect(obj.func).toHaveBeenCalledWith(123, '123');
});
it('calls the function with supplied scope and arguments', function() {
var scope = {};
var obj = {
func: function() {
return this;
}
};
spyOn(obj, 'func').andCallThrough();
expect(StateMachine.functionApply(obj.func, scope, [123, '123'])).toBe(scope);
expect(obj.func).toHaveBeenCalledWith(123, '123');
});
});
/* ----- StateMachine Functions ----- */
describe('initFsm', function() {
it('creates fsm object from full configuration', function() {
var host = {};
var states = [{
name: 'A',
innerStates: [{
name: 'D'
}, {
name: 'E'
}]
}, {
name: 'B'
}, {
name: 'C',
innerStates: [{
name: 'F'
}]
}];
var fsm = StateMachine.initFsm(host, states);
expect(fsm).toBeDefined();
expect(fsm.host).toBe(host);
expect(fsm.statesMap instanceof Object).toBe(true);
expect(fsm.currentState).toBeNull();
expect(fsm.currentStateStack).toEqual([]);
});
it('creates fsm object with correct statesMap', function() {
var host = {};
var states = [{
name: 'A',
innerStates: [{
name: 'D'
}, {
name: 'E'
}]
}, {
name: 'B'
}, {
name: 'C',
innerStates: [{
name: 'F'
}]
}];
var statesFqns = ['A', 'A.D', 'A.E', 'B', 'C', 'C.F'];
var fsm = StateMachine.initFsm(host, states);
for (var i = 0, len = statesFqns.length; i < len; i++) {
expect(statesFqns[i] in fsm.statesMap).toBe(true);
}
});
it('creates fsm object from nothing', function() {
var fsm = StateMachine.initFsm();
expect(fsm).toBeDefined();
expect(fsm.host).toBe(undefined);
expect(fsm.statesMap).toEqual({});
expect(fsm.currentState).toBeNull();
expect(fsm.currentStateStack).toEqual([]);
});
});
describe('initState', function() {
var statesMap;
beforeEach(function() {
statesMap = {};
});
it('creates a state from full configuration', function() {
var config = {
name: 'SimpleState',
entry: 'entryFn',
exit: 'exitFn',
transitions: [{
trigger: 'someEvent',
dest: 'SimpleDestination',
action: 'toSimpleDestinationAction',
guard: 'toSimpleDestinationGuard'
}, {
trigger: 'anotherEvent',
dest: 'AnotherSimpleDestination',
action: 'toAnotherSimpleDestinationAction',
guard: 'toAnotherSimpleDestinationGuard'
}]
};
var state = StateMachine.initState(statesMap, config);
expect(state).toBeDefined();
expect(state.name).toEqual(config.name);
expect(state.fqn).toEqual(config.name);
expect(state.entry).toEqual(config.entry);
expect(state.exit).toEqual(config.exit);
expect(state.outerState).toBeNull();
expect(state.innerStates).toEqual([]);
expect(state.transitions.length).toBe(2);
for (var i = 0, len = state.transitions.length; i < len; i++) {
expect(state.transitions[i]).not.toBe(config.transitions[i]);
expect(state.transitions[i]).toEqual(config.transitions[i]);
}
expect(statesMap[state.fqn]).toBe(state);
});
it('creates a state from no configuration', function() {
var state = StateMachine.initState(statesMap);
expect(state).toBeDefined();
expect(state.name).toEqual('UnnamedState');
expect(state.fqn).toEqual('UnnamedState');
expect(state.entry).not.toBeDefined()
expect(state.exit).not.toBeDefined();
expect(state.outerState).toBeNull();
expect(state.innerStates).toEqual([]);
expect(state.transitions.length).toBe(0);
});
it('creates a state if a string is provided instead of configuration object', function() {
var state = StateMachine.initState(statesMap, 'SimpleState');
expect(state).toBeDefined();
expect(state.name).toEqual('SimpleState');
expect(state.fqn).toEqual('SimpleState');
expect(state.entry).not.toBeDefined()
expect(state.exit).not.toBeDefined();
expect(state.outerState).toBeNull();
expect(state.innerStates).toEqual([]);
expect(state.transitions.length).toBe(0);
});
it('creates an inner state if outer state is provided', function() {
var outer = StateMachine.initState(statesMap, 'Outer');
var inner = StateMachine.initState(statesMap, 'Inner', outer);
expect(inner).toBeDefined();
expect(inner.name).toEqual('Inner');
expect(inner.fqn).toEqual('Outer.Inner');
expect(inner.outerState).toBe(outer);
});
it('creates a state with inner states', function() {
var state = StateMachine.initState(statesMap, {
name: 'A',
innerStates: [{
name: 'B'
}, {
name: 'C'
}, {
name: 'D'
}]
});
expect(state).toBeDefined();
expect(state.innerStates.length).toBe(3);
expect(state.innerStates[0].fqn).toEqual('A.B');
expect(state.innerStates[1].fqn).toEqual('A.C');
expect(state.innerStates[2].fqn).toEqual('A.D');
});
});
describe('handleStateTriggerFactory', function() {
it('creates a function', function() {
var func = StateMachine.handleStateTriggerFactory({});
expect(typeof func).toEqual('function');
});
});
describe('getTransitions', function() {
it('returns empty array if state is falsy', function() {
var ret = StateMachine.getTransitions();
expect(ret).toEqual([]);
});
it('returns empty array if state has no transitions', function() {
var state = {
transitions: []
};
var ret = StateMachine.getTransitions(state, 'anyEvent');
expect(ret).toEqual([]);
});
it('returns empty array if state has no transitions with given trigger name', function() {
var state = {
transitions: [{
trigger: 'event1'
}, {
trigger: 'event2'
}, {
trigger: 'event3'
}]
};
var ret = StateMachine.getTransitions(state, 'otherEvent');
expect(ret).toEqual([]);
});
it('returns an array of transitions with given trigger name', function() {
var trans1 = {
trigger: 'event1'
};
var trans2 = {
trigger: 'event2'
};
var trans3 = {
trigger: 'event1'
};
var state = {
transitions: [trans1, trans2, trans3]
};
var ret = StateMachine.getTransitions(state, 'event1');
expect(ret).toEqual([trans1, trans3]);
});
});
describe('getState', function() {
var fsm;
var stateA = {
name: 'A',
fqn: 'A'
};
var stateB = {
name: 'B',
fqn: 'B'
};
var stateC = {
name: 'C',
fqn: 'A.C',
outerState: stateA
};
var stateD = {
name: 'D',
fqn: 'A.C.D',
outerState: stateC
};
var stateE = {
name: 'E',
fqn: 'A.C.E',
outerState: stateC
};
var stateF = {
name: 'F',
fqn: 'B.F',
outerState: stateB
};
beforeEach(function() {
fsm = {
statesMap: {
'A': stateA,
'B': stateB,
'A.C': stateC,
'A.C.D': stateD,
'A.C.E': stateE,
'B.F': stateF
},
currentStateStack: []
};
});
it('returns the state object with provided name', function() {
var state = StateMachine.getState(fsm, 'A');
expect(state).toBe(stateA);
});
it('returns a falsy value if no states has the given name', function() {
var state = StateMachine.getState(fsm, 'OtherState');
expect(state).toBeFalsy();
});
it('returns the state object with the provided name, which is in the same outer state as the current state', function() {
fsm.currentState = stateD;
var state = StateMachine.getState(fsm, 'E');
expect(state).toBe(stateE);
});
it('returns the outer state object with the provided name if an inner state is looking for it', function() {
fsm.currentState = stateE;
var state = StateMachine.getState(fsm, 'C');
expect(state).toBe(stateC);
});
it('returns the ascendant state object with the provided name if a ascendant state is looking for it', function() {
fsm.currentState = stateD;
var state = StateMachine.getState(fsm, 'A');
expect(state).toBe(stateA);
});
it('returns the other root state object with the provided name if an inner state is looking for it', function() {
fsm.currentState = stateE;
var state = StateMachine.getState(fsm, 'B');
expect(state).toBe(stateB);
});
it('returns a falsy value if a state is looking for another state from different family', function() {
fsm.currentState = stateD;
var state = StateMachine.getState(fsm, 'F');
expect(state).toBeFalsy();
});
});
describe('pushCurrentState', function() {
var fsm;
var stateA = {
name: 'A'
};
var stateB = {
name: 'B'
};
beforeEach(function() {
fsm = {
currentStateStack: [],
currentState: stateA
};
});
it('pushes current state onto stack', function() {
StateMachine.pushCurrentState(fsm);
expect(fsm.currentStateStack.length).toBe(1);
expect(fsm.currentStateStack[0]).toBe(stateA);
});
it('changes current state to provided state', function() {
StateMachine.pushCurrentState(fsm, stateB);
expect(fsm.currentState).toBe(stateB);
});
});
describe('popCurrentState', function() {
var fsm;
var stateA = {
name: 'A'
};
var stateB = {
name: 'B'
};
beforeEach(function() {
fsm = {
currentStateStack: [stateA],
currentState: stateB
};
});
it('pops the current state stack', function() {
StateMachine.popCurrentState(fsm);
expect(fsm.currentStateStack.length).toBe(0);
});
it('changes current state to the removed state', function() {
StateMachine.popCurrentState(fsm);
expect(fsm.currentState).toBe(stateA);
});
});
describe('changeState', function() {
var fsm;
var stateA = {
name: 'A'
};
var stateB = {
name: 'B'
};
beforeEach(function() {
fsm = {};
});
it('changes current and previous states', function() {
StateMachine.changeState(fsm, stateA, stateB);
expect(fsm.currentState).toBe(stateB);
expect(fsm.previousState).toBe(stateA);
});
});
describe('doEntryAction', function() {
var fsm;
beforeEach(function() {
fsm = {
host: {
func: function() {},
handleStateTrigger: function() {}
},
currentState: {
entry: 'func'
}
};
});
it('calls the entry function', function() {
spyOn(fsm.host, 'func');
StateMachine.doEntryAction(fsm);
expect(fsm.host.func).toHaveBeenCalled();
});
it('calls the handleStateTrigger with auto trigger', function() {
spyOn(fsm.host, 'handleStateTrigger');
StateMachine.doEntryAction(fsm);
expect(fsm.host.handleStateTrigger).toHaveBeenCalledWith('.');
});
});
describe('doExitAction', function() {
var fsm;
var stateA = {
exit: 'exitFnA'
};
var stateB = {
exit: 'exitFnB',
outerState: stateA
};
var stateC = {
exit: 'exitFnC',
outerState: stateB
};
var stateD = {
exit: 'exitFnD',
outerState: stateB
};
var stateE = {
exit: 'exitFnE'
};
beforeEach(function() {
fsm = {
host: {
exitFnA: function() {},
exitFnB: function() {},
exitFnC: function() {},
exitFnD: function() {},
exitFnE: function() {}
}
};
});
it('calls the exit function', function() {
spyOn(fsm.host, 'exitFnA');
StateMachine.doExitAction(fsm, stateA, stateB);
expect(fsm.host.exitFnA).toHaveBeenCalled();
});
it('calls the exit functions all the way up if next state is not in the family', function() {
spyOn(fsm.host, 'exitFnA');
spyOn(fsm.host, 'exitFnB');
spyOn(fsm.host, 'exitFnC');
StateMachine.doExitAction(fsm, stateC, stateE);
expect(fsm.host.exitFnC).toHaveBeenCalled();
expect(fsm.host.exitFnB).toHaveBeenCalled();
expect(fsm.host.exitFnA).toHaveBeenCalled();
});
it('calls the exit functions only up to where next state is if next state is in the same family', function() {
spyOn(fsm.host, 'exitFnA');
spyOn(fsm.host, 'exitFnB');
spyOn(fsm.host, 'exitFnC');
StateMachine.doExitAction(fsm, stateC, stateA);
expect(fsm.host.exitFnC).toHaveBeenCalled();
expect(fsm.host.exitFnB).toHaveBeenCalled();
expect(fsm.host.exitFnA).not.toHaveBeenCalled();
});
it('calls the state\'s own exit function if next state shares the same outer state', function() {
spyOn(fsm.host, 'exitFnA');
spyOn(fsm.host, 'exitFnB');
spyOn(fsm.host, 'exitFnC');
StateMachine.doExitAction(fsm, stateC, stateD);
expect(fsm.host.exitFnC).toHaveBeenCalled();
expect(fsm.host.exitFnB).not.toHaveBeenCalled();
expect(fsm.host.exitFnA).not.toHaveBeenCalled();
});
});
describe('init', function() {
it('attaches fsm object and various functions to host object', function() {
var host = {};
StateMachine.init(host, [], true, true);
expect(host.fsm).toBeDefined();
expect(host.handleStateTrigger).toBeDefined();
expect(host.getCurrentStateName).toBeDefined();
expect(host.getPreviousStateName).toBeDefined();
});
it('assigns true as default values to callEntryIfTransitBack and callExitIfTransitBack', function() {
var host = {};
StateMachine.init(host, []);
expect(host.fsm.callEntryIfTransitBack).toBe(true);
expect(host.fsm.callExitIfTransitBack).toBe(true);
});
it('moves the state machine to the first state', function() {
var host = {};
StateMachine.init(host, [{
name: 'A'
}]);
expect(host.getCurrentStateName()).toEqual('A');
});
it('uses states definition from host object', function() {
var host = {
states: [{
name: 'A'
}]
};
StateMachine.init(host);
expect(host.getCurrentStateName()).toEqual('A');
});
});
describe('handleStateTrigger', function() {
var host;
var states = [{
name: 'A',
entry: 'stateAEntry',
exit: 'stateAExit',
transitions: [{
trigger: 'stateAToBEvent',
dest: 'B',
action: 'stateAToBAction',
guard: 'stateAToBGuard',
}, {
trigger: 'stateAToNotExistEvent',
dest: 'NotExist'
}, {
trigger: 'stateADupEvent',
dest: 'C',
guard: function() { return false; }
}, {
trigger: 'stateADupEvent',
dest: 'D',
guard: function() { return true; }
}, {
trigger: 'stateAToEEvent',
dest: 'E',
}]
}, {
name: 'B',
entry: 'stateBEntry',
exit: 'stateBExit'
}, {
name: 'C'
}, {
name: 'D'
}, {
name: 'E',
entry: 'stateEEntry',
exit: 'stateEExit',
innerStates: [{
name: 'F',
entry: 'stateFEntry',
exit: 'stateFExit',
transitions: [{
trigger: 'stateFToBEvent',
dest: 'B'
}]
}, {
name: 'G',
entry: 'stateGEntry',
exit: 'stateGExit'
}]
}];
beforeEach(function() {
host = {
stateAEntry: function() {},
stateAExit: function() {},
stateAToBAction: function() {},
stateAToBGuard: function() {},
stateBEntry: function() {},
stateBExit: function() {},
stateEEntry: function() {},
stateEExit: function() {},
stateFEntry: function() {},
stateFExit: function() {},
stateGEntry: function() {},
stateGExit: function() {},
};
StateMachine.init(host, states);
});
it('changes state if a transition can be found with the given trigger', function() {
spyOn(host, 'stateAExit');
spyOn(host, 'stateBEntry');
expect(host.getCurrentStateName()).toEqual('A');
host.handleStateTrigger('stateAToBEvent');
expect(host.getCurrentStateName()).toEqual('B');
expect(host.getPreviousStateName()).toEqual('A');
expect(host.stateAExit).toHaveBeenCalled();
expect(host.stateBEntry).toHaveBeenCalled();
});
it('does not change state if a transition can not be found with the given trigger', function() {
spyOn(host, 'stateAExit');
expect(host.getCurrentStateName()).toEqual('A');
host.handleStateTrigger('eventNotExist');
expect(host.getCurrentStateName()).toEqual('A');
expect(host.stateAExit).not.toHaveBeenCalled();
});
it('calls action function if a transition can be found with the given trigger', function() {
spyOn(host, 'stateAToBAction');
host.handleStateTrigger('stateAToBEvent', [123, '123']);
expect(host.stateAToBAction).toHaveBeenCalledWith(123, '123');
});
it('calls guard function if a transition can be found with the given trigger', function() {
spyOn(host, 'stateAToBGuard');
host.handleStateTrigger('stateAToBEvent', [], [123, '123']);
expect(host.stateAToBGuard).toHaveBeenCalledWith(123, '123');
});
it('does not change state if guard function returns false', function() {
spyOn(host, 'stateAExit');
spyOn(host, 'stateAToBGuard').andReturn(false);
host.handleStateTrigger('stateAToBEvent', [], [123, '123']);
expect(host.stateAToBGuard).toHaveBeenCalledWith(123, '123');
expect(host.getCurrentStateName()).toBe('A');
expect(host.stateAExit).not.toHaveBeenCalled();
});
it('does not change state if destination does not exist', function() {
spyOn(host, 'stateAExit');
host.handleStateTrigger('stateAToNotExistEvent');
expect(host.getCurrentStateName()).toBe('A');
expect(host.stateAExit).not.toHaveBeenCalled();
});
it('chooses the correct destination based on the return value of guard function', function() {
host.handleStateTrigger('stateADupEvent');
expect(host.getCurrentStateName()).toBe('D');
});
it('changes state to first inner state automatically', function() {
spyOn(host, 'stateAExit');
spyOn(host, 'stateEEntry');
spyOn(host, 'stateFEntry');
host.handleStateTrigger('stateAToEEvent');
expect(host.getCurrentStateName()).toBe('F');
expect(host.stateAExit).toHaveBeenCalled();
expect(host.stateEEntry).toHaveBeenCalled();
expect(host.stateFEntry).toHaveBeenCalled();
});
it('changes state from inner state to outside', function() {
spyOn(host, 'stateEExit');
spyOn(host, 'stateFExit');
spyOn(host, 'stateBEntry');
host.handleStateTrigger('stateAToEEvent');
host.handleStateTrigger('stateFToBEvent');
expect(host.getCurrentStateName()).toBe('B');
expect(host.stateFExit).toHaveBeenCalled();
expect(host.stateEExit).toHaveBeenCalled();
expect(host.stateBEntry).toHaveBeenCalled();
});
});
describe('callEntryIfTransitBack', function() {
var host;
var states = [{
name: 'A',
entry: 'stateAEntry',
transitions: {
trigger: 'toSelf',
dest: 'A'
}
}];
beforeEach(function() {
host = {
stateAEntry: function() {}
};
});
it('is set to true, entry function will be called if transiting back', function() {
StateMachine.init(host, states, true);
expect(host.getCurrentStateName()).toEqual('A');
spyOn(host, 'stateAEntry');
host.handleStateTrigger('toSelf');
expect(host.stateAEntry).toHaveBeenCalled();
});
it('is set to false, entry function will not be called if transiting back', function() {
StateMachine.init(host, states, false);
expect(host.getCurrentStateName()).toEqual('A');
spyOn(host, 'stateAEntry');
host.handleStateTrigger('toSelf');
expect(host.stateAEntry).not.toHaveBeenCalled();
});
});
describe('callExitIfTransitBack', function() {
var host;
var states = [{
name: 'A',
exit: 'stateAExit',
transitions: {
trigger: 'toSelf',
dest: 'A'
}
}];
beforeEach(function() {
host = {
stateAExit: function() {}
};
});
it('is set to true, exit function will be called if transiting back', function() {
StateMachine.init(host, states, true, true);
expect(host.getCurrentStateName()).toEqual('A');
spyOn(host, 'stateAExit');
host.handleStateTrigger('toSelf');
expect(host.stateAExit).toHaveBeenCalled();
});
it('is set to false, entry function will not be called if transiting back', function() {
StateMachine.init(host, states, true, false);
expect(host.getCurrentStateName()).toEqual('A');
spyOn(host, 'stateAExit');
host.handleStateTrigger('toSelf');
expect(host.stateAExit).not.toHaveBeenCalled();
});
});
});
})(this);