Merge branch '3.0'

This commit is contained in:
kikito 2013-09-19 00:11:14 +02:00
commit 0a6eb856e6
16 changed files with 553 additions and 507 deletions

View File

@ -7,6 +7,6 @@ env:
install: install:
- sudo apt-get install luajit - sudo apt-get install luajit
- sudo apt-get install luarocks - sudo apt-get install luarocks
- sudo luarocks install telescope - sudo luarocks install busted
script: "tsc -f spec/*" script: "busted"

23
CHANGELOG.md Normal file
View File

@ -0,0 +1,23 @@
middleclass changelog
====================
Version 3.0
* Anything that behaves reasonably like a class can be a class (no internal list of classes)
* The `class` global function is now just the return value of `require
'middleclass'`. It is a callable table, but works exactly as before.
* The global variable `Object` becomes `class.Object`
* The global function `instanceOf` becomes `class.Object.isInstanceOf`. Parameter order is reversed.
* The global function `subclassOf` becomes `class.Object.static.isSubclassOf`. Parameter order is reversed.
* The global function `implements` becomes `class.Object.static.implements`. Parameter order is reversed.
* Specs have been translated from telescope to busted
Version 2.0
* Static methods are now separated from instance methods
* class.superclass has now become class.super
* It's now possible to do class.subclasses
* middleclass is now a single file; init.lua has dissapeared
* license is changed from BSD to MIT. License included in source FTW

97
README.md Normal file
View File

