mirror of
https://github.com/kikito/middleclass.git
synced 2024-11-25 02:44:20 +00:00
merged branch 2.0
This commit is contained in:
commit
3f9ae1d6e8
@ -22,5 +22,4 @@ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -5,7 +5,7 @@ Lua OOP classes usually end being:
|
|||||||
* multi-file libraries, too difficult to understand
|
* multi-file libraries, too difficult to understand
|
||||||
* very small libraries, not very powerful
|
* very small libraries, not very powerful
|
||||||
|
|
||||||
Middleclass attemps to be a mid-sized library (~120 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
|
Middleclass attemps to be a mid-sized library (~130 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
|
||||||
|
|
||||||
h1. Documentation
|
h1. Documentation
|
||||||
|
|
||||||
@ -13,12 +13,13 @@ See the "github wiki page":https://github.com/kikito/middleclass/wiki for exampl
|
|||||||
|
|
||||||
h1. Features
|
h1. Features
|
||||||
|
|
||||||
* ~120 lines of code
|
* ~130 lines of code
|
||||||
* top-level Object class
|
* top-level Object class
|
||||||
* all methods are virtual
|
* all methods are virtual
|
||||||
* instance.class returns the instance's class
|
* instance.class returns the instance's class
|
||||||
* @Class.name@ returns the class name (a string)
|
* @Class.name@ returns the class name (a string)
|
||||||
* @Class.superclass@ returns its super class
|
* @Class.super@ returns its super class
|
||||||
|
* Class methods can be defined with Class.static.methodname
|
||||||
* Subclassing:
|
* Subclassing:
|
||||||
** @class(name)@ creates a subclass of @Object@
|
** @class(name)@ creates a subclass of @Object@
|
||||||
** @class(name, Superclass)@ creates a subclass of the class @SuperClass@
|
** @class(name, Superclass)@ creates a subclass of the class @SuperClass@
|
||||||
@ -43,47 +44,19 @@ Features left out:
|
|||||||
* classes are not Objects (instances are)
|
* classes are not Objects (instances are)
|
||||||
* simulating a 'super' keyword (for performance concerns)
|
* simulating a 'super' keyword (for performance concerns)
|
||||||
|
|
||||||
h1. Installation using git (recommended)
|
h1. Installation
|
||||||
|
|
||||||
The easiest way is creating a clone using git:
|
Just copy the middleclass.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it:
|
||||||
|
|
||||||
<pre>git clone git://github.com/kikito/middleclass.git</pre>
|
|
||||||
|
|
||||||
This will create a folder called @middleclass@. You can require it using:
|
|
||||||
|
|
||||||
<pre>require 'middleclass.init'</pre>
|
|
||||||
|
|
||||||
If you have @?/init.lua@ included in your @package.path@ variable, you can remove the @.init@ part.
|
|
||||||
|
|
||||||
If you ever want to update middleclass, you can use the following commands:
|
|
||||||
|
|
||||||
<pre>cd your/path/to/middleclass
|
|
||||||
git pull origin master</pre>
|
|
||||||
|
|
||||||
Git will automatically update middleclass for you.
|
|
||||||
|
|
||||||
h1. Manual download
|
|
||||||
|
|
||||||
Middleclass can be directly downloaded using "the github downloader":https://github.com/kikito/middleclass/archives/master
|
|
||||||
|
|
||||||
You will not be able to get automatic updates with that method, but your folder size will be smaller.
|
|
||||||
|
|
||||||
As an alternative, you can directly download "middleclass.lua":https://github.com/kikito/middleclass/raw/master/middleclass.lua and "BSD-LICENSE.txt":https://github.com/kikito/middleclass/raw/master/BSD-LICENSE.txt and put them in your project.
|
|
||||||
|
|
||||||
In that case, you will have to require it doing something like this (will vary if you put middleclass.lua inside a folder):
|
|
||||||
|
|
||||||
<pre>require 'middleclass'</pre>
|
<pre>require 'middleclass'</pre>
|
||||||
|
|
||||||
(This assumes you have @?.lua@ in your @package.path@, which is the default)
|
Make sure that you read the license, too. In order to comply with it you will have to include its full text inside your program's documentation, or else make it visible in the app's credits.
|
||||||
|
|
||||||
You will have to put BSD-LICENSE.txt somewhere in your folder, or copy-paste it inside of your own license.txt file.
|
|
||||||
|
|
||||||
h1. middleclass-extras
|
|
||||||
|
|
||||||
This library has a companion lib that adds a lot of interesting functionality to your objects. Give it a look at "middleclass-extras":https://github.com/kikito/middleclass-extras
|
|
||||||
|
|
||||||
If you are looking for @MindState@ (now called @Stateful@), it's over there, too.
|
|
||||||
|
|
||||||
h1. Specs
|
h1. Specs
|
||||||
|
|
||||||
You may find the specs for this library in "middleclass-specs":https://github.com/kikito/middleclass-specs
|
This project uses "telescope":https://github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just execute the following from the root inspect folder:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
tsc -f spec/*
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
258
middleclass.lua
258
middleclass.lua
@ -1,120 +1,138 @@
|
|||||||
-----------------------------------------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
-- middleclass.lua - v1.4 (2011-03)
|
-- middleclass.lua - v2.0 (2011-08)
|
||||||
-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com
|
-- Enrique Garcia Cota - enrique.garcia.cota [AT] gmail [DOT] com
|
||||||
-- Based on YaciCode, from Julien Patte and LuaObject, from Sébastien Rocca-Serra
|
-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra
|
||||||
-----------------------------------------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
local _nilf = function() end -- empty function
|
local _classes = setmetatable({}, {__mode = "k"})
|
||||||
|
|
||||||
local _classes = setmetatable({}, {__mode = "k"}) -- keeps track of all the tables that are classes
|
local function _setClassDictionariesMetatables(klass)
|
||||||
|
local dict = klass.__instanceDict
|
||||||
Object = { name = "Object", __mixins = {} }
|
dict.__index = dict
|
||||||
|
|
||||||
Object.__classDict = {
|
local super = klass.super
|
||||||
initialize = _nilf, destroy = _nilf, subclassed = _nilf,
|
if super then
|
||||||
__tostring = function(instance) return ("instance of ".. instance.class.name) end, -- root of __tostring method,
|
local superStatic = super.static
|
||||||
__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
|
setmetatable(dict, super.__instanceDict)
|
||||||
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
|
setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end })
|
||||||
}
|
else
|
||||||
}
|
setmetatable(klass.static, { __index = function(_,k) return dict[k] end })
|
||||||
Object.__classDict.__index = Object.__classDict -- instances of Object need this
|
end
|
||||||
|
end
|
||||||
setmetatable(Object, {
|
|
||||||
__index = Object.__classDict, -- look up methods in the classDict
|
local function _setClassMetatable(klass)
|
||||||
__newindex = Object.__classDict, -- any new Object methods will be defined in classDict
|
setmetatable(klass, {
|
||||||
__call = Object.new, -- allows instantiation via Object()
|
__tostring = function() return "class " .. klass.name end,
|
||||||
__tostring = function() return "class Object" end -- allows tostring(obj)
|
__index = klass.static,
|
||||||
})
|
__newindex = klass.__instanceDict,
|
||||||
|
__call = function(self, ...) return self:new(...) end
|
||||||
_classes[Object] = true -- register Object on the list of classes.
|
})
|
||||||
|
end
|
||||||
-- creates the instance based of the class, but doesn't initialize it
|
|
||||||
function Object.allocate(theClass)
|
local function _createClass(name, super)
|
||||||
assert(_classes[theClass], "Use Class:allocate instead of Class.allocate")
|
local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
|
||||||
return setmetatable({ class = theClass }, theClass.__classDict)
|
klass.subclasses = setmetatable({}, {__mode = "k"})
|
||||||
end
|
|
||||||
|
_setClassDictionariesMetatables(klass)
|
||||||
-- both creates and initializes an instance
|
_setClassMetatable(klass)
|
||||||
function Object.new(theClass, ...)
|
_classes[klass] = true
|
||||||
local instance = theClass:allocate()
|
|
||||||
instance:initialize(...)
|
return klass
|
||||||
return instance
|
end
|
||||||
end
|
|
||||||
|
local function _createLookupMetamethod(klass, name)
|
||||||
-- creates a subclass
|
return function(...)
|
||||||
function Object.subclass(theClass, name)
|
local method = klass.super[name]
|
||||||
assert(_classes[theClass], "Use Class:subclass instead of Class.subclass")
|
assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" )
|
||||||
assert( type(name)=="string", "You must provide a name(string) for your class")
|
return method(...)
|
||||||
|
end
|
||||||
local theSubClass = { name = name, superclass = theClass, __classDict = {}, __mixins={} }
|
end
|
||||||
|
|
||||||
local dict = theSubClass.__classDict -- classDict contains all the [meta]methods of the class
|
local function _setClassMetamethods(klass)
|
||||||
dict.__index = dict -- It "points to itself" so instances can use it as a metatable.
|
for _,m in ipairs(klass.__metamethods) do
|
||||||
local superDict = theClass.__classDict -- The superclass' classDict
|
klass[m]= _createLookupMetamethod(klass, m)
|
||||||
|
end
|
||||||
setmetatable(dict, superDict) -- when a method isn't found on classDict, 'escalate upwards'.
|
end
|
||||||
|
|
||||||
setmetatable(theSubClass, {
|
local function _setDefaultInitializeMethod(klass, super)
|
||||||
__index = dict, -- look for stuff on the dict
|
klass.initialize = function(instance, ...)
|
||||||
__newindex = function(_, methodName, method) -- ensure that __index isn't modified by mistake
|
return super.initialize(instance, ...)
|
||||||
assert(methodName ~= '__index', "Can't modify __index. Include middleclass-extras.Indexable and use 'index' instead")
|
end
|
||||||
rawset(dict, methodName , method)
|
end
|
||||||
end,
|
|
||||||
__tostring = function() return ("class ".. name) end, -- allows tostring(MyClass)
|
local function _includeMixin(klass, mixin)
|
||||||
__call = function(_, ...) return theSubClass:new(...) end -- allows MyClass(...) instead of MyClass:new(...)
|
assert(type(mixin)=='table', "mixin must be a table")
|
||||||
})
|
for name,method in pairs(mixin) do
|
||||||
|
if name ~= "included" and name ~= "static" then klass[name] = method end
|
||||||
for _,mmName in ipairs(theClass.__metamethods) do -- Creates the initial metamethods
|
end
|
||||||
dict[mmName]= function(...) -- by default, they just 'look up' for an implememtation
|
if mixin.static then
|
||||||
local method = superDict[mmName] -- and if none found, they throw an error
|
for name,method in pairs(mixin.static) do
|
||||||
assert( type(method)=='function', tostring(theSubClass) .. " doesn't implement metamethod '" .. mmName .. "'" )
|
klass.static[name] = method
|
||||||
return method(...)
|
end
|
||||||
end
|
end
|
||||||
end
|
if type(mixin.included)=="function" then mixin:included(klass) end
|
||||||
|
klass.__mixins[mixin] = true
|
||||||
theSubClass.initialize = function(instance,...) theClass.initialize(instance, ...) end
|
end
|
||||||
_classes[theSubClass]= true -- registers the new class on the list of _classes
|
|
||||||
theClass:subclassed(theSubClass) -- hook method. By default it does nothing
|
Object = _createClass("Object", nil)
|
||||||
|
|
||||||
return theSubClass
|
Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
|
||||||
end
|
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' }
|
||||||
|
|
||||||
-- Mixin extension function - simulates very basically ruby's include. Receives a table table, probably with functions.
|
function Object.static:allocate()
|
||||||
-- Its contents are copied to theClass, with one exception: the included() method will be called instead of copied
|
assert(_classes[self], "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||||
function Object.include(theClass, mixin, ... )
|
return setmetatable({ class = self }, self.__instanceDict)
|
||||||
assert(_classes[theClass], "Use class:include instead of class.include")
|
end
|
||||||
assert(type(mixin)=='table', "mixin must be a table")
|
|
||||||
for methodName,method in pairs(mixin) do
|
function Object.static:new(...)
|
||||||
if methodName ~="included" then theClass[methodName] = method end
|
local instance = self:allocate()
|
||||||
end
|
instance:initialize(...)
|
||||||
if type(mixin.included)=="function" then mixin:included(theClass, ... ) end
|
return instance
|
||||||
theClass.__mixins[mixin] = mixin
|
end
|
||||||
return theClass
|
|
||||||
end
|
function Object.static:subclass(name)
|
||||||
|
assert(_classes[self], "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
|
||||||
-- Returns true if aClass is a subclass of other, false otherwise
|
assert(type(name) == "string", "You must provide a name(string) for your class")
|
||||||
function subclassOf(other, aClass)
|
|
||||||
if not _classes[aClass] or not _classes[other] then return false end
|
local subclass = _createClass(name, self)
|
||||||
if aClass.superclass==nil then return false end -- aClass is Object, or a non-class
|
_setClassMetamethods(subclass)
|
||||||
return aClass.superclass == other or subclassOf(other, aClass.superclass)
|
_setDefaultInitializeMethod(subclass, self)
|
||||||
end
|
self.subclasses[subclass] = true
|
||||||
|
self:subclassed(subclass)
|
||||||
-- Returns true if obj is an instance of aClass (or one of its subclasses) false otherwise
|
|
||||||
function instanceOf(aClass, obj)
|
return subclass
|
||||||
if not _classes[aClass] or type(obj)~='table' or not _classes[obj.class] then return false end
|
end
|
||||||
if obj.class==aClass then return true end
|
|
||||||
return subclassOf(aClass, obj.class)
|
function Object.static:subclassed(other) end
|
||||||
end
|
|
||||||
|
function Object.static:include( ... )
|
||||||
-- Returns true if the mixin has already been included on a class (or a superclass)
|
assert(_classes[self], "Make sure you that you are using 'Class:include' instead of 'Class.include'")
|
||||||
function includes(mixin, aClass)
|
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
|
||||||
if not _classes[aClass] then return false end
|
return self
|
||||||
if aClass.__mixins[mixin]==mixin then return true end
|
end
|
||||||
return includes(mixin, aClass.superclass)
|
|
||||||
end
|
function Object:initialize() end
|
||||||
|
|
||||||
-- Creates a new class named 'name'. Uses Object if no baseClass is specified.
|
function Object:__tostring() return "instance of " .. tostring(self.class) end
|
||||||
function class(name, baseClass, ...)
|
|
||||||
baseClass = baseClass or Object
|
function class(name, super, ...)
|
||||||
return baseClass:subclass(name, ...)
|
super = super or Object
|
||||||
end
|
return super:subclass(name, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function instanceOf(aClass, obj)
|
||||||
|
if not _classes[aClass] or type(obj) ~= 'table' or not _classes[obj.class] then return false end
|
||||||
|
if obj.class == aClass then return true end
|
||||||
|
return subclassOf(aClass, obj.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
function subclassOf(other, aClass)
|
||||||
|
if not _classes[aClass] or not _classes[other] or aClass.super == nil then return false end
|
||||||
|
return aClass.super == other or subclassOf(other, aClass.super)
|
||||||
|
end
|
||||||
|
|
||||||
|
function includes(mixin, aClass)
|
||||||
|
if not _classes[aClass] then return false end
|
||||||
|
if aClass.__mixins[mixin] then return true end
|
||||||
|
return includes(mixin, aClass.super)
|
||||||
|
end
|
||||||
|
43
performance/run.lua
Normal file
43
performance/run.lua
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
time = require 'performance/time'
|
||||||
|
|
||||||
|
time('class creation', function()
|
||||||
|
local A = class('A')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local A = class('A')
|
||||||
|
|
||||||
|
time('instance creation', function()
|
||||||
|
local a = A:new()
|
||||||
|
end)
|
||||||
|
|
||||||
|
function A:foo()
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local a = A:new()
|
||||||
|
|
||||||
|
time('instance method invocation', function()
|
||||||
|
a:foo()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local B = class('B', A)
|
||||||
|
|
||||||
|
local b = B:new()
|
||||||
|
|
||||||
|
time('inherited method invocation', function()
|
||||||
|
b:foo()
|
||||||
|
end)
|
||||||
|
|
||||||
|
function A.static:bar()
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
|
||||||
|
time('class method invocation', function()
|
||||||
|
A:bar()
|
||||||
|
end)
|
||||||
|
|
||||||
|
time('inherited class method invocation', function()
|
||||||
|
B:bar()
|
||||||
|
end)
|
13
performance/time.lua
Normal file
13
performance/time.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
return function(title, f)
|
||||||
|
|
||||||
|
collectgarbage()
|
||||||
|
|
||||||
|
local startTime = os.clock()
|
||||||
|
|
||||||
|
for i=0,10000 do f() end
|
||||||
|
|
||||||
|
local endTime = os.clock()
|
||||||
|
|
||||||
|
print( title, endTime - startTime )
|
||||||
|
|
||||||
|
end
|
125
spec/Object_spec.lua
Normal file
125
spec/Object_spec.lua
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('Object', function()
|
||||||
|
|
||||||
|
|
||||||
|
context('name', function()
|
||||||
|
test('is correctly set', function()
|
||||||
|
assert_equal(Object.name, 'Object')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('tostring', function()
|
||||||
|
test('returns "class Object"', function()
|
||||||
|
assert_equal(tostring(Object), 'class Object')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('()', function()
|
||||||
|
test('returns an object, like Object:new()', function()
|
||||||
|
local obj = Object()
|
||||||
|
assert_true(instanceOf(Object, obj))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('subclass', function()
|
||||||
|
|
||||||
|
test('throws an error when used without the :', function()
|
||||||
|
assert_error(function() Object.subclass() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('throws an error when no name is given', function()
|
||||||
|
assert_error( function() Object:subclass() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('when given a class name', function()
|
||||||
|
|
||||||
|
local SubClass
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
SubClass = Object:subclass('SubClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it returns a class with the correct name', function()
|
||||||
|
assert_equal(SubClass.name, 'SubClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it returns a class with the correct superclass', function()
|
||||||
|
assert_equal(SubClass.super, Object)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it includes the subclass in the list of subclasses', function()
|
||||||
|
assert_true(Object.subclasses[SubClass])
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('instance creation', function()
|
||||||
|
|
||||||
|
local SubClass
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
SubClass = class('SubClass')
|
||||||
|
function SubClass:initialize() self.mark=true end
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('allocate', function()
|
||||||
|
|
||||||
|
test('allocates instances properly', function()
|
||||||
|
local instance = SubClass:allocate()
|
||||||
|
assert_equal(instance.class, SubClass)
|
||||||
|
assert_equal(tostring(instance), "instance of " .. tostring(SubClass))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('throws an error when used without the :', function()
|
||||||
|
assert_error(Object.allocate)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('does not call the initializer', function()
|
||||||
|
local allocated = SubClass:allocate()
|
||||||
|
assert_nil(allocated.mark)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('can be overriden', function()
|
||||||
|
|
||||||
|
local previousAllocate = SubClass.static.allocate
|
||||||
|
|
||||||
|
function SubClass.static:allocate()
|
||||||
|
local instance = previousAllocate(SubClass)
|
||||||
|
instance.mark = true
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
|
||||||
|
local allocated = SubClass:allocate()
|
||||||
|
assert_true(allocated.mark)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('new', function()
|
||||||
|
|
||||||
|
test('initializes instances properly', function()
|
||||||
|
local instance = SubClass:new()
|
||||||
|
assert_equal(instance.class, SubClass)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('throws an error when used without the :', function()
|
||||||
|
assert_error(SubClass.new)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('calls the initializer', function()
|
||||||
|
local initialized = SubClass:new()
|
||||||
|
assert_true(initialized.mark)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
40
spec/class_spec.lua
Normal file
40
spec/class_spec.lua
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('class()', function()
|
||||||
|
|
||||||
|
context('when given no params', function()
|
||||||
|
test('it throws an error', function()
|
||||||
|
assert_error(class)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('when given a name', function()
|
||||||
|
local TheClass
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
TheClass = class('TheClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('the resulting class has the correct name', function()
|
||||||
|
assert_equal(TheClass.name, 'TheClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('the resulting class has Object as its superclass', function()
|
||||||
|
assert_equal(TheClass.super, Object)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('when given a name and a superclass', function()
|
||||||
|
local TheSuperClass = class('TheSuperClass')
|
||||||
|
local TheSubClass = class('TheSubClass', TheSuperClass)
|
||||||
|
|
||||||
|
test('the resulting class has the correct name', function()
|
||||||
|
assert_equal(TheSubClass.name, 'TheSubClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('the resulting class has the correct superclass', function()
|
||||||
|
assert_equal(TheSubClass.super, TheSuperClass)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
138
spec/classes_spec.lua
Normal file
138
spec/classes_spec.lua
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('A Class', function()
|
||||||
|
|
||||||
|
context('Default stuff', function()
|
||||||
|
|
||||||
|
local AClass
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
AClass = class('AClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('name', function()
|
||||||
|
test('is correctly set', function()
|
||||||
|
assert_equal(AClass.name, 'AClass')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('tostring', function()
|
||||||
|
test('returns "class *name*"', function()
|
||||||
|
assert_equal(tostring(AClass), 'class AClass')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('()', function()
|
||||||
|
test('returns an object, like Class:new()', function()
|
||||||
|
local obj = AClass()
|
||||||
|
assert_equal(obj.class, AClass)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('include', function()
|
||||||
|
test('throws an error when used without the :', function()
|
||||||
|
assert_error(function() AClass.include() end)
|
||||||
|
end)
|
||||||
|
test('throws an error when passed a non-table:', function()
|
||||||
|
assert_error(function() AClass:include(1) end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('subclass', function()
|
||||||
|
|
||||||
|
test('throws an error when used without the :', function()
|
||||||
|
assert_error(function() AClass.subclass() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('throws an error when no name is given', function()
|
||||||
|
assert_error( function() AClass:subclass() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('when given a subclass name', function()
|
||||||
|
|
||||||
|
local SubClass
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
function AClass.static:subclassed(other) self.static.child = other end
|
||||||
|
SubClass = AClass:subclass('SubClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it returns a class with the correct name', function()
|
||||||
|
assert_equal(SubClass.name, 'SubClass')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it returns a class with the correct superclass', function()
|
||||||
|
assert_equal(SubClass.super, AClass)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it invokes the subclassed hook method', function()
|
||||||
|
assert_equal(SubClass, AClass.child)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('it includes the subclass in the list of subclasses', function()
|
||||||
|
assert_true(AClass.subclasses[SubClass])
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
context('attributes', function()
|
||||||
|
|
||||||
|
local A, B
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
A = class('A')
|
||||||
|
A.static.foo = 'foo'
|
||||||
|
|
||||||
|
B = class('B', A)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available after being initialized', function()
|
||||||
|
assert_equal(A.foo, 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available for subclasses', function()
|
||||||
|
assert_equal(B.foo, 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are overridable by subclasses, without affecting the superclasses', function()
|
||||||
|
B.static.foo = 'chunky bacon'
|
||||||
|
assert_equal(B.foo, 'chunky bacon')
|
||||||
|
assert_equal(A.foo, 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('methods', function()
|
||||||
|
|
||||||
|
local A, B
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
A = class('A')
|
||||||
|
function A.static:foo() return 'foo' end
|
||||||
|
|
||||||
|
B = class('B', A)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available after being initialized', function()
|
||||||
|
assert_equal(A:foo(), 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available for subclasses', function()
|
||||||
|
assert_equal(B:foo(), 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are overridable by subclasses, without affecting the superclasses', function()
|
||||||
|
function B.static:foo() return 'chunky bacon' end
|
||||||
|
assert_equal(B:foo(), 'chunky bacon')
|
||||||
|
assert_equal(A:foo(), 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
66
spec/includes_spec.lua
Normal file
66
spec/includes_spec.lua
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('includes', function()
|
||||||
|
|
||||||
|
context('nils, numbers, etc', function()
|
||||||
|
local o = Object:new()
|
||||||
|
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||||
|
|
||||||
|
for _,primitive in pairs(primitives) do
|
||||||
|
local theType = type(primitive)
|
||||||
|
context('A ' .. theType, function()
|
||||||
|
|
||||||
|
local f1 = function() return includes(Object, primitive) end
|
||||||
|
local f2 = function() return includes(primitive, o) end
|
||||||
|
local f3 = function() return includes(primitive, primitive) end
|
||||||
|
|
||||||
|
context('don\'t throw errors', function()
|
||||||
|
test('includes(Object, '.. theType ..')', function()
|
||||||
|
assert_not_error(f1)
|
||||||
|
end)
|
||||||
|
test('includes(' .. theType .. ', Object:new())', function()
|
||||||
|
assert_not_error(f2)
|
||||||
|
end)
|
||||||
|
test('includes(' .. theType .. ',' .. theType ..')', function()
|
||||||
|
assert_not_error(f3)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('make includes return false', function()
|
||||||
|
assert_false(f1())
|
||||||
|
assert_false(f2())
|
||||||
|
assert_false(f3())
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
end -- for
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('A class', function()
|
||||||
|
|
||||||
|
local Class1 = class('Class1')
|
||||||
|
local Class2 = class('Class2', Class1)
|
||||||
|
local Class3 = class('Class3', Class2)
|
||||||
|
local UnrelatedClass = class('Unrelated')
|
||||||
|
|
||||||
|
local hasFoo = { foo=function() return 'foo' end }
|
||||||
|
Class1:include(hasFoo)
|
||||||
|
|
||||||
|
test('returns true if it includes a mixin', function()
|
||||||
|
assert_true(includes(hasFoo, Class1))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('returns true if its superclass includes a mixin', function()
|
||||||
|
assert_true(includes(hasFoo, Class2))
|
||||||
|
assert_true(includes(hasFoo, Class3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('returns false otherwise', function()
|
||||||
|
assert_false(includes(hasFoo, UnrelatedClass))
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
80
spec/instanceOf_spec.lua
Normal file
80
spec/instanceOf_spec.lua
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('instanceOf', function()
|
||||||
|
|
||||||
|
context('nils, integers, strings, tables, and functions', function()
|
||||||
|
local o = Object:new()
|
||||||
|
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||||
|
|
||||||
|
for _,primitive in pairs(primitives) do
|
||||||
|
local theType = type(primitive)
|
||||||
|
context('A ' .. theType, function()
|
||||||
|
|
||||||
|
local f1 = function() return instanceOf(Object, primitive) end
|
||||||
|
local f2 = function() return instanceOf(primitive, o) end
|
||||||
|
local f3 = function() return instanceOf(primitive, primitive) end
|
||||||
|
|
||||||
|
context('does not throw errors', function()
|
||||||
|
test('instanceOf(Object, '.. theType ..')', function()
|
||||||
|
assert_not_error(f1)
|
||||||
|
end)
|
||||||
|
test('instanceOf(' .. theType .. ', Object:new())', function()
|
||||||
|
assert_not_error(f2)
|
||||||
|
end)
|
||||||
|
test('instanceOf(' .. theType .. ',' .. theType ..')', function()
|
||||||
|
assert_not_error(f3)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('makes instanceOf return false', function()
|
||||||
|
assert_false(f1())
|
||||||
|
assert_false(f2())
|
||||||
|
assert_false(f3())
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('An instance', function()
|
||||||
|
local Class1 = class('Class1')
|
||||||
|
local Class2 = class('Class2', Class1)
|
||||||
|
local Class3 = class('Class3', Class2)
|
||||||
|
local UnrelatedClass = class('Unrelated')
|
||||||
|
|
||||||
|
local o1, o2, o3 = Class1:new(), Class2:new(), Class3:new()
|
||||||
|
|
||||||
|
test('is instanceOf(Object)', function()
|
||||||
|
assert_true(instanceOf(Object, o1))
|
||||||
|
assert_true(instanceOf(Object, o2))
|
||||||
|
assert_true(instanceOf(Object, o3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is instanceOf its class', function()
|
||||||
|
assert_true(instanceOf(Class1, o1))
|
||||||
|
assert_true(instanceOf(Class2, o2))
|
||||||
|
assert_true(instanceOf(Class3, o3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is instanceOf its class\' superclasses', function()
|
||||||
|
assert_true(instanceOf(Class1, o2))
|
||||||
|
assert_true(instanceOf(Class1, o3))
|
||||||
|
assert_true(instanceOf(Class2, o3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is not instanceOf its class\' subclasses', function()
|
||||||
|
assert_false(instanceOf(Class2, o1))
|
||||||
|
assert_false(instanceOf(Class3, o1))
|
||||||
|
assert_false(instanceOf(Class3, o2))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is not instanceOf an unrelated class', function()
|
||||||
|
assert_false(instanceOf(UnrelatedClass, o1))
|
||||||
|
assert_false(instanceOf(UnrelatedClass, o2))
|
||||||
|
assert_false(instanceOf(UnrelatedClass, o3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
65
spec/instances_spec.lua
Normal file
65
spec/instances_spec.lua
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('An instance', function()
|
||||||
|
|
||||||
|
context('attributes', function()
|
||||||
|
|
||||||
|
local Person
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
Person = class('Person')
|
||||||
|
function Person:initialize(name)
|
||||||
|
self.name = name
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available in the instance after being initialized', function()
|
||||||
|
local bob = Person:new('bob')
|
||||||
|
assert_equal(bob.name, 'bob')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available in the instance after being initialized by a superclass', function()
|
||||||
|
local AgedPerson = class('AgedPerson', Person)
|
||||||
|
function AgedPerson:initialize(name, age)
|
||||||
|
Person.initialize(self, name)
|
||||||
|
self.age = age
|
||||||
|
end
|
||||||
|
|
||||||
|
local pete = AgedPerson:new('pete', 31)
|
||||||
|
assert_equal(pete.name, 'pete')
|
||||||
|
assert_equal(pete.age, 31)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('methods', function()
|
||||||
|
|
||||||
|
local A, B, a, b
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
A = class('A')
|
||||||
|
function A:overridden() return 'foo' end
|
||||||
|
function A:regular() return 'regular' end
|
||||||
|
|
||||||
|
B = class('B', A)
|
||||||
|
function B:overridden() return 'bar' end
|
||||||
|
|
||||||
|
a = A:new()
|
||||||
|
b = B:new()
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are available for any instance', function()
|
||||||
|
assert_equal(a:overridden(), 'foo')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are inheritable', function()
|
||||||
|
assert_equal(b:regular(), 'regular')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('are overridable', function()
|
||||||
|
assert_equal(b:overridden(), 'bar')
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
106
spec/metamethods_spec.lua
Normal file
106
spec/metamethods_spec.lua
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('Metamethods', function()
|
||||||
|
|
||||||
|
context('Custom Metamethods', function()
|
||||||
|
-- Tests all metamethods. Note that __len is missing (lua makes table length unoverridable)
|
||||||
|
-- I'll use a() to note the length of vector "a" (I would have preferred to use #a, but it's not possible)
|
||||||
|
-- I'll be using 'a' instead of 'self' on this example since it is shorter
|
||||||
|
local Vector= class('Vector')
|
||||||
|
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
|
||||||
|
function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end
|
||||||
|
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
|
||||||
|
function Vector.__lt(a,b) return a() < b() end
|
||||||
|
function Vector.__le(a,b) return a() <= b() end
|
||||||
|
function Vector.__add(a,b) return Vector:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
|
||||||
|
function Vector.__sub(a,b) return Vector:new(a.x-b.x, a.y-b.y, a.z-b.z) end
|
||||||
|
function Vector.__div(a,s) return Vector:new(a.x/s, a.y/s, a.z/s) end
|
||||||
|
function Vector.__unm(a) return Vector:new(-a.x, -a.y, -a.z) end
|
||||||
|
function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end
|
||||||
|
function Vector.__call(a) return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end
|
||||||
|
function Vector.__pow(a,b)
|
||||||
|
return Vector:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x)
|
||||||
|
end
|
||||||
|
function Vector.__mul(a,b)
|
||||||
|
if type(b)=="number" then return Vector:new(a.x*b, a.y*b, a.z*b) end
|
||||||
|
if type(a)=="number" then return Vector:new(a*b.x, a*b.y, a*b.z) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local a = Vector:new(1,2,3)
|
||||||
|
local b = Vector:new(2,4,6)
|
||||||
|
|
||||||
|
for metamethod,values in pairs({
|
||||||
|
__tostring = { tostring(a), "Vector[1,2,3]" },
|
||||||
|
__eq = { a, a},
|
||||||
|
__lt = { a<b, true },
|
||||||
|
__le = { a<=b, true },
|
||||||
|
__add = { a+b, Vector(3,6,9) },
|
||||||
|
__sub = { b-a, Vector(1,2,3) },
|
||||||
|
__div = { b/2, Vector(1,2,3) },
|
||||||
|
__unm = { -a, Vector(-1,-2,-3) },
|
||||||
|
__concat = { a..b, 28 },
|
||||||
|
__call = { a(), math.sqrt(14) },
|
||||||
|
__pow = { a^b, Vector(0,0,0) },
|
||||||
|
__mul = { 4*a, Vector(4,8,12) }--,
|
||||||
|
--__index = { b[1], 3 }
|
||||||
|
}) do
|
||||||
|
test(metamethod .. ' works as expected', function()
|
||||||
|
assert_equal(values[1], values[2])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
context('Inherited Metamethods', function()
|
||||||
|
local Vector2= class('Vector2', Vector)
|
||||||
|
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
|
||||||
|
|
||||||
|
local c = Vector2:new(1,2,3)
|
||||||
|
local d = Vector2:new(2,4,6)
|
||||||
|
for metamethod,values in pairs({
|
||||||
|
__tostring = { tostring(c), "Vector2[1,2,3]" },
|
||||||
|
__eq = { c, c },
|
||||||
|
__lt = { c<d, true },
|
||||||
|
__le = { c<=d, true },
|
||||||
|
__add = { c+d, Vector(3,6,9) },
|
||||||
|
__sub = { d-c, Vector(1,2,3) },
|
||||||
|
__div = { d/2, Vector(1,2,3) },
|
||||||
|
__unm = { -c, Vector(-1,-2,-3) },
|
||||||
|
__concat = { c..d, 28 },
|
||||||
|
__call = { c(), math.sqrt(14) },
|
||||||
|
__pow = { c^d, Vector(0,0,0) },
|
||||||
|
__mul = { 4*c, Vector(4,8,12) }
|
||||||
|
}) do
|
||||||
|
test(metamethod .. ' works as expected', function()
|
||||||
|
assert_equal(values[1], values[2])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('Default Metamethods', function()
|
||||||
|
|
||||||
|
local Peter, peter
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
Peter = class('Peter')
|
||||||
|
peter = Peter()
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('A Class', function()
|
||||||
|
test('has a call metamethod properly set', function()
|
||||||
|
assert_true(instanceOf(Peter, peter))
|
||||||
|
end)
|
||||||
|
test('has a tostring metamethod properly set', function()
|
||||||
|
assert_equal(tostring(Peter), 'class Peter')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('An instance', function()
|
||||||
|
test('has a tostring metamethod, returning a different result from Object.__tostring', function()
|
||||||
|
assert_not_equal(Peter.__tostring, Object.__tostring)
|
||||||
|
assert_equal(tostring(peter), 'instance of class Peter')
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
53
spec/mixins_spec.lua
Normal file
53
spec/mixins_spec.lua
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('A Mixin', function()
|
||||||
|
|
||||||
|
local Mixin1, Mixin2, Class1, Class2
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
Mixin1, Mixin2 = {},{}
|
||||||
|
|
||||||
|
function Mixin1:included(theClass) theClass.includesMixin1 = true end
|
||||||
|
function Mixin1:foo() return 'foo' end
|
||||||
|
function Mixin1:bar() return 'bar' end
|
||||||
|
Mixin1.static = {}
|
||||||
|
Mixin1.static.bazzz = function() return 'bazzz' end
|
||||||
|
|
||||||
|
|
||||||
|
function Mixin2:baz() return 'baz' end
|
||||||
|
|
||||||
|
Class1 = class('Class1'):include(Mixin1, Mixin2)
|
||||||
|
function Class1:foo() return 'foo1' end
|
||||||
|
|
||||||
|
Class2 = class('Class2', Class1)
|
||||||
|
function Class2:bar2() return 'bar2' end
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('invokes the "included" method when included', function()
|
||||||
|
assert_true(Class1.includesMixin1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('has all its functions (except "included") copied to its target class', function()
|
||||||
|
assert_equal(Class1:bar(), 'bar')
|
||||||
|
assert_nil(Class1.included)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('makes its functions available to subclasses', function()
|
||||||
|
assert_equal(Class2:baz(), 'baz')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('allows overriding of methods in the same class', function()
|
||||||
|
assert_equal(Class2:foo(), 'foo1')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('allows overriding of methods on subclasses', function()
|
||||||
|
assert_equal(Class2:bar2(), 'bar2')
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('makes new static methods available in classes', function()
|
||||||
|
assert_equal(Class1:bazzz(), 'bazzz')
|
||||||
|
assert_equal(Class2:bazzz(), 'bazzz')
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
74
spec/subclassOf_spec.lua
Normal file
74
spec/subclassOf_spec.lua
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
require 'middleclass'
|
||||||
|
|
||||||
|
context('subclassOf', function()
|
||||||
|
|
||||||
|
context('nils, integers, etc', function()
|
||||||
|
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||||
|
|
||||||
|
for _,primitive in pairs(primitives) do
|
||||||
|
local theType = type(primitive)
|
||||||
|
context('A ' .. theType, function()
|
||||||
|
|
||||||
|
local f1 = function() return subclassOf(Object, primitive) end
|
||||||
|
local f2 = function() return subclassOf(primitive, o) end
|
||||||
|
local f3 = function() return subclassOf(primitive, primitive) end
|
||||||
|
|
||||||
|
context('does not throw errors', function()
|
||||||
|
test('subclassOf(Object, '.. theType ..')', function()
|
||||||
|
assert_not_error(f1)
|
||||||
|
end)
|
||||||
|
test('subclassOf(' .. theType .. ', Object:new())', function()
|
||||||
|
assert_not_error(f2)
|
||||||
|
end)
|
||||||
|
test('subclassOf(' .. theType .. ',' .. theType ..')', function()
|
||||||
|
assert_not_error(f3)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('makes subclassOf return false', function()
|
||||||
|
assert_false(f1())
|
||||||
|
assert_false(f2())
|
||||||
|
assert_false(f3())
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
context('Any class (except Object)', function()
|
||||||
|
local Class1 = class('Class1')
|
||||||
|
local Class2 = class('Class2', Class1)
|
||||||
|
local Class3 = class('Class3', Class2)
|
||||||
|
local UnrelatedClass = class('Unrelated')
|
||||||
|
|
||||||
|
test('is subclassOf(Object)', function()
|
||||||
|
assert_true(subclassOf(Object, Class1))
|
||||||
|
assert_true(subclassOf(Object, Class2))
|
||||||
|
assert_true(subclassOf(Object, Class3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is subclassOf its direct superclass', function()
|
||||||
|
assert_true(subclassOf(Class1, Class2))
|
||||||
|
assert_true(subclassOf(Class2, Class3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is subclassOf its ancestors', function()
|
||||||
|
assert_true(subclassOf(Class1, Class3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is a subclassOf its class\' subclasses', function()
|
||||||
|
assert_false(subclassOf(Class2, Class1))
|
||||||
|
assert_false(subclassOf(Class3, Class1))
|
||||||
|
assert_false(subclassOf(Class3, Class2))
|
||||||
|
end)
|
||||||
|
|
||||||
|
test('is not a subclassOf an unrelated class', function()
|
||||||
|
assert_false(subclassOf(UnrelatedClass, Class1))
|
||||||
|
assert_false(subclassOf(UnrelatedClass, Class2))
|
||||||
|
assert_false(subclassOf(UnrelatedClass, Class3))
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
Loading…
Reference in New Issue
Block a user