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:
- sudo apt-get install luajit
- 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 dict = klass.__instanceDict
local function _setClassDictionariesMetatables(aClass)
local dict = aClass.__instanceDict
dict.__index = dict
local super = klass.super
local super = aClass.super
if super then
local superStatic = super.static
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
setmetatable(klass.static, { __index = function(_,k) return dict[k] end })
setmetatable(aClass.static, { __index = function(_,k) return dict[k] end })
end
end
local function _setClassMetatable(klass)
setmetatable(klass, {
__tostring = function() return "class " .. klass.name end,
__index = klass.static,
__newindex = klass.__instanceDict,
local function _setClassMetatable(aClass)
setmetatable(aClass, {
__tostring = function() return "class " .. aClass.name end,
__index = aClass.static,
__newindex = aClass.__instanceDict,
__call = function(self, ...) return self:new(...) end
})
end
local function _createClass(name, super)
local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
klass.subclasses = setmetatable({}, {__mode = "k"})
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
aClass.subclasses = setmetatable({}, {__mode = "k"})
_setClassDictionariesMetatables(klass)
_setClassMetatable(klass)
_setClassDictionariesMetatables(aClass)
_setClassMetatable(aClass)
return klass
return aClass
end
local function _createLookupMetamethod(klass, name)
local function _createLookupMetamethod(aClass, name)
return function(...)
local method = klass.super[name]
assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" )
local method = aClass.super[name]
assert( type(method)=='function', tostring(aClass) .. " doesn't implement metamethod '" .. name .. "'" )
return method(...)
end
end
local function _setClassMetamethods(klass)
for _,m in ipairs(klass.__metamethods) do
klass[m]= _createLookupMetamethod(klass, m)
local function _setClassMetamethods(aClass)
for _,m in ipairs(aClass.__metamethods) do
aClass[m]= _createLookupMetamethod(aClass, m)
end
end
local function _setDefaultInitializeMethod(klass, super)
klass.initialize = function(instance, ...)
local function _setDefaultInitializeMethod(aClass, super)
aClass.initialize = function(instance, ...)
return super.initialize(instance, ...)
end
end
local function _includeMixin(klass, mixin)
local function _includeMixin(aClass, mixin)
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
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
if mixin.static then
for name,method in pairs(mixin.static) do
klass.static[name] = method
aClass.static[name] = method
end
end
if type(mixin.included)=="function" then mixin:included(klass) end
klass.__mixins[mixin] = true
if type(mixin.included)=="function" then mixin:included(aClass) end
aClass.__mixins[mixin] = true
end
local Object = _createClass("Object", nil)
@ -100,7 +100,7 @@ Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le',
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' }
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)
end
@ -111,7 +111,7 @@ function Object.static:new(...)
end
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")
local subclass = _createClass(name, self)
@ -125,32 +125,48 @@ 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( ... )
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
return self
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:__tostring() return "instance of " .. tostring(self.class) end
function instanceOf(aClass, obj)
if type(aClass) ~= 'table' or type(obj) ~= 'table' or not obj.class then return false end
if obj.class == aClass then return true end
return subclassOf(aClass, obj.class)
function Object:isInstanceOf(aClass)
return type(self) == 'table' and
type(self.class) == 'table' and
type(aClass) == 'table' and
( aClass == self.class or
type(aClass.isSubclassOf) == 'function' and
self.class:isSubclassOf(aClass)
)
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, ...)
super = super or Object

View File

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

View File