@ -0,0 +1,97 @@
middleclass
===========
[![Build Status](https://travis-ci.org/kikito/middleclass.png?branch=master)](https://travis-ci.org/kikito/middleclass)
A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
h1. Quick Look
local class = require 'middleclass'
local Fruit = class('Fruit') -- 'Fruit' is the class' name
function Fruit:initialize(sweetness)
self.sweetness = sweetness
end
Fruit.static.sweetness_threshold = 5 -- class variable (also admits methods)
function Fruit:isSweet()
return self.sweetness > Fruit.sweetness_threshold
end
local Lemon = class('Lemon', Fruit) -- subclassing
function Lemon:initialize()
Fruit.initialize(self, 1) -- invoking the superclass' initializer
end
local lemon = Lemon:new()
print(lemon:isSweet()) -- false
h1. Documentation
See the "github wiki page":https://github.com/kikito/middleclass/wiki for examples & documentation.
h1. Installation
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:
local class = require 'middleclass'
h1. Specs
This project uses [busted](http://olivinelabs.com/busted/) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following:
cd /folder/where/the/spec/folder/is
busted
h1. Performance tests
Middleclass also comes with a small performance test suite. Just run the following command:
lua performance/run.lua
h1. Updating from 2.0
Middleclass used to expose several global variables on the main scope. It does not do that any more.
`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this:
local class = require 'middleclass'
local MyClass = class('MyClass') -- works as before
`Object` is not a global variable any more. But you can get it from `class.Object`
local class = require 'middleclass'
local Object = class.Object
print(Object) -- prints 'class Object'
The public functions `instanceOf`, `subclassOf` and `includes` have been replaced by `Object.isInstanceOf`, `Object.static.isSubclassOf` and `Object.static.includes`.
Before:
instanceOf(MyClass, obj)
subclassOf(Object, aClass)
includes(aMixin, aClass)
After:
obj:isInstanceOf(MyClass)
aClass:isSubclassOf(Object)
aClass:includes(aMixin)
The previous code will throw an error if `obj` is not an object, or if `aClass` is not a class (since they will not implement `isInstanceOf`, `isSubclassOf` or `includes`).
If you are unsure of wether `obj` and `aClass` are an object or a class, you can use the methods in `Object`. They are prepared to work with random types, not just classes and instances:
Object.isInstanceOf(obj, MyClass)
Object.isSubclassOf(aClass, Object)
Object.includes(aClass, aMixin)
Notice that the parameter order is not the same now as it was in 2.x. Also note the change in naming: `isInstanceOf` instead of `istanceOf`, and `isSubclassOf` instead of `subclassOf`.

View File

@ -1,75 +0,0 @@
h1. MiddleClass
!https://travis-ci.org/kikito/middleclass.png?branch=master!:https://travis-ci.org/kikito/middleclass
Lua OOP classes usually end being:
* multi-file libraries, too difficult to understand
* very small libraries, not very powerful
Middleclass attemps to be a mid-sized library (~140 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
See the "github wiki page":https://github.com/kikito/middleclass/wiki for examples & documentation.
h1. Features
* ~140 lines of code
* top-level Object class
* all methods are virtual
* instance.class returns the instance's class
* @Class.name@ returns the class name (a string)
* @Class.super@ returns its super class
* Class methods can be defined with Class.static.methodname
* Subclassing:
** @class(name)@ creates a subclass of @Object@
** @class(name, Superclass)@ creates a subclass of the class @SuperClass@
** @SuperClass:subclass(name)@ also creates a subclass of the class @SuperClass@
* Instantiation:
** Classes can define an @initialize@ method for initializing new instances. They can accept an arbitrary number of params.
** Instances are created by doing @Class:new(params)@ or also @Class(params)@
** SuperClass' methods can be used by using this syntax: @SuperClass.initialize(self, params)@.
* support for Lua metamethods: just define a method called @__tostring@, @__add@, etc. and your instances will be able to use it.
* Mixins:
** A very simple mechanism for sharing functionality among a group of classes that are otherwise non-related.
** Mixins are just simple lua tables with functions inside them.
** @Class:include(mixin)@ will copy the function definitions of @mixin@ to @class@
** If @mixin@ contains a function, called @included@, that function will be invoked right after the functions have been copied. It allows for modifying the class more profoundly.
* The function @instanceOf(class, instance)@ returns @true@ if @instance@ is an instance of the class @Class@
* The function @subclassOf(Superclass, Class)@ returns @true@ if @Class@ is a subclass of @SuperClass@
* The function @includes(mixin, Class)@ returns @true@ if @Class@ (or one of its superclasses) includes @mixin@.
Features left out:
* metaclasses
* classes are not Objects (instances are)
* simulating a 'super' keyword (for performance concerns)
h1. Installation
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>require 'middleclass'</pre>
The @package.path@ variable must be configured so that the folder in which middleclass.lua is copied is available, of course.
Please make sure that you read the license, too (for your convenience it's now included at the beginning of the middleclass.lua file).
h1. 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>
h1. Performance tests
Middleclass also comes with a small performance test suite. Just run the following command:
<pre>
lua performance/run.lua
</pre>

View File

@ -1,8 +0,0 @@
Version 2.0
* Static methods are now separated from instance methods
* class.superclass has now become class.super
* It's now possible to do class.subclasses
* middleclass is now a single file; init.lua has dissapeared
* license is changed from BSD to MIT. License included in source FTW

View File

@ -27,71 +27,71 @@ local middleclass = {
]] ]]
} }
local function _setClassDictionariesMetatables(klass) local function _setClassDictionariesMetatables(aClass)
local dict = klass.__instanceDict local dict = aClass.__instanceDict
dict.__index = dict dict.__index = dict
local super = klass.super local super = aClass.super
if super then if super then
local superStatic = super.static local superStatic = super.static
setmetatable(dict, super.__instanceDict) setmetatable(dict, super.__instanceDict)
setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end }) setmetatable(aClass.static, { __index = function(_,k) return dict[k] or superStatic[k] end })
else else
setmetatable(klass.static, { __index = function(_,k) return dict[k] end }) setmetatable(aClass.static, { __index = function(_,k) return dict[k] end })
end end
end end
local function _setClassMetatable(klass) local function _setClassMetatable(aClass)
setmetatable(klass, { setmetatable(aClass, {
__tostring = function() return "class " .. klass.name end, __tostring = function() return "class " .. aClass.name end,
__index = klass.static, __index = aClass.static,
__newindex = klass.__instanceDict, __newindex = aClass.__instanceDict,
__call = function(self, ...) return self:new(...) end __call = function(self, ...) return self:new(...) end
}) })
end end
local function _createClass(name, super) local function _createClass(name, super)
local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} } local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
klass.subclasses = setmetatable({}, {__mode = "k"}) aClass.subclasses = setmetatable({}, {__mode = "k"})
_setClassDictionariesMetatables(klass) _setClassDictionariesMetatables(aClass)
_setClassMetatable(klass) _setClassMetatable(aClass)
return klass return aClass
end end
local function _createLookupMetamethod(klass, name) local function _createLookupMetamethod(aClass, name)
return function(...) return function(...)
local method = klass.super[name] local method = aClass.super[name]
assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" ) assert( type(method)=='function', tostring(aClass) .. " doesn't implement metamethod '" .. name .. "'" )
return method(...) return method(...)
end end
end end
local function _setClassMetamethods(klass) local function _setClassMetamethods(aClass)
for _,m in ipairs(klass.__metamethods) do for _,m in ipairs(aClass.__metamethods) do
klass[m]= _createLookupMetamethod(klass, m) aClass[m]= _createLookupMetamethod(aClass, m)
end end
end end
local function _setDefaultInitializeMethod(klass, super) local function _setDefaultInitializeMethod(aClass, super)
klass.initialize = function(instance, ...) aClass.initialize = function(instance, ...)
return super.initialize(instance, ...) return super.initialize(instance, ...)
end end
end end
local function _includeMixin(klass, mixin) local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', "mixin must be a table") assert(type(mixin)=='table', "mixin must be a table")
for name,method in pairs(mixin) do for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then klass[name] = method end if name ~= "included" and name ~= "static" then aClass[name] = method end
end end
if mixin.static then if mixin.static then
for name,method in pairs(mixin.static) do for name,method in pairs(mixin.static) do
klass.static[name] = method aClass.static[name] = method
end end
end end
if type(mixin.included)=="function" then mixin:included(klass) end if type(mixin.included)=="function" then mixin:included(aClass) end
klass.__mixins[mixin] = true aClass.__mixins[mixin] = true
end end
local Object = _createClass("Object", nil) local Object = _createClass("Object", nil)
@ -100,7 +100,7 @@ Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le',
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' } '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' }
function Object.static:allocate() function Object.static:allocate()
assert(self, "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict) return setmetatable({ class = self }, self.__instanceDict)
end end
@ -111,7 +111,7 @@ function Object.static:new(...)
end end
function Object.static:subclass(name) function Object.static:subclass(name)
assert(self, "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class") assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self) local subclass = _createClass(name, self)
@ -125,32 +125,48 @@ end
function Object.static:subclassed(other) end function Object.static:subclassed(other) end
function Object.static:isSubclassOf(other)
return type(other) == 'table' and
type(self) == 'table' and
type(self.super) == 'table' and
( self.super == other or
type(self.super.isSubclassOf) == 'function' and
self.super:isSubclassOf(other)
)
end
function Object.static:include( ... ) function Object.static:include( ... )
assert(self, "Make sure you that you are using 'Class:include' instead of 'Class.include'") assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self return self
end end
function Object.static:includes(mixin)
return type(mixin) == 'table' and
type(self) == 'table' and
type(self.__mixins) == 'table' and
( self.__mixins[mixin] or
type(self.super) == 'table' and
type(self.super.includes) == 'function' and
self.super:includes(mixin)
)
end
function Object:initialize() end function Object:initialize() end
function Object:__tostring() return "instance of " .. tostring(self.class) end function Object:__tostring() return "instance of " .. tostring(self.class) end
function instanceOf(aClass, obj) function Object:isInstanceOf(aClass)
if type(aClass) ~= 'table' or type(obj) ~= 'table' or not obj.class then return false end return type(self) == 'table' and
if obj.class == aClass then return true end type(self.class) == 'table' and
return subclassOf(aClass, obj.class) type(aClass) == 'table' and
( aClass == self.class or
type(aClass.isSubclassOf) == 'function' and
self.class:isSubclassOf(aClass)
)
end end
function subclassOf(other, aClass)
if type(other) ~= 'table' or type(aClass) ~= 'table' or not aClass.super then return false end
return aClass.super == other or subclassOf(other, aClass.super)
end
function includes(mixin, aClass)
if type(mixin) ~= 'table' or type(aClass) ~= 'table' or not aClass.__mixins then return false end
if aClass.__mixins[mixin] then return true end
return includes(mixin, aClass.super)
end
function middleclass.class(name, super, ...) function middleclass.class(name, super, ...)
super = super or Object super = super or Object

View File

@ -1,4 +1,4 @@
require 'middleclass' local class = require 'middleclass'
time = require 'performance/time' time = require 'performance/time'

View File

@ -1,89 +1,89 @@
local class = require 'middleclass' local class = require 'middleclass'
local Object = class.Object local Object = class.Object
context('Object', function() describe('Object', function()
context('name', function() describe('name', function()
test('is correctly set', function() it('is correctly set', function()
assert_equal(Object.name, 'Object') assert.equal(Object.name, 'Object')
end) end)
end) end)
context('tostring', function() describe('tostring', function()
test('returns "class Object"', function() it('returns "class Object"', function()
assert_equal(tostring(Object), 'class Object') assert.equal(tostring(Object), 'class Object')
end) end)
end) end)
context('()', function() describe('()', function()
test('returns an object, like Object:new()', function() it('returns an object, like Object:new()', function()
local obj = Object() local obj = Object()
assert_true(instanceOf(Object, obj)) assert.is_true(obj:isInstanceOf(Object))
end) end)
end) end)
context('subclass', function() describe('subclass', function()
test('throws an error when used without the :', function() it('throws an error when used without the :', function()
assert_error(function() Object.subclass() end) assert.error(function() Object.subclass() end)
end) end)
test('throws an error when no name is given', function() it('throws an error when no name is given', function()
assert_error( function() Object:subclass() end) assert.error( function() Object:subclass() end)
end) end)
context('when given a class name', function() describe('when given a class name', function()
local SubClass local SubClass
before(function() before_each(function()
SubClass = Object:subclass('SubClass') SubClass = Object:subclass('SubClass')
end) end)
test('it returns a class with the correct name', function() it('it returns a class with the correct name', function()
assert_equal(SubClass.name, 'SubClass') assert.equal(SubClass.name, 'SubClass')
end) end)
test('it returns a class with the correct superclass', function() it('it returns a class with the correct superclass', function()
assert_equal(SubClass.super, Object) assert.equal(SubClass.super, Object)
end) end)
test('it includes the subclass in the list of subclasses', function() it('it includes the subclass in the list of subclasses', function()
assert_true(Object.subclasses[SubClass]) assert.is_true(Object.subclasses[SubClass])
end) end)
end) end)
end) end)
context('instance creation', function() describe('instance creation', function()
local SubClass local SubClass
before(function() before_each(function()
SubClass = class('SubClass') SubClass = class('SubClass')
function SubClass:initialize() self.mark=true end function SubClass:initialize() self.mark=true end
end) end)
context('allocate', function() describe('allocate', function()
test('allocates instances properly', function() it('allocates instances properly', function()
local instance = SubClass:allocate() local instance = SubClass:allocate()
assert_equal(instance.class, SubClass) assert.equal(instance.class, SubClass)
assert_equal(tostring(instance), "instance of " .. tostring(SubClass)) assert.equal(tostring(instance), "instance of " .. tostring(SubClass))
end) end)
test('throws an error when used without the :', function() it('throws an error when used without the :', function()
assert_error(Object.allocate) assert.error(Object.allocate)
end) end)
test('does not call the initializer', function() it('does not call the initializer', function()
local allocated = SubClass:allocate() local allocated = SubClass:allocate()
assert_nil(allocated.mark) assert.is_nil(allocated.mark)
end) end)
test('can be overriden', function() it('can be overriden', function()
local previousAllocate = SubClass.static.allocate local previousAllocate = SubClass.static.allocate
@ -94,33 +94,249 @@ context('Object', function()
end end
local allocated = SubClass:allocate() local allocated = SubClass:allocate()
assert_true(allocated.mark) assert.is_true(allocated.mark)
end) end)
end) end)
context('new', function() describe('new', function()
test('initializes instances properly', function() it('initializes instances properly', function()
local instance = SubClass:new() local instance = SubClass:new()
assert_equal(instance.class, SubClass) assert.equal(instance.class, SubClass)
end) end)
test('throws an error when used without the :', function() it('throws an error when used without the :', function()
assert_error(SubClass.new) assert.error(SubClass.new)
end) end)
test('calls the initializer', function() it('calls the initializer', function()
local initialized = SubClass:new() local initialized = SubClass:new()
assert_true(initialized.mark) assert.is_true(initialized.mark)
end) end)
end) end)
describe('isInstanceOf', function()
describe('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)
describe('A ' .. theType, function()
local f1 = function() return Object.isInstanceOf(primitive, Object) end
local f2 = function() return Object.isInstanceOf(primitive, o) end
local f3 = function() return Object.isInstanceOf(primitive, primitive) end
describe('does not throw errors', function()
it('instanceOf(Object, '.. theType ..')', function()
assert.not_error(f1)
end)
it('instanceOf(' .. theType .. ', Object:new())', function()
assert.not_error(f2)
end)
it('instanceOf(' .. theType .. ',' .. theType ..')', function()
assert.not_error(f3)
end)
end)
it('makes instanceOf return false', function()
assert.is_false(f1())
assert.is_false(f2())
assert.is_false(f3())
end)
end)
end
end)
describe('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()
it('isInstanceOf(Object)', function()
assert.is_true(o1:isInstanceOf(Object))
assert.is_true(o2:isInstanceOf(Object))
assert.is_true(o3:isInstanceOf(Object))
end)
it('isInstanceOf its class', function()
assert.is_true(o1:isInstanceOf(Class1))
assert.is_true(o2:isInstanceOf(Class2))
assert.is_true(o3:isInstanceOf(Class3))
end)
it('is instanceOf its class\' superclasses', function()
assert.is_true(o2:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class2))
end)
it('is not instanceOf its class\' subclasses', function()
assert.is_false(o1:isInstanceOf(Class2))
assert.is_false(o1:isInstanceOf(Class3))
assert.is_false(o2:isInstanceOf(Class3))
end)
it('is not instanceOf an unrelated class', function()
assert.is_false(o1:isInstanceOf(UnrelatedClass))
assert.is_false(o2:isInstanceOf(UnrelatedClass))
assert.is_false(o3:isInstanceOf(UnrelatedClass))
end)
end)
end)
end) end)
describe('isSubclassOf', function()
describe('nils, integers, etc', function()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
describe('A ' .. theType, function()
local f1 = function() return Object.isSubclassOf(Object, primitive) end
local f2 = function() return Object.isSubclassOf(primitive, o) end
local f3 = function() return Object.isSubclassOf(primitive, primitive) end
describe('does not throw errors', function()
it('isSubclassOf(Object, '.. theType ..')', function()
assert.not_error(f1)
end)
it('isSubclassOf(' .. theType .. ', Object:new())', function()
assert.not_error(f2)
end)
it('isSubclassOf(' .. theType .. ',' .. theType ..')', function()
assert.not_error(f3)
end)
end)
it('makes isSubclassOf return false', function()
assert.is_false(f1())
assert.is_false(f2())
assert.is_false(f3())
end)
end)
end
end)
describe('Any class (except Object)', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
it('isSubclassOf(Object)', function()
assert.is_true(Class1:isSubclassOf(Object))
assert.is_true(Class2:isSubclassOf(Object))
assert.is_true(Class3:isSubclassOf(Object))
end)
it('is subclassOf its direct superclass', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is subclassOf its ancestors', function()
assert.is_true(Class3:isSubclassOf(Class1))
end)
it('is a subclassOf its class\' subclasses', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is not a subclassOf an unrelated class', function()
assert.is_false(Class1:isSubclassOf(UnrelatedClass))
assert.is_false(Class2:isSubclassOf(UnrelatedClass))
assert.is_false(Class3:isSubclassOf(UnrelatedClass))
end)
end)
end)
describe('includes', function()
describe('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)
describe('A ' .. theType, function()
local f1 = function() return Object.includes(Object, primitive) end
local f2 = function() return Object.includes(primitive, o) end
local f3 = function() return Object.includes(primitive, primitive) end
describe('don\'t throw errors', function()
it('includes(Object, '.. theType ..')', function()
assert.not_error(f1)
end)
it('includes(' .. theType .. ', Object:new())', function()
assert.not_error(f2)
end)
it('includes(' .. theType .. ',' .. theType ..')', function()
assert.not_error(f3)
end)
end)
it('make includes return false', function()
assert.is_false(f1())
assert.is_false(f2())
assert.is_false(f3())
end)
end)
end -- for
end)
describe('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)
it('returns true if it includes a mixin', function()
assert.is_true(Class1:includes(hasFoo))
end)
it('returns true if its superclass includes a mixin', function()
assert.is_true(Class2:includes(hasFoo))
assert.is_true(Class3:includes(hasFoo))
end)
it('returns false otherwise', function()
assert.is_false(UnrelatedClass:includes(hasFoo))
end)
end)
end)
end) end)

View File

@ -1,28 +1,28 @@
local class = require 'middleclass' local class = require 'middleclass'
local Object = class.Object local Object = class.Object
context('class()', function() describe('class()', function()
context('when given no params', function() describe('when given no params', function()
test('it throws an error', function() it('it throws an error', function()
assert_error(class) assert.error(class)
end) end)
end) end)
context('when given a name', function() describe('when given a name', function()
test('the resulting class has the correct name and Object as its superclass', function() it('the resulting class has the correct name and Object as its superclass', function()
local TheClass = class('TheClass') local TheClass = class('TheClass')
assert_equal(TheClass.name, 'TheClass') assert.equal(TheClass.name, 'TheClass')
assert_equal(TheClass.super, Object) assert.equal(TheClass.super, Object)
end) end)
end) end)
context('when given a name and a superclass', function() describe('when given a name and a superclass', function()
test('the resulting class has the correct name and superclass', function() it('the resulting class has the correct name and superclass', function()
local TheSuperClass = class('TheSuperClass') local TheSuperClass = class('TheSuperClass')
local TheSubClass = class('TheSubClass', TheSuperClass) local TheSubClass = class('TheSubClass', TheSuperClass)
assert_equal(TheSubClass.name, 'TheSubClass') assert.equal(TheSubClass.name, 'TheSubClass')
assert_equal(TheSubClass.super, TheSuperClass) assert.equal(TheSubClass.super, TheSuperClass)
end) end)
end) end)

View File

@ -1,76 +1,76 @@
local class = require 'middleclass' local class = require 'middleclass'
context('A Class', function() describe('A Class', function()
context('Default stuff', function() describe('Default stuff', function()
local AClass local AClass
before(function() before_each(function()
AClass = class('AClass') AClass = class('AClass')
end) end)
context('name', function() describe('name', function()
test('is correctly set', function() it('is correctly set', function()
assert_equal(AClass.name, 'AClass') assert.equal(AClass.name, 'AClass')
end) end)
end) end)
context('tostring', function() describe('tostring', function()
test('returns "class *name*"', function() it('returns "class *name*"', function()
assert_equal(tostring(AClass), 'class AClass') assert.equal(tostring(AClass), 'class AClass')
end) end)
end) end)
context('()', function() describe('()', function()
test('returns an object, like Class:new()', function() it('returns an object, like Class:new()', function()
local obj = AClass() local obj = AClass()
assert_equal(obj.class, AClass) assert.equal(obj.class, AClass)
end) end)
end) end)
context('include', function() describe('include', function()
test('throws an error when used without the :', function() it('throws an error when used without the :', function()
assert_error(function() AClass.include() end) assert.error(function() AClass.include() end)
end) end)
test('throws an error when passed a non-table:', function() it('throws an error when passed a non-table:', function()
assert_error(function() AClass:include(1) end) assert.error(function() AClass:include(1) end)
end) end)
end) end)
context('subclass', function() describe('subclass', function()
test('throws an error when used without the :', function() it('throws an error when used without the :', function()
assert_error(function() AClass.subclass() end) assert.error(function() AClass.subclass() end)
end) end)
test('throws an error when no name is given', function() it('throws an error when no name is given', function()
assert_error( function() AClass:subclass() end) assert.error( function() AClass:subclass() end)
end) end)
context('when given a subclass name', function() describe('when given a subclass name', function()
local SubClass local SubClass
before(function() before_each(function()
function AClass.static:subclassed(other) self.static.child = other end function AClass.static:subclassed(other) self.static.child = other end
SubClass = AClass:subclass('SubClass') SubClass = AClass:subclass('SubClass')
end) end)
test('it returns a class with the correct name', function() it('it returns a class with the correct name', function()
assert_equal(SubClass.name, 'SubClass') assert.equal(SubClass.name, 'SubClass')
end) end)
test('it returns a class with the correct superclass', function() it('it returns a class with the correct superclass', function()
assert_equal(SubClass.super, AClass) assert.equal(SubClass.super, AClass)
end) end)
test('it invokes the subclassed hook method', function() it('it invokes the subclassed hook method', function()
assert_equal(SubClass, AClass.child) assert.equal(SubClass, AClass.child)
end) end)
test('it includes the subclass in the list of subclasses', function() it('it includes the subclass in the list of subclasses', function()
assert_true(AClass.subclasses[SubClass]) assert.is_true(AClass.subclasses[SubClass])
end) end)
end) end)
@ -81,56 +81,56 @@ context('A Class', function()
context('attributes', function() describe('attributes', function()
local A, B local A, B
before(function() before_each(function()
A = class('A') A = class('A')
A.static.foo = 'foo' A.static.foo = 'foo'
B = class('B', A) B = class('B', A)
end) end)
test('are available after being initialized', function() it('are available after being initialized', function()
assert_equal(A.foo, 'foo') assert.equal(A.foo, 'foo')
end) end)
test('are available for subclasses', function() it('are available for subclasses', function()
assert_equal(B.foo, 'foo') assert.equal(B.foo, 'foo')
end) end)
test('are overridable by subclasses, without affecting the superclasses', function() it('are overridable by subclasses, without affecting the superclasses', function()
B.static.foo = 'chunky bacon' B.static.foo = 'chunky bacon'
assert_equal(B.foo, 'chunky bacon') assert.equal(B.foo, 'chunky bacon')
assert_equal(A.foo, 'foo') assert.equal(A.foo, 'foo')
end) end)
end) end)
context('methods', function() describe('methods', function()
local A, B local A, B
before(function() before_each(function()
A = class('A') A = class('A')
function A.static:foo() return 'foo' end function A.static:foo() return 'foo' end
B = class('B', A) B = class('B', A)
end) end)
test('are available after being initialized', function() it('are available after being initialized', function()
assert_equal(A:foo(), 'foo') assert.equal(A:foo(), 'foo')
end) end)
test('are available for subclasses', function() it('are available for subclasses', function()
assert_equal(B:foo(), 'foo') assert.equal(B:foo(), 'foo')
end) end)
test('are overridable by subclasses, without affecting the superclasses', function() it('are overridable by subclasses, without affecting the superclasses', function()
function B.static:foo() return 'chunky bacon' end function B.static:foo() return 'chunky bacon' end
assert_equal(B:foo(), 'chunky bacon') assert.equal(B:foo(), 'chunky bacon')
assert_equal(A:foo(), 'foo') assert.equal(A:foo(), 'foo')
end) end)
end) end)

View File

@ -1,67 +0,0 @@
local class = require 'middleclass'
local Object = class.Object
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)

View File

@ -1,81 +0,0 @@
local class = require 'middleclass'
local Object = class.Object
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)

View File

@ -1,24 +1,24 @@
local class = require 'middleclass' local class = require 'middleclass'
context('An instance', function() describe('An instance', function()
context('attributes', function() describe('attributes', function()
local Person local Person
before(function() before_each(function()
Person = class('Person') Person = class('Person')
function Person:initialize(name) function Person:initialize(name)
self.name = name self.name = name
end end
end) end)
test('are available in the instance after being initialized', function() it('are available in the instance after being initialized', function()
local bob = Person:new('bob') local bob = Person:new('bob')
assert_equal(bob.name, 'bob') assert.equal(bob.name, 'bob')
end) end)
test('are available in the instance after being initialized by a superclass', function() it('are available in the instance after being initialized by a superclass', function()
local AgedPerson = class('AgedPerson', Person) local AgedPerson = class('AgedPerson', Person)
function AgedPerson:initialize(name, age) function AgedPerson:initialize(name, age)
Person.initialize(self, name) Person.initialize(self, name)
@ -26,17 +26,17 @@ context('An instance', function()
end end
local pete = AgedPerson:new('pete', 31) local pete = AgedPerson:new('pete', 31)
assert_equal(pete.name, 'pete') assert.equal(pete.name, 'pete')
assert_equal(pete.age, 31) assert.equal(pete.age, 31)
end) end)
end) end)
context('methods', function() describe('methods', function()
local A, B, a, b local A, B, a, b
before(function() before_each(function()
A = class('A') A = class('A')
function A:overridden() return 'foo' end function A:overridden() return 'foo' end
function A:regular() return 'regular' end function A:regular() return 'regular' end
@ -48,16 +48,16 @@ context('An instance', function()
b = B:new() b = B:new()
end) end)
test('are available for any instance', function() it('are available for any instance', function()
assert_equal(a:overridden(), 'foo') assert.equal(a:overridden(), 'foo')
end) end)
test('are inheritable', function() it('are inheritable', function()
assert_equal(b:regular(), 'regular') assert.equal(b:regular(), 'regular')
end) end)
test('are overridable', function() it('are overridable', function()
assert_equal(b:overridden(), 'bar') assert.equal(b:overridden(), 'bar')
end) end)
end) end)

View File

@ -1,9 +1,9 @@
local class = require 'middleclass' local class = require 'middleclass'
local Object = class.Object local Object = class.Object
context('Metamethods', function() describe('Metamethods', function()
context('Custom Metamethods', function() describe('Custom Metamethods', function()
-- Tests all metamethods. Note that __len is missing (lua makes table length unoverridable) -- 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 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 -- I'll be using 'a' instead of 'self' on this example since it is shorter
@ -45,12 +45,12 @@ context('Metamethods', function()
__mul = { 4*a, Vector(4,8,12) }--, __mul = { 4*a, Vector(4,8,12) }--,
--__index = { b[1], 3 } --__index = { b[1], 3 }
}) do }) do
test(metamethod .. ' works as expected', function() it(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2]) assert.equal(values[1], values[2])
end) end)
end end
context('Inherited Metamethods', function() describe('Inherited Metamethods', function()
local Vector2= class('Vector2', Vector) local Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
@ -70,36 +70,36 @@ context('Metamethods', function()
__pow = { c^d, Vector(0,0,0) }, __pow = { c^d, Vector(0,0,0) },
__mul = { 4*c, Vector(4,8,12) } __mul = { 4*c, Vector(4,8,12) }
}) do }) do
test(metamethod .. ' works as expected', function() it(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2]) assert.equal(values[1], values[2])
end) end)
end end
end) end)
end) end)
context('Default Metamethods', function() describe('Default Metamethods', function()
local Peter, peter local Peter, peter
before(function() before_each(function()
Peter = class('Peter') Peter = class('Peter')
peter = Peter() peter = Peter()
end) end)
context('A Class', function() describe('A Class', function()
test('has a call metamethod properly set', function() it('has a call metamethod properly set', function()
assert_true(instanceOf(Peter, peter)) assert.is_true(peter:isInstanceOf(Peter))
end) end)
test('has a tostring metamethod properly set', function() it('has a tostring metamethod properly set', function()
assert_equal(tostring(Peter), 'class Peter') assert.equal(tostring(Peter), 'class Peter')
end) end)
end) end)
context('An instance', function() describe('An instance', function()
test('has a tostring metamethod, returning a different result from Object.__tostring', function() it('has a tostring metamethod, returning a different result from Object.__tostring', function()
assert_not_equal(Peter.__tostring, Object.__tostring) assert.not_equal(Peter.__tostring, Object.__tostring)
assert_equal(tostring(peter), 'instance of class Peter') assert.equal(tostring(peter), 'instance of class Peter')
end) end)
end) end)
end) end)

View File

@ -1,10 +1,10 @@
local class = require 'middleclass' local class = require 'middleclass'
context('A Mixin', function() describe('A Mixin', function()
local Mixin1, Mixin2, Class1, Class2 local Mixin1, Mixin2, Class1, Class2
before(function() before_each(function()
Mixin1, Mixin2 = {},{} Mixin1, Mixin2 = {},{}
function Mixin1:included(theClass) theClass.includesMixin1 = true end function Mixin1:included(theClass) theClass.includesMixin1 = true end
@ -23,30 +23,30 @@ context('A Mixin', function()
function Class2:bar2() return 'bar2' end function Class2:bar2() return 'bar2' end
end) end)
test('invokes the "included" method when included', function() it('invokes the "included" method when included', function()
assert_true(Class1.includesMixin1) assert.is_true(Class1.includesMixin1)
end) end)
test('has all its functions (except "included") copied to its target class', function() it('has all its functions (except "included") copied to its target class', function()
assert_equal(Class1:bar(), 'bar') assert.equal(Class1:bar(), 'bar')
assert_nil(Class1.included) assert.is_nil(Class1.included)
end) end)
test('makes its functions available to subclasses', function() it('makes its functions available to subclasses', function()
assert_equal(Class2:baz(), 'baz') assert.equal(Class2:baz(), 'baz')
end) end)
test('allows overriding of methods in the same class', function() it('allows overriding of methods in the same class', function()
assert_equal(Class2:foo(), 'foo1') assert.equal(Class2:foo(), 'foo1')
end) end)
test('allows overriding of methods on subclasses', function() it('allows overriding of methods on subclasses', function()
assert_equal(Class2:bar2(), 'bar2') assert.equal(Class2:bar2(), 'bar2')
end) end)
test('makes new static methods available in classes', function() it('makes new static methods available in classes', function()
assert_equal(Class1:bazzz(), 'bazzz') assert.equal(Class1:bazzz(), 'bazzz')
assert_equal(Class2:bazzz(), 'bazzz') assert.equal(Class2:bazzz(), 'bazzz')
end) end)
end) end)

View File

@ -1,75 +0,0 @@
local class = require 'middleclass'
local Object = class.Object
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)