mirror of
https://github.com/kikito/middleclass.git
synced 2024-11-08 09:34:22 +00:00
Merge branch 'v4.0'
This commit is contained in:
commit
9cb11c4dd6
13
.travis.yml
13
.travis.yml
@ -10,12 +10,19 @@ env:
|
||||
|
||||
before_install:
|
||||
- pip install hererocks
|
||||
- hererocks base -r^ --$LUA
|
||||
- export PATH=$PATH:$PWD/base/bin # Add directory with all installed binaries to PATH
|
||||
- hererocks lua_install -r^ --$LUA
|
||||
- export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH
|
||||
|
||||
install:
|
||||
- luarocks install busted
|
||||
- luarocks install luacov
|
||||
- luarocks install luacov-coveralls
|
||||
|
||||
script:
|
||||
- busted -v
|
||||
- busted --verbose --coverage
|
||||
|
||||
after_success:
|
||||
- luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install
|
||||
|
||||
branches:
|
||||
except:
|
||||
|
@ -1,6 +1,14 @@
|
||||
middleclass changelog
|
||||
====================
|
||||
|
||||
Version 4.0.0
|
||||
|
||||
* Unified the method and metamethod lookup into a single algorithm
|
||||
* Added the capacity of setting up the `__index` metamethod in classes
|
||||
* Removed global `Object` (classes created with `class(<name>)` have no superclass now)
|
||||
* Removed default method `Class:implements(<mixin>)`
|
||||
* Renamed several internal functions
|
||||
|
||||
Version 3.2.0
|
||||
|
||||
* Changed the way metamethods were handled to fix certain bugs (un-stubbed metamethods could not be inherited)
|
||||
|
53
README.md
53
README.md
@ -2,6 +2,7 @@ middleclass
|
||||
===========
|
||||
|
||||
[![Build Status](https://travis-ci.org/kikito/middleclass.png?branch=master)](https://travis-ci.org/kikito/middleclass)
|
||||
[![Coverage Status](https://coveralls.io/repos/kikito/middleclass/badge.svg?branch=master&service=github)](https://coveralls.io/github/kikito/middleclass?branch=master)
|
||||
|
||||
A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
|
||||
|
||||
@ -39,6 +40,10 @@ Documentation
|
||||
|
||||
See the [github wiki page](https://github.com/kikito/middleclass/wiki) for examples & documentation.
|
||||
|
||||
You can read the `CHANGELOG.md` file to see what has changed on each version of this library.
|
||||
|
||||
If you need help updating to a new middleclass version, read `UPDATING.md`.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
@ -72,52 +77,4 @@ License
|
||||
|
||||
Middleclass is distributed under the MIT license.
|
||||
|
||||
Updating from 2.x
|
||||
=================
|
||||
|
||||
Middleclass used to expose several global variables on the main scope. It does not do that anymore.
|
||||
|
||||
`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this:
|
||||
|
||||
```lua
|
||||
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`
|
||||
|
||||
```lua
|
||||
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`.
|
||||
|
||||
Prior to 3.x:
|
||||
|
||||
```lua
|
||||
instanceOf(MyClass, obj)
|
||||
subclassOf(Object, aClass)
|
||||
includes(aMixin, aClass)
|
||||
```
|
||||
|
||||
Since 3.x:
|
||||
|
||||
```lua
|
||||
obj:isInstanceOf(MyClass)
|
||||
aClass:isSubclassOf(Object)
|
||||
aClass:includes(aMixin)
|
||||
```
|
||||
|
||||
The 3.x code snippet 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 whether `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:
|
||||
|
||||
```lua
|
||||
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 `instanceOf`, and `isSubclassOf` instead of `subclassOf`.
|
||||
|
69
UPDATING.md
Normal file
69
UPDATING.md
Normal file
@ -0,0 +1,69 @@
|
||||
Updating from 3.x to 4.x
|
||||
========================
|
||||
|
||||
In middleclass 4.0 there is no global `Object` class any more. Classes created with `class(<name>)` don't have a superclass any more.
|
||||
If you need a global `Object` class, you must create it explicitly and then use it when creating new classes:
|
||||
|
||||
```lua
|
||||
local Object = class('Object')
|
||||
|
||||
...
|
||||
|
||||
local MyClass = class('MyClass', Object)
|
||||
```
|
||||
|
||||
If you are using a library which depends on the internal implementation of middleclass (for example, [stateful.lua](https://github.com/kikito/stateful.lua) they might not work with middleclass 4.0. You might need to update those other libraries.
|
||||
|
||||
Middleclass 4.0 comes with support for `__index` metamethod support. If your library manipulated the classes' `__instanceDict` internal attribute, you might do the same thing now using `__index` instead.
|
||||
|
||||
Also note that the class method `:implements` has been removed.
|
||||
|
||||
Updating from 2.x to 3.x
|
||||
========================
|
||||
|
||||
Middleclass used to expose several global variables on the main scope. It does not do that anymore.
|
||||
|
||||
`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this:
|
||||
|
||||
```lua
|
||||
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`
|
||||
|
||||
```lua
|
||||
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`.
|
||||
|
||||
Prior to 3.x:
|
||||
|
||||
```lua
|
||||
instanceOf(MyClass, obj)
|
||||
subclassOf(Object, aClass)
|
||||
includes(aMixin, aClass)
|
||||
```
|
||||
|
||||
Since 3.x:
|
||||
|
||||
```lua
|
||||
obj:isInstanceOf(MyClass)
|
||||
aClass:isSubclassOf(Object)
|
||||
aClass:includes(aMixin)
|
||||
```
|
||||
|
||||
The 3.x code snippet 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 whether `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:
|
||||
|
||||
```lua
|
||||
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 `instanceOf`, and `isSubclassOf` instead of `subclassOf`.
|
263
middleclass.lua
263
middleclass.lua
@ -1,5 +1,5 @@
|
||||
local middleclass = {
|
||||
_VERSION = 'middleclass v3.2.0',
|
||||
_VERSION = 'middleclass v4.0.0',
|
||||
_DESCRIPTION = 'Object Orientation for Lua',
|
||||
_URL = 'https://github.com/kikito/middleclass',
|
||||
_LICENSE = [[
|
||||
@ -28,176 +28,151 @@ local middleclass = {
|
||||
]]
|
||||
}
|
||||
|
||||
local _metamethods = {}
|
||||
for m in ([[ add band bor bxor bnot call concat div eq
|
||||
gc ipairs idiv le len lt metatable mod mode
|
||||
mul pairs pow shl shr sub tostring unm ]]):gmatch("%S+") do
|
||||
_metamethods['__' .. m] = true
|
||||
end
|
||||
|
||||
local function _setClassDictionariesMetatables(aClass)
|
||||
local dict = aClass.__instanceDict
|
||||
dict.__index = dict
|
||||
|
||||
local super = aClass.super
|
||||
if super then
|
||||
local superStatic = super.static
|
||||
setmetatable(dict, { __index = super.__instanceDict })
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or superStatic[k] end })
|
||||
local function _createIndexWrapper(aClass, f)
|
||||
if f == nil then
|
||||
return aClass.__instanceDict
|
||||
else
|
||||
setmetatable(aClass.static, { __index = function(_,k) return dict[k] end })
|
||||
end
|
||||
end
|
||||
return function(self, name)
|
||||
local value = aClass.__instanceDict[name]
|
||||
|
||||
local function _propagateMetamethod(aClass, name, f)
|
||||
for subclass in pairs(aClass.subclasses) do
|
||||
if not subclass.__metamethods[name] then
|
||||
subclass.__instanceDict[name] = f
|
||||
_propagateMetamethod(subclass, name, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _updateClassDict(aClass, key, value)
|
||||
if _metamethods[key] then
|
||||
if value == nil then
|
||||
aClass.__metamethods[key] = nil
|
||||
if aClass.super then
|
||||
value = aClass.super.__instanceDict[key]
|
||||
if value ~= nil then
|
||||
return value
|
||||
elseif type(f) == "function" then
|
||||
return (f(self, name))
|
||||
else
|
||||
return f[name]
|
||||
end
|
||||
else
|
||||
aClass.__metamethods[key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_propagateMetamethod(aClass, key, value)
|
||||
local function _propagateInstanceMethod(aClass, name, f)
|
||||
f = name == "__index" and _createIndexWrapper(aClass, f) or f
|
||||
aClass.__instanceDict[name] = f
|
||||
|
||||
for subclass in pairs(aClass.subclasses) do
|
||||
if rawget(subclass.__declaredMethods, name) == nil then
|
||||
_propagateInstanceMethod(subclass, name, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _declareInstanceMethod(aClass, name, f)
|
||||
aClass.__declaredMethods[name] = f
|
||||
|
||||
if f == nil and aClass.super then
|
||||
f = aClass.super.__instanceDict[name]
|
||||
end
|
||||
|
||||
aClass.__instanceDict[key] = value
|
||||
_propagateInstanceMethod(aClass, name, f)
|
||||
end
|
||||
|
||||
local function _setClassMetatable(aClass)
|
||||
setmetatable(aClass, {
|
||||
__tostring = function() return "class " .. aClass.name end,
|
||||
__index = aClass.static,
|
||||
__newindex = _updateClassDict,
|
||||
__call = function(self, ...) return self:new(...) end
|
||||
})
|
||||
end
|
||||
local function _tostring(self) return "class " .. self.name end
|
||||
local function _call(self, ...) return self:new(...) end
|
||||
|
||||
local function _createClass(name, super)
|
||||
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict = {}, __metamethods = {} }
|
||||
aClass.subclasses = setmetatable({}, {__mode = "k"})
|
||||
local dict = {}
|
||||
dict.__index = dict
|
||||
|
||||
_setClassDictionariesMetatables(aClass)
|
||||
_setClassMetatable(aClass)
|
||||
local aClass = { name = name, super = super, static = {},
|
||||
__instanceDict = dict, __declaredMethods = {},
|
||||
subclasses = setmetatable({}, {__mode='k'}) }
|
||||
|
||||
if super then
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
|
||||
else
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
|
||||
end
|
||||
|
||||
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
|
||||
__call = _call, __newindex = _declareInstanceMethod })
|
||||
|
||||
return aClass
|
||||
end
|
||||
|
||||
local function _setSubclassMetamethods(aClass, subclass)
|
||||
for m in pairs(_metamethods) do
|
||||
subclass.__instanceDict[m] = aClass.__instanceDict[m]
|
||||
end
|
||||
end
|
||||
|
||||
local function _setDefaultInitializeMethod(aClass, super)
|
||||
aClass.initialize = function(instance, ...)
|
||||
return super.initialize(instance, ...)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
if name ~= "included" and name ~= "static" then aClass[name] = method end
|
||||
end
|
||||
if mixin.static then
|
||||
for name,method in pairs(mixin.static) do
|
||||
aClass.static[name] = method
|
||||
end
|
||||
|
||||
for name,method in pairs(mixin.static or {}) do
|
||||
aClass.static[name] = method
|
||||
end
|
||||
|
||||
if type(mixin.included)=="function" then mixin:included(aClass) end
|
||||
aClass.__mixins[mixin] = true
|
||||
return aClass
|
||||
end
|
||||
|
||||
local Object = _createClass("Object", nil)
|
||||
local DefaultMixin = {
|
||||
__tostring = function(self) return "instance of " .. tostring(self.class) end,
|
||||
|
||||
function Object.static:allocate()
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||
return setmetatable({ class = self }, self.__instanceDict)
|
||||
initialize = function(self, ...) end,
|
||||
|
||||
isInstanceOf = function(self, 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,
|
||||
|
||||
static = {
|
||||
allocate = function(self)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||
return setmetatable({ class = self }, self.__instanceDict)
|
||||
end,
|
||||
|
||||
new = function(self, ...)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
|
||||
local instance = self:allocate()
|
||||
instance:initialize(...)
|
||||
return instance
|
||||
end,
|
||||
|
||||
subclass = function(self, name)
|
||||
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)
|
||||
|
||||
for name, f in pairs(self.__instanceDict) do
|
||||
_propagateInstanceMethod(subclass, name, f)
|
||||
end
|
||||
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
|
||||
|
||||
self.subclasses[subclass] = true
|
||||
self:subclassed(subclass)
|
||||
|
||||
return subclass
|
||||
end,
|
||||
|
||||
subclassed = function(self, other) end,
|
||||
|
||||
isSubclassOf = function(self, 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,
|
||||
|
||||
include = function(self, ...)
|
||||
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 middleclass.class(name, super)
|
||||
assert(type(name) == 'string', "A name (string) is needed for the new class")
|
||||
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
|
||||
end
|
||||
|
||||
function Object.static:new(...)
|
||||
local instance = self:allocate()
|
||||
instance:initialize(...)
|
||||
return instance
|
||||
end
|
||||
|
||||
function Object.static:subclass(name)
|
||||
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)
|
||||
_setSubclassMetamethods(self, subclass)
|
||||
_setDefaultInitializeMethod(subclass, self)
|
||||
self.subclasses[subclass] = true
|
||||
self:subclassed(subclass)
|
||||
|
||||
return subclass
|
||||
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(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 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 middleclass.class(name, super, ...)
|
||||
super = super or Object
|
||||
return super:subclass(name, ...)
|
||||
end
|
||||
|
||||
middleclass.Object = Object
|
||||
|
||||
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
|
||||
|
||||
return middleclass
|
||||
|
@ -13,7 +13,7 @@ describe('class()', 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.is_nil(TheClass.super)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
local class = require 'middleclass'
|
||||
local Object = class.Object
|
||||
|
||||
describe('Object', function()
|
||||
|
||||
describe('Default methods', function()
|
||||
local Object
|
||||
before_each(function()
|
||||
Object = class('Object')
|
||||
end)
|
||||
|
||||
describe('name', function()
|
||||
it('is correctly set', function()
|
||||
@ -95,12 +97,11 @@ describe('Object', function()
|
||||
|
||||
local allocated = SubClass:allocate()
|
||||
assert.is_true(allocated.mark)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe('new', function()
|
||||
describe('new', function()
|
||||
|
||||
it('initializes instances properly', function()
|
||||
local instance = SubClass:new()
|
||||
@ -163,12 +164,6 @@ describe('Object', function()
|
||||
|
||||
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))
|
||||
@ -241,12 +236,6 @@ describe('Object', function()
|
||||
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))
|
||||
@ -270,73 +259,6 @@ describe('Object', function()
|
||||
|
||||
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)
|
||||
|
||||
|
@ -204,6 +204,92 @@ describe('Metamethods', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Custom __index and __newindex', function()
|
||||
describe('Tables', function()
|
||||
local Proxy, fallback, p
|
||||
before_each(function()
|
||||
Proxy = class('Proxy')
|
||||
fallback = {foo = 'bar', common = 'fallback'}
|
||||
Proxy.__index = fallback
|
||||
Proxy.__newindex = fallback
|
||||
Proxy.common = 'class'
|
||||
p = Proxy()
|
||||
end)
|
||||
|
||||
it('uses __index', function()
|
||||
assert.equal(p.foo, 'bar')
|
||||
end)
|
||||
|
||||
it('does not use __index when field exists in class', function()
|
||||
assert.equal(p.common, 'class')
|
||||
end)
|
||||
|
||||
it('uses __newindex', function()
|
||||
p.key = 'value'
|
||||
assert.equal(fallback.key, 'value')
|
||||
end)
|
||||
|
||||
it('uses __newindex when field exists in class', function()
|
||||
p.common = 'value'
|
||||
assert.equal(p.common, 'class')
|
||||
assert.equal(Proxy.common, 'class')
|
||||
assert.equal(fallback.common, 'value')
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Functions', function()
|
||||
local Namespace, Rectangle, r
|
||||
before_each(function()
|
||||
Namespace = class('Namespace')
|
||||
function Namespace:__index(name)
|
||||
local getter = self.class[name.."Getter"]
|
||||
if getter then return getter(self) end
|
||||
end
|
||||
function Namespace:__newindex(name, value)
|
||||
local setter = self.class[name.."Setter"]
|
||||
if setter then setter(self, value) else rawset(self, name, value) end
|
||||
end
|
||||
Rectangle = class('Rectangle', Namespace)
|
||||
function Rectangle:initialize(x, y, scale)
|
||||
self._scale, self.x, self.y = 1, x, y
|
||||
self.scale = scale
|
||||
end
|
||||
function Rectangle:scaleGetter() return self._scale end
|
||||
function Rectangle:scaleSetter(v)
|
||||
self.x = self.x*v/self._scale
|
||||
self.y = self.y*v/self._scale
|
||||
self._scale = v
|
||||
end
|
||||
function Rectangle:areaGetter() return self.x * self.y end
|
||||
r = Rectangle(3, 4, 2)
|
||||
end)
|
||||
|
||||
it('uses setter', function()
|
||||
assert.equal(r.x, 6)
|
||||
assert.equal(r.y, 8)
|
||||
r.scale = 3
|
||||
assert.equal(r.x, 9)
|
||||
assert.equal(r.y, 12)
|
||||
end)
|
||||
|
||||
it('uses getters', function()
|
||||
assert.equal(r.scale, 2)
|
||||
assert.equal(r.area, 48)
|
||||
end)
|
||||
|
||||
it('updates inherited __index', function()
|
||||
function Namespace.__index() return 42 end
|
||||
assert.equal(r.area, 42)
|
||||
function Rectangle.__index() return 24 end
|
||||
assert.equal(r.area, 24)
|
||||
function Namespace.__index() return 96 end
|
||||
assert.equal(r.area, 24)
|
||||
Rectangle.__index = nil
|
||||
assert.equal(r.area, 96)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Default Metamethods', function()
|
||||
|
||||
local Peter, peter
|
||||
|
Loading…
Reference in New Issue
Block a user