@ -1,89 +1,89 @@
local class = require 'middleclass'
local Object = class.Object
context('Object', function()
describe('Object', function()
context('name', function()
test('is correctly set', function()
assert_equal(Object.name, 'Object')
describe('name', function()
it('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')
describe('tostring', function()
it('returns "class Object"', function()
assert.equal(tostring(Object), 'class Object')
end)
end)
context('()', function()
test('returns an object, like Object:new()', function()
describe('()', function()
it('returns an object, like Object:new()', function()
local obj = Object()
assert_true(instanceOf(Object, obj))
assert.is_true(obj:isInstanceOf(Object))
end)
end)
context('subclass', function()
describe('subclass', function()
test('throws an error when used without the :', function()
assert_error(function() Object.subclass() end)
it('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)
it('throws an error when no name is given', function()
assert.error( function() Object:subclass() end)
end)
context('when given a class name', function()
describe('when given a class name', function()
local SubClass
before(function()
before_each(function()
SubClass = Object:subclass('SubClass')
end)
test('it returns a class with the correct name', function()
assert_equal(SubClass.name, 'SubClass')
it('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)
it('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])
it('it includes the subclass in the list of subclasses', function()
assert.is_true(Object.subclasses[SubClass])
end)
end)
end)
context('instance creation', function()
describe('instance creation', function()
local SubClass
before(function()
before_each(function()
SubClass = class('SubClass')
function SubClass:initialize() self.mark=true end
end)
context('allocate', function()
describe('allocate', function()
test('allocates instances properly', function()
it('allocates instances properly', function()
local instance = SubClass:allocate()
assert_equal(instance.class, SubClass)
assert_equal(tostring(instance), "instance of " .. tostring(SubClass))
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)
it('throws an error when used without the :', function()
assert.error(Object.allocate)
end)
test('does not call the initializer', function()
it('does not call the initializer', function()
local allocated = SubClass:allocate()
assert_nil(allocated.mark)
assert.is_nil(allocated.mark)
end)
test('can be overriden', function()
it('can be overriden', function()
local previousAllocate = SubClass.static.allocate
@ -94,33 +94,249 @@ context('Object', function()
end
local allocated = SubClass:allocate()
assert_true(allocated.mark)
assert.is_true(allocated.mark)
end)
end)
context('new', function()
describe('new', function()
test('initializes instances properly', function()
it('initializes instances properly', function()
local instance = SubClass:new()
assert_equal(instance.class, SubClass)
assert.equal(instance.class, SubClass)
end)
test('throws an error when used without the :', function()
assert_error(SubClass.new)
it('throws an error when used without the :', function()
assert.error(SubClass.new)
end)
test('calls the initializer', function()
it('calls the initializer', function()
local initialized = SubClass:new()
assert_true(initialized.mark)
assert.is_true(initialized.mark)
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)
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)

View File

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

View File

@ -1,76 +1,76 @@
local class = require 'middleclass'
context('A Class', function()
describe('A Class', function()
context('Default stuff', function()
describe('Default stuff', function()
local AClass
before(function()
before_each(function()
AClass = class('AClass')
end)
context('name', function()
test('is correctly set', function()
assert_equal(AClass.name, 'AClass')
describe('name', function()
it('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')
describe('tostring', function()
it('returns "class *name*"', function()
assert.equal(tostring(AClass), 'class AClass')
end)
end)
context('()', function()
test('returns an object, like Class:new()', function()
describe('()', function()
it('returns an object, like Class:new()', function()
local obj = AClass()
assert_equal(obj.class, 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)
describe('include', function()
it('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)
it('throws an error when passed a non-table:', function()
assert.error(function() AClass:include(1) end)
end)
end)
context('subclass', function()
describe('subclass', function()
test('throws an error when used without the :', function()
assert_error(function() AClass.subclass() end)
it('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)
it('throws an error when no name is given', function()
assert.error( function() AClass:subclass() end)
end)
context('when given a subclass name', function()
describe('when given a subclass name', function()
local SubClass
before(function()
before_each(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')
it('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)
it('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)
it('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])
it('it includes the subclass in the list of subclasses', function()
assert.is_true(AClass.subclasses[SubClass])
end)
end)
@ -81,56 +81,56 @@ context('A Class', function()
context('attributes', function()
describe('attributes', function()
local A, B
before(function()
before_each(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')
it('are available after being initialized', function()
assert.equal(A.foo, 'foo')
end)
test('are available for subclasses', function()
assert_equal(B.foo, 'foo')
it('are available for subclasses', function()
assert.equal(B.foo, 'foo')
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'
assert_equal(B.foo, 'chunky bacon')
assert_equal(A.foo, 'foo')
assert.equal(B.foo, 'chunky bacon')
assert.equal(A.foo, 'foo')
end)
end)
context('methods', function()
describe('methods', function()
local A, B
before(function()
before_each(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')
it('are available after being initialized', function()
assert.equal(A:foo(), 'foo')
end)
test('are available for subclasses', function()
assert_equal(B:foo(), 'foo')
it('are available for subclasses', function()
assert.equal(B:foo(), 'foo')
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
assert_equal(B:foo(), 'chunky bacon')
assert_equal(A:foo(), 'foo')
assert.equal(B:foo(), 'chunky bacon')
assert.equal(A:foo(), 'foo')
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'
context('An instance', function()
describe('An instance', function()
context('attributes', function()
describe('attributes', function()
local Person
before(function()
before_each(function()
Person = class('Person')
function Person:initialize(name)
self.name = name
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')
assert_equal(bob.name, 'bob')
assert.equal(bob.name, 'bob')
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)
function AgedPerson:initialize(name, age)
Person.initialize(self, name)
@ -26,17 +26,17 @@ context('An instance', function()
end
local pete = AgedPerson:new('pete', 31)
assert_equal(pete.name, 'pete')
assert_equal(pete.age, 31)
assert.equal(pete.name, 'pete')
assert.equal(pete.age, 31)
end)
end)
context('methods', function()
describe('methods', function()
local A, B, a, b
before(function()
before_each(function()
A = class('A')
function A:overridden() return 'foo' end
function A:regular() return 'regular' end
@ -48,16 +48,16 @@ context('An instance', function()
b = B:new()
end)
test('are available for any instance', function()
assert_equal(a:overridden(), 'foo')
it('are available for any instance', function()
assert.equal(a:overridden(), 'foo')
end)
test('are inheritable', function()
assert_equal(b:regular(), 'regular')
it('are inheritable', function()
assert.equal(b:regular(), 'regular')
end)
test('are overridable', function()
assert_equal(b:overridden(), 'bar')
it('are overridable', function()
assert.equal(b:overridden(), 'bar')
end)
end)

View File

@ -1,9 +1,9 @@
local class = require 'middleclass'
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)
-- 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
@ -45,12 +45,12 @@ context('Metamethods', function()
__mul = { 4*a, Vector(4,8,12) }--,
--__index = { b[1], 3 }
}) do
test(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2])
it(metamethod .. ' works as expected', function()
assert.equal(values[1], values[2])
end)
end
context('Inherited Metamethods', function()
describe('Inherited Metamethods', function()
local Vector2= class('Vector2', Vector)
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) },
__mul = { 4*c, Vector(4,8,12) }
}) do
test(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2])
it(metamethod .. ' works as expected', function()
assert.equal(values[1], values[2])
end)
end
end)
end)
context('Default Metamethods', function()
describe('Default Metamethods', function()
local Peter, peter
before(function()
before_each(function()
Peter = class('Peter')
peter = Peter()
end)
context('A Class', function()
test('has a call metamethod properly set', function()
assert_true(instanceOf(Peter, peter))
describe('A Class', function()
it('has a call metamethod properly set', function()
assert.is_true(peter:isInstanceOf(Peter))
end)
test('has a tostring metamethod properly set', function()
assert_equal(tostring(Peter), 'class Peter')
it('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')
describe('An instance', function()
it('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)

View File

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