2010-08-04 23:41:30 +00:00
|
|
|
require('MiddleClass')
|
|
|
|
require('MindState')
|
|
|
|
|
|
|
|
context( 'StatefulObject', function()
|
|
|
|
|
|
|
|
|
2010-08-10 22:59:48 +00:00
|
|
|
context('A State', function()
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should require 3 parameters when subclassed', function()
|
2010-08-10 22:59:48 +00:00
|
|
|
assert_error(function() State:subclass() end)
|
|
|
|
assert_error(function() State:subclass('meh') end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('Super calls should work correctly', function()
|
|
|
|
local SuperClass = class('SuperClass', StatefulObject)
|
|
|
|
function SuperClass:foo() return 'foo' end
|
|
|
|
|
|
|
|
local RootClass = class('RootClass', SuperClass)
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-10 22:59:48 +00:00
|
|
|
local State1 = RootClass:addState('State1')
|
|
|
|
function State1:foo() return(super.foo(self) .. 'state1') end
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-10 22:59:48 +00:00
|
|
|
local State2 = RootClass:addState('State2', State1)
|
|
|
|
function State2:foo() return(super.foo(self) .. 'state2') end
|
|
|
|
|
|
|
|
local obj = RootClass:new()
|
|
|
|
|
|
|
|
obj:gotoState('State1')
|
|
|
|
assert_equal(obj:foo(), 'foostate1')
|
|
|
|
|
|
|
|
obj:gotoState('State2')
|
|
|
|
assert_equal(obj:foo(), 'foostate1state2')
|
|
|
|
end)
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
context('A stateful class', function()
|
|
|
|
local Warrior = class('Warrior', StatefulObject)
|
|
|
|
local WarriorIddle, WarriorWalking
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-10 22:59:48 +00:00
|
|
|
context('When adding a new state', function()
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should not throw errors', function()
|
2010-08-10 22:59:48 +00:00
|
|
|
assert_not_error(function() WarriorIddle = Warrior:addState('Iddle') end)
|
|
|
|
function WarriorIddle:speak() return 'iddle' end
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return the existing state if it already exists', function()
|
2010-08-10 22:59:48 +00:00
|
|
|
assert_equal(Warrior:addState('Iddle'), WarriorIddle)
|
|
|
|
assert_equal(Warrior:addState('Iddle'), Warrior.states.Iddle)
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should work with superstates', function()
|
2010-08-10 22:59:48 +00:00
|
|
|
assert_not_error(function()
|
|
|
|
WarriorWalking = Warrior:addState('Walking', WarriorIddle)
|
|
|
|
end)
|
|
|
|
|
|
|
|
function WarriorWalking:walk() return 'tap tap tap' end
|
|
|
|
|
|
|
|
local novita = Warrior:new()
|
|
|
|
novita:gotoState('Walking')
|
|
|
|
|
|
|
|
assert_equal(novita:speak(), 'iddle') -- inherited from Warrioriddle
|
|
|
|
assert_equal(novita:walk(), 'tap tap tap')
|
|
|
|
assert_true(subclassOf(WarriorIddle, WarriorWalking))
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
context('When subclassing', function()
|
|
|
|
|
|
|
|
local Vehicle = class('Vehicle', StatefulObject)
|
|
|
|
Vehicle:addState('Parked')
|
|
|
|
function Vehicle.states.Parked:getStatus() return 'stopped' end
|
|
|
|
|
|
|
|
local Tank = class('Tank', Vehicle)
|
|
|
|
|
|
|
|
test('The subclass should inherit the superclass states', function()
|
|
|
|
|
|
|
|
assert_true(subclassOf(Vehicle.states.Parked, Tank.states.Parked))
|
|
|
|
|
|
|
|
panzer = Tank:new()
|
|
|
|
panzer:gotoState('Parked')
|
|
|
|
|
|
|
|
assert_equal(panzer:getStatus(), 'stopped')
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
context('When including a mixin', function()
|
|
|
|
local Class1 = class('Class1', StatefulObject)
|
|
|
|
|
|
|
|
local State1 = Class1:addState('State1')
|
|
|
|
function State1:foo() return 'state1' end
|
|
|
|
|
|
|
|
local Mixin = {
|
|
|
|
included = function(mixin, theClass) theClass.includesMixin = true end,
|
|
|
|
foo = function() return 'mixin' end,
|
|
|
|
states = {
|
|
|
|
State1 = {
|
|
|
|
bar = function() return 'bar' end
|
|
|
|
},
|
|
|
|
State2 = {
|
|
|
|
foo = function() return 'state2' end
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Class1:include(Mixin)
|
|
|
|
|
|
|
|
local obj = Class1:new()
|
|
|
|
|
|
|
|
test('should invoke the "included" method when included', function()
|
|
|
|
assert_true(Class1.includesMixin)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('should have all its functions (except "included") copied to its target class', function()
|
|
|
|
assert_equal(Class1:foo(), 'mixin')
|
|
|
|
assert_equal(Class1.included, nil)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('should have new states', function()
|
|
|
|
assert_true(subclassOf(State, Class1.states.State2))
|
|
|
|
obj:gotoState('State2')
|
|
|
|
assert_equal(obj:foo(), 'state2')
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('existing states should be modified', function()
|
|
|
|
assert_true(includes(Mixin.states.State1, Class1.states.State1))
|
|
|
|
obj:gotoState('State1')
|
|
|
|
assert_equal(obj:foo(), 'state1')
|
|
|
|
assert_equal(obj:bar(), 'bar')
|
|
|
|
end)
|
|
|
|
|
2010-08-10 22:59:48 +00:00
|
|
|
end)
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
context('A stateful instance', function()
|
|
|
|
|
|
|
|
local Enemy = class('Enemy', StatefulObject)
|
|
|
|
function Enemy:getStatus() return 'none' end
|
|
|
|
|
|
|
|
local EnemyIddle = Enemy:addState('Iddle')
|
|
|
|
function EnemyIddle:enterState() self.enteredIddle = true end
|
|
|
|
function EnemyIddle:getStatus() return 'iddling' end
|
|
|
|
|
|
|
|
local Goblin = class('Goblin', Enemy)
|
|
|
|
|
|
|
|
local GoblinIddle = Goblin:addState('Iddle')
|
|
|
|
function GoblinIddle:getStatus() return 'me bored boss' end
|
|
|
|
function GoblinIddle:exitState() self.exitedIddle = true end
|
|
|
|
function GoblinIddle:pausedState() self.pausedIddle = true end
|
|
|
|
function GoblinIddle:poppedState() self.poppedIddle = true end
|
|
|
|
function GoblinIddle:continuedState() self.continuedIddle = true end
|
|
|
|
|
|
|
|
local GoblinAttacking = Goblin:addState('Attacking')
|
|
|
|
function GoblinAttacking:pushedState() self.pushedAttacking = true end
|
|
|
|
function GoblinAttacking:enterState() self.enteredAttacking = true end
|
|
|
|
function GoblinAttacking:shout() return 'gnaaa!' end
|
|
|
|
function GoblinAttacking:poppedState() self.poppedAttacking = true end
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-05 17:26:19 +00:00
|
|
|
context('When it goes from one state to another', function()
|
|
|
|
local albert = Enemy:new()
|
|
|
|
albert:gotoState('Iddle')
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-05 17:26:19 +00:00
|
|
|
local chester = Goblin:new()
|
|
|
|
chester:gotoState('Iddle')
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should not throw an error for a valid state name', function()
|
2010-08-04 23:41:30 +00:00
|
|
|
assert_not_error(function() chester:gotoState('Attacking') end)
|
|
|
|
assert_equal(chester:shout(), 'gnaaa!')
|
|
|
|
end)
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should throw an error for an invalid state name', function()
|
2010-08-05 17:26:19 +00:00
|
|
|
assert_error(function() albert:gotoState('Sleeping') end)
|
|
|
|
end)
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should be able to go to the nil-state', function()
|
2010-08-04 23:41:30 +00:00
|
|
|
assert_not_error(function() albert:gotoState(nil) end)
|
|
|
|
assert_equal(albert:getStatus(), 'none')
|
|
|
|
end)
|
|
|
|
test('enterState callbacks should be called, if existing', function()
|
|
|
|
assert_true(albert.enteredIddle)
|
|
|
|
assert_true(chester.enteredIddle)
|
|
|
|
end)
|
2010-08-05 17:26:19 +00:00
|
|
|
test('exitState callbacks should be called, if existing', function()
|
2010-08-04 23:41:30 +00:00
|
|
|
assert_nil(albert.exitedIddle)
|
|
|
|
assert_true(chester.exitedIddle)
|
|
|
|
end)
|
|
|
|
end)
|
2010-08-05 17:26:19 +00:00
|
|
|
|
2010-08-06 09:36:30 +00:00
|
|
|
context('When pushing states', function()
|
2010-08-05 17:26:19 +00:00
|
|
|
local andrew = Enemy:new()
|
|
|
|
local ian = Goblin:new()
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should error if the statename is invalid', function()
|
2010-08-05 17:26:19 +00:00
|
|
|
assert_error(function() andrew:pushState(nil) end)
|
|
|
|
assert_error(function() andrew:pushState('Sleeping') end)
|
|
|
|
end)
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should correctly push valid states, without errors', function()
|
2010-08-05 17:26:19 +00:00
|
|
|
assert_not_error(function() ian:pushState('Iddle') end)
|
|
|
|
assert_not_error(function() ian:pushState('Attacking') end)
|
|
|
|
assert_equal(ian:shout(), 'gnaaa!')
|
|
|
|
assert_equal(ian:getStatus(), 'me bored boss')
|
|
|
|
end)
|
|
|
|
test('pushing the same state several times should not throw errors', function()
|
|
|
|
assert_not_error(function() andrew:pushState('Iddle') end)
|
|
|
|
assert_not_error(function() andrew:pushState('Iddle') end)
|
|
|
|
end)
|
|
|
|
test('enterState callbacks should be called, if existing', function()
|
|
|
|
assert_true(andrew.enteredIddle)
|
|
|
|
assert_true(ian.enteredIddle)
|
|
|
|
end)
|
|
|
|
test('pushedState callbacks should be called, if existing', function()
|
|
|
|
assert_nil(andrew.pushedAttacking)
|
|
|
|
assert_true(ian.pushedAttacking)
|
|
|
|
end)
|
|
|
|
test('pausedState callbacks should be called, if existing', function()
|
|
|
|
assert_nil(andrew.pausedIddle)
|
|
|
|
assert_true(ian.pausedIddle)
|
|
|
|
end)
|
|
|
|
end)
|
2010-08-04 23:41:30 +00:00
|
|
|
|
2010-08-06 09:36:30 +00:00
|
|
|
context('When popping states', function()
|
|
|
|
local renfield = Goblin:new()
|
|
|
|
|
|
|
|
-- reset vlad and renfield before each test
|
|
|
|
before(function()
|
|
|
|
renfield = Goblin:new()
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should be able to pop states by name, calling callbacks', function()
|
2010-08-06 09:36:30 +00:00
|
|
|
renfield:pushState('Iddle')
|
|
|
|
renfield:pushState('Attacking')
|
|
|
|
|
|
|
|
assert_not_error(function() renfield:popState('Iddle') end)
|
|
|
|
assert_equal(renfield:getStatus(), 'none')
|
|
|
|
assert_equal(renfield:shout(), 'gnaaa!')
|
|
|
|
assert_true(renfield.poppedIddle)
|
|
|
|
assert_true(renfield.exitedIddle)
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should be able to pop the top state, with appropiate callbacks', function()
|
2010-08-06 09:36:30 +00:00
|
|
|
renfield:pushState('Iddle')
|
|
|
|
renfield:pushState('Attacking')
|
|
|
|
|
|
|
|
assert_not_error(function() renfield:popState() end)
|
|
|
|
assert_equal(renfield:getStatus(), 'me bored boss')
|
|
|
|
assert_error(function() renfield:shout() end)
|
|
|
|
assert_true(renfield.poppedAttacking)
|
|
|
|
assert_true(renfield.continuedIddle)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('popping the same state several times should not throw errors, and call no callbacks', function()
|
|
|
|
renfield:pushState('Iddle')
|
|
|
|
renfield:popState('Iddle')
|
|
|
|
renfield.poppedIddle = nil
|
|
|
|
assert_not_error(function() renfield:popState('Iddle') end)
|
|
|
|
assert_nil(renfield.poppedIddle)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('popping inexistant states should not throw errors', function()
|
|
|
|
assert_not_error(function() renfield:popState('Foo') end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
test('popAllStates should work correctly and invoke all required callbacks', function()
|
|
|
|
renfield:pushState('Iddle')
|
|
|
|
renfield:pushState('Attacking')
|
|
|
|
|
|
|
|
assert_not_error(function() renfield:popAllStates() end)
|
|
|
|
assert_true(renfield.poppedAttacking)
|
|
|
|
assert_true(renfield.continuedIddle)
|
|
|
|
assert_true(renfield.poppedIddle)
|
|
|
|
assert_true(renfield.exitedIddle)
|
|
|
|
end)
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
context('When testing whether it is on one state', function()
|
|
|
|
local igor = Goblin:new()
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return true if the state is on the top of the stack', function()
|
2010-08-06 18:05:50 +00:00
|
|
|
igor:gotoState('Iddle')
|
|
|
|
assert_true(igor:isInState('Iddle'))
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return true if the state is on the stack and "testStateStack" is true', function()
|
2010-08-06 18:05:50 +00:00
|
|
|
igor:pushState('Attacking')
|
|
|
|
assert_true(igor:isInState('Attacking'))
|
|
|
|
assert_true(igor:isInState('Iddle', true))
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return false otherwise', function()
|
2010-08-06 18:05:50 +00:00
|
|
|
assert_false(igor:isInState(nil))
|
|
|
|
assert_false(igor:isInState('Foo', true))
|
|
|
|
end)
|
2010-08-06 09:36:30 +00:00
|
|
|
end)
|
|
|
|
|
2010-08-06 18:05:50 +00:00
|
|
|
context('When getting the current state name', function()
|
|
|
|
local peppy = Goblin:new()
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return nil when on nil-state', function()
|
2010-08-06 18:05:50 +00:00
|
|
|
assert_nil(peppy:getCurrentStateName())
|
|
|
|
end)
|
|
|
|
|
2010-08-11 22:31:07 +00:00
|
|
|
test('should return the top-of-the-stack statename otherwise', function()
|
2010-08-06 18:05:50 +00:00
|
|
|
peppy:pushState('Iddle')
|
|
|
|
assert_equal(peppy:getCurrentStateName(), 'Iddle')
|
|
|
|
peppy:pushState('Attacking')
|
|
|
|
assert_equal(peppy:getCurrentStateName(), 'Attacking')
|
|
|
|
end)
|
|
|
|
end)
|
2010-08-06 09:36:30 +00:00
|
|
|
|
|
|
|
end) -- context 'An Instance'
|
2010-08-10 22:59:48 +00:00
|
|
|
|
2010-08-04 23:41:30 +00:00
|
|
|
end)
|
|
|
|
|