merged branch 2.0

This commit is contained in:
Enrique García Cota 2011-09-19 00:21:14 +02:00
commit 3f9ae1d6e8
14 changed files with 955 additions and 162 deletions

View File

@ -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.

View File

@ -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>

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)