added more tests for mindState and fixed minor issues

This commit is contained in:
kikito 2010-08-11 00:59:48 +02:00
parent 0cdd3bacbb
commit 68e1f779df
3 changed files with 128 additions and 40 deletions

View File

@ -118,8 +118,8 @@ function includes(module, aClass)
return includes(module, aClass.superclass)
end
-- Creates a new class named 'name'. It uses baseClass as the parent (Object if none specified)
function class(name, baseClass)
-- Creates a new class named 'name'. Uses Object if no baseClass is specified. Additional parameters for compatibility
function class(name, baseClass, ...)
baseClass = baseClass or Object
return baseClass:subclass(name)
return baseClass:subclass(name, ...)
end

View File

@ -52,12 +52,9 @@ local _ignoredMethods = {
states=1, initialize=1,
gotoState=1, pushState=1, popState=1, popAllStates=1, isInState=1,
enterState=1, exitState=1, pushedState=1, poppedState=1, pausedState=1, continuedState=1,
addState=1, subclass=1, includes=1, destroy=1, getCurrentStateName=1
addState=1, subclass=1, include=1, destroy=1, getCurrentStateName=1
}
local _prevSubclass = StatefulObject.subclass -- previous way of creating subclasses (used to redefine subclass itself)
------------------------------------
-- STATE CLASS
------------------------------------
@ -65,11 +62,17 @@ local _prevSubclass = StatefulObject.subclass -- previous way of creating subcla
-- The State class; is the father of all State objects
State = class('State', Object)
function State.subclass(theClass, name, theStatefulClass)
-- subclass takes an extra parameter: theRootClass the class where the state is being added
-- It is used for method lookup
function State.subclass(theClass, name, theRootClass)
assert(type(name) == 'string', "Must provide a name for the new state")
assert(subclassOf(StatefulObject, theRootClass), "Must provide a stateful object subclass")
local theSubClass = Object.subclass(theClass, name)
local superDict = (theClass==State and theClass.__classDict or theStatefulClass.superclass.__classDict)
local superDict = (theClass==State and theRootClass.superclass.__classDict or theClass.__classDict )
theSubClass.subclass = State.subclass
-- Modify super so it points to either the SuperState or RootClass if we are subclassing State
local mt = getmetatable(theSubClass)
mt.__newindex = function(_, methodName, method)
if type(method) == 'function' then
@ -253,11 +256,11 @@ end
]]
function StatefulObject.addState(theClass, stateName, superState)
superState = superState or State
--print(theClass.name, stateName, superState.name)
assert(subclassOf(StatefulObject, theClass), "Use class:addState instead of class.addState")
assert(type(stateName)=="string", "stateName must be a string")
local prevState = theClass.states[stateName]
local prevState = rawget(theClass.states, stateName)
if prevState~=nil then return prevState end
-- states are just regular classes. If superState is nil, this uses Object as superClass
@ -274,11 +277,11 @@ end
function StatefulObject.subclass(theClass, name)
assert(theClass==StatefulObject or subclassOf(StatefulObject, theClass), "Use class:subclass instead of class.subclass")
local theSubClass = _prevSubclass(theClass, name) --for now, theClass is just a regular subclass
local theSubClass = Object.subclass(theClass, name) --for now, theClass is just a regular subclass
--the states of the subclass are subclasses of the superclass' states
theSubClass.states = {}
for stateName,state in pairs(theClass.states) do
for stateName,state in pairs(theClass.states) do
theSubClass:addState(stateName, state)
end
@ -307,19 +310,19 @@ end
then each member of that module.states is included on the StatefulObject class.
If module.states has a state that doesn't exist on StatefulObject, a new state will be created.
]]
function StatefulObject.includes(theClass, module, ...)
function StatefulObject.include(theClass, module, ...)
assert(subclassOf(StatefulObject, theClass), "Use class:includes instead of class.includes")
for methodName,method in pairs(module) do
if methodName ~="included" and methodName ~= "states" then
theClass[methodName] = method
end
end
if type(module.included)=="function" then module.included(theClass, ...) end
if type(module.included)=="function" then module:included(theClass, ...) end
if type(module.states)=="table" then
for stateName,moduleState in pairs(module.states) do
local state = theClass.states[stateName]
if state == nil then state = theClass:addState(stateName) end
state:includes(moduleState, ...)
state:include(moduleState, ...)
end
end
end

View File

@ -3,29 +3,117 @@ require('MindState')
context( 'StatefulObject', 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
context('A State', function()
test('it should require 3 parameters when subclassed', function()
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)
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
local State1 = RootClass:addState('State1')
function State1:foo() return(super.foo(self) .. 'state1') end
context('An instance', function()
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
context('When adding a new state', function()
test('it should not throw errors', function()
assert_not_error(function() WarriorIddle = Warrior:addState('Iddle') end)
function WarriorIddle:speak() return 'iddle' end
end)
test('it returns an existing state if it already exists', function()
assert_equal(Warrior:addState('Iddle'), WarriorIddle)
assert_equal(Warrior:addState('Iddle'), Warrior.states.Iddle)
end)
test('it should work with superstates', function()
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)
context('When instantiating', function()
-- pending
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
context('When it goes from one state to another', function()
local albert = Enemy:new()
@ -179,13 +267,10 @@ context( 'StatefulObject', function()
end) -- context 'An Instance'
context('A mixin on a stateful object', function()
-- pending
end)
context('A State', function()
context('A mixin included on a stateful object', function()
-- pending
end)
end)