mirror of
https://github.com/kikito/middleclass.git
synced 2024-11-25 02:44:20 +00:00
added more tests for mindState and fixed minor issues
This commit is contained in:
parent
0cdd3bacbb
commit
68e1f779df
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user