mirror of
https://github.com/Reuh/classtoi.git
synced 2025-10-27 12:19:31 +00:00
Added tests, fixed __index, custom constructor return values, 0.1.4
This commit is contained in:
parent
0214c99cc4
commit
cc53045d4d
4 changed files with 426 additions and 11 deletions
|
|
@ -1,3 +1,7 @@
|
||||||
|
0.1.4:
|
||||||
|
- :new non-nil custom returns values now replace the usual returned instance
|
||||||
|
- Custom inherited __index metamethods are now called on the correct class instead of the class it is defined in.
|
||||||
|
- Added tests.
|
||||||
0.1.3:
|
0.1.3:
|
||||||
- Added __name attribute in BaseClass for a more informative __tostring.
|
- Added __name attribute in BaseClass for a more informative __tostring.
|
||||||
- Fixed instanciation error when no custom :new method is defined.
|
- Fixed instanciation error when no custom :new method is defined.
|
||||||
|
|
|
||||||
31
classtoi.lua
31
classtoi.lua
|
|
@ -1,4 +1,4 @@
|
||||||
--- Reuh's class library version 0.1.3. Lua 5.1-5.3 and LuaJit compatible.
|
--- Reuh's class library version 0.1.4. Lua 5.1-5.3 and LuaJit compatible.
|
||||||
-- Objects and classes behavior are identical, so you can consider this to be somewhat prototype-based.
|
-- Objects and classes behavior are identical, so you can consider this to be somewhat prototype-based.
|
||||||
-- Features:
|
-- Features:
|
||||||
-- * Multiple inheritance with class(parents...) or someclass(newstuff...)
|
-- * Multiple inheritance with class(parents...) or someclass(newstuff...)
|
||||||
|
|
@ -8,11 +8,12 @@
|
||||||
-- * Instanciate with class:new(...)
|
-- * Instanciate with class:new(...)
|
||||||
-- * Test inheritance relations with class/object.is(thing, isThis)
|
-- * Test inheritance relations with class/object.is(thing, isThis)
|
||||||
-- * Call object:new(...) on instanciation
|
-- * Call object:new(...) on instanciation
|
||||||
|
-- * If object:new(...) returns non-nil values, they will be returned instead of the instance
|
||||||
-- * Call class.__inherit(class, inheritingClass) when creating a class inheriting the previous class. If class.__inherit returns a value, it will
|
-- * Call class.__inherit(class, inheritingClass) when creating a class inheriting the previous class. If class.__inherit returns a value, it will
|
||||||
-- be used as the parent table instead of class, allowing some pretty fancy behavior (it's like an inheritance metamethod).
|
-- be used as the parent table instead of class, allowing some pretty fancy behavior (it's like an inheritance metamethod).
|
||||||
-- * Implements Class Commons
|
-- * Implements Class Commons
|
||||||
-- * I don't like to do this, but you can redefine every field and metamethod after class creation (except __index and __super).
|
-- * I don't like to do this, but you can redefine every field and metamethod after class creation (except __index and __super).
|
||||||
-- Not features (things you may want to know):
|
-- Not features / Things you may want to know:
|
||||||
-- * Will set the metatable of all parent classes/tables if no metatable is set (the table will be its own metatable).
|
-- * Will set the metatable of all parent classes/tables if no metatable is set (the table will be its own metatable).
|
||||||
-- * You can't redefine __super (any __super you define will be only avaible by searching in the default __super contents).
|
-- * You can't redefine __super (any __super you define will be only avaible by searching in the default __super contents).
|
||||||
-- * Redefining __super or __index after class creation will break everything (though it should be ok with new, is, __call and everything else).
|
-- * Redefining __super or __index after class creation will break everything (though it should be ok with new, is, __call and everything else).
|
||||||
|
|
@ -20,6 +21,8 @@
|
||||||
-- will return the default method and not the one you've defined. However, theses defaults will be replaced by yours automatically on instanciation,
|
-- will return the default method and not the one you've defined. However, theses defaults will be replaced by yours automatically on instanciation,
|
||||||
-- except __super and __index, but __index should call your __index and act like you expect. __super will however always be the default one
|
-- except __super and __index, but __index should call your __index and act like you expect. __super will however always be the default one
|
||||||
-- and doesn't proxy in any way yours.
|
-- and doesn't proxy in any way yours.
|
||||||
|
-- * __index metamethods will be called with an extra third argument, which is the current class being searched in the inheritance tree.
|
||||||
|
-- You can safely ignore it.
|
||||||
--
|
--
|
||||||
-- Please also note that the last universal ancestor of the classes (defined here in BaseClass) sets the default __tostring method
|
-- Please also note that the last universal ancestor of the classes (defined here in BaseClass) sets the default __tostring method
|
||||||
-- and __name attribute for nice class-name-printing. Unlike the previoulsy described attributes and methods however, it is done in a normal
|
-- and __name attribute for nice class-name-printing. Unlike the previoulsy described attributes and methods however, it is done in a normal
|
||||||
|
|
@ -59,17 +62,22 @@ methods = {
|
||||||
-- A new object will only be created if calling the method "class:new(...)", if you call for example "class.new(someTable, ...)", it
|
-- A new object will only be created if calling the method "class:new(...)", if you call for example "class.new(someTable, ...)", it
|
||||||
-- will only execute the constructor defined in the class on someTable. This can be used to execute the parent constructor in a child
|
-- will only execute the constructor defined in the class on someTable. This can be used to execute the parent constructor in a child
|
||||||
-- object, for example.
|
-- object, for example.
|
||||||
|
-- It should also be noted that if the new method returns non-nil value(s), they will be returned instead of the object.
|
||||||
["!new"] = function(self, ...)
|
["!new"] = function(self, ...)
|
||||||
if lastIndexed == self then
|
if lastIndexed == self then
|
||||||
local obj = self()
|
local obj, ret = self(), nil
|
||||||
-- Setting class methods to the ones found in parents (we use rawset in order to avoid calling the __newindex metamethod)
|
-- Setting class methods to the ones found in parents (we use rawset in order to avoid calling the __newindex metamethod)
|
||||||
different = methods["!new"] rawset(obj, "new", obj:__index("new") or nil)
|
different = methods["!new"] rawset(obj, "new", obj:__index("new") or nil)
|
||||||
different = methods["!is"] rawset(obj, "is", obj:__index("is") or nil)
|
different = methods["!is"] rawset(obj, "is", obj:__index("is") or nil)
|
||||||
different = methods.__call rawset(obj, "__call", obj:__index("__call") or nil)
|
different = methods.__call rawset(obj, "__call", obj:__index("__call") or nil)
|
||||||
different = nil
|
different = nil
|
||||||
-- Call constructor
|
-- Call constructor
|
||||||
if obj.new ~= methods["!new"] and type(obj.new) == "function" then obj:new(...) end
|
if obj.new ~= methods["!new"] and type(obj.new) == "function" then ret = { obj:new(...) } end
|
||||||
return obj
|
if not ret or #ret == 0 then
|
||||||
|
return obj
|
||||||
|
else
|
||||||
|
return unpack(ret)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
different = methods["!new"]
|
different = methods["!new"]
|
||||||
local new = lastIndexed:__index("new") or nil
|
local new = lastIndexed:__index("new") or nil
|
||||||
|
|
@ -104,18 +112,19 @@ methods = {
|
||||||
-- When getting a value from the class, it will be first searched in stuff, then in Base1, then in all Base1 parents,
|
-- When getting a value from the class, it will be first searched in stuff, then in Base1, then in all Base1 parents,
|
||||||
-- then in Base2, then in Base2 parents.
|
-- then in Base2, then in Base2 parents.
|
||||||
-- A way to describe this will be search in the latest added tables (from the farthest child to the first parents), from left-to-right.
|
-- A way to describe this will be search in the latest added tables (from the farthest child to the first parents), from left-to-right.
|
||||||
__index = function(self, k)
|
-- self always refer to the initial table the metamethod was called on, super refers to the class currently being searched for a value.
|
||||||
|
__index = function(self, k, super)
|
||||||
local proxied = methods["!"..tostring(k)]
|
local proxied = methods["!"..tostring(k)]
|
||||||
if proxied ~= nil and proxied ~= different then -- proxied methods
|
if proxied ~= nil and proxied ~= different then -- proxied methods
|
||||||
lastIndexed = self
|
lastIndexed = self
|
||||||
return proxied
|
return proxied
|
||||||
end
|
end
|
||||||
for _, t in ipairs(self.__super) do -- search in super (will auto-follow __index metamethods)
|
for _, t in ipairs((super or self).__super) do -- search in super (will follow __index metamethods)
|
||||||
local val = t[k]
|
local val = rawget(t, k)
|
||||||
if val ~= nil and val ~= different then return val end
|
if val ~= nil and val ~= different then return val end
|
||||||
-- If different search is on and the direct t[k] returns an identical value, force the __index metamethod search.
|
-- Also covers the case when different search is enabled and the raw t[k] returns an identical value, so the __index metamethod search will be tried for another value.
|
||||||
if different ~= nil and getmetatable(t) and getmetatable(t).__index then
|
if getmetatable(t) and getmetatable(t).__index then
|
||||||
val = getmetatable(t):__index(k)
|
val = getmetatable(t).__index(self, k, t)
|
||||||
if val ~= nil and val ~= different then return val end
|
if val ~= nil and val ~= different then return val end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
156
knife-test.lua
Normal file
156
knife-test.lua
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
--[[
|
||||||
|
knife.test - A fixture-free test framework.
|
||||||
|
|
||||||
|
https://github.com/airstruck/knife
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 airstruck
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local test, testAssert, testError
|
||||||
|
|
||||||
|
-- Create a node representing a test section
|
||||||
|
local function createNode (parent, description, process)
|
||||||
|
return setmetatable({
|
||||||
|
parent = parent,
|
||||||
|
description = description,
|
||||||
|
process = process,
|
||||||
|
nodes = {},
|
||||||
|
activeNodeIndex = 1,
|
||||||
|
currentNodeIndex = 0,
|
||||||
|
assert = testAssert,
|
||||||
|
error = testError,
|
||||||
|
}, { __call = test })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run a node
|
||||||
|
local function runNode (node)
|
||||||
|
node.currentNodeIndex = 0
|
||||||
|
return node:process()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get the root node for a given node
|
||||||
|
local function getRootNode (node)
|
||||||
|
local parent = node.parent
|
||||||
|
return parent and getRootNode(parent) or node
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the active child node of the given node
|
||||||
|
local function updateActiveNode (node, description, process)
|
||||||
|
local activeNodeIndex = node.activeNodeIndex
|
||||||
|
local nodes = node.nodes
|
||||||
|
local activeNode = nodes[activeNodeIndex]
|
||||||
|
|
||||||
|
if not activeNode then
|
||||||
|
activeNode = createNode(node, description, process)
|
||||||
|
nodes[activeNodeIndex] = activeNode
|
||||||
|
else
|
||||||
|
activeNode.process = process
|
||||||
|
end
|
||||||
|
|
||||||
|
getRootNode(node).lastActiveLeaf = activeNode
|
||||||
|
|
||||||
|
return activeNode
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run the active child node of the given node
|
||||||
|
local function runActiveNode (node, description, process)
|
||||||
|
local activeNode = updateActiveNode(node, description, process)
|
||||||
|
return runNode(activeNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get ancestors of a node, including the node
|
||||||
|
local function getAncestors (node)
|
||||||
|
local ancestors = { node }
|
||||||
|
for ancestor in function () return node.parent end do
|
||||||
|
ancestors[#ancestors + 1] = ancestor
|
||||||
|
node = ancestor
|
||||||
|
end
|
||||||
|
return ancestors
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Print a message describing one execution path in the test scenario
|
||||||
|
local function printScenario (node)
|
||||||
|
local ancestors = getAncestors(node)
|
||||||
|
for i = #ancestors, 1, -1 do
|
||||||
|
io.stderr:write(ancestors[i].description or '')
|
||||||
|
io.stderr:write('\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Print a message and stop the test scenario when an assertion fails
|
||||||
|
local function failAssert (node, description, message)
|
||||||
|
io.stderr:write(message or '')
|
||||||
|
io.stderr:write('\n\n')
|
||||||
|
printScenario(node)
|
||||||
|
io.stderr:write(description or '')
|
||||||
|
io.stderr:write('\n\n')
|
||||||
|
error(message or '', 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a branch node for a test scenario
|
||||||
|
test = function (node, description, process)
|
||||||
|
node.currentNodeIndex = node.currentNodeIndex + 1
|
||||||
|
if node.currentNodeIndex == node.activeNodeIndex then
|
||||||
|
return runActiveNode(node, description, process)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test an assertion
|
||||||
|
testAssert = function (self, value, description)
|
||||||
|
if not value then
|
||||||
|
return failAssert(self, description, 'Test failed: assertion failed')
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Expect function f to fail
|
||||||
|
testError = function (self, f, description)
|
||||||
|
if pcall(f) then
|
||||||
|
return failAssert(self, description, 'Test failed: expected error')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create the root node for a test scenario
|
||||||
|
local function T (description, process)
|
||||||
|
local root = createNode(nil, description, process)
|
||||||
|
|
||||||
|
runNode(root)
|
||||||
|
while root.activeNodeIndex <= #root.nodes do
|
||||||
|
local lastActiveBranch = root.lastActiveLeaf.parent
|
||||||
|
lastActiveBranch.activeNodeIndex = lastActiveBranch.activeNodeIndex + 1
|
||||||
|
runNode(root)
|
||||||
|
end
|
||||||
|
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run any other files passed from CLI.
|
||||||
|
if arg and arg[0] and arg[0]:gmatch('test.lua') then
|
||||||
|
_G.T = T
|
||||||
|
for i = 1, #arg do
|
||||||
|
dofile(arg[i])
|
||||||
|
end
|
||||||
|
_G.T = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return T
|
||||||
246
test.lua
Normal file
246
test.lua
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
local T = require("knife-test")
|
||||||
|
|
||||||
|
-- luacheck: ignore T
|
||||||
|
T("Given the base class", function(T)
|
||||||
|
local class = require("classtoi")
|
||||||
|
|
||||||
|
-- Inheritance
|
||||||
|
T("When subclassed with an attribute", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
attribute = "thing"
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing.attribute == "thing", "Then the attribute should be set on the subclass")
|
||||||
|
T:assert(class.attribute == nil, "Then the attribute shouldn't be set on the base class'")
|
||||||
|
|
||||||
|
T:assert(class.is(Thing), "Then the subclass should be a subclass of the base class (class.is)")
|
||||||
|
T:assert(Thing:is(class), "Then the subclass should be a subclass of the base class (subclass:is)")
|
||||||
|
|
||||||
|
T("When the subclass is instanced", function(T)
|
||||||
|
local thing = Thing:new()
|
||||||
|
|
||||||
|
T:assert(thing.attribute == "thing", "Then the attribute should be kept")
|
||||||
|
|
||||||
|
T:assert(class.is(thing), "Then the object should be a subclass of the base class (class.is(object))")
|
||||||
|
T:assert(thing:is(class), "Then the object should be a subclass of the base class (object:is(class))")
|
||||||
|
T:assert(thing:is(Thing), "Then the object should be a subclass of the subclass (object:is(subclass))")
|
||||||
|
T:assert(Thing.is(thing), "Then the object should be a subclass of the subclass (subclass.is(object))")
|
||||||
|
|
||||||
|
T("When setting the attribute on the instance", function(T)
|
||||||
|
thing.attribute = "not the same thing"
|
||||||
|
|
||||||
|
T:assert(thing.attribute == "not the same thing", "Then the attribute should be set for the object")
|
||||||
|
T:assert(Thing.attribute == "thing", "Then the attribute should be kept for the subclass")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
T("When the subclassed is subclassed with two parents", function(T)
|
||||||
|
local OtherThing = class {
|
||||||
|
attribute = "other thing",
|
||||||
|
other = true
|
||||||
|
}
|
||||||
|
|
||||||
|
local SubThing = Thing(OtherThing)
|
||||||
|
|
||||||
|
T:assert(SubThing.attribute == "other thing", "Then the last added parent should have priority")
|
||||||
|
|
||||||
|
local SubOtherThing = class(Thing, OtherThing)
|
||||||
|
|
||||||
|
T:assert(SubOtherThing.attribute == "thing", "Then the left-most class should have priority")
|
||||||
|
|
||||||
|
T:assert(SubThing.other and SubOtherThing.other, "Then new attribute should be always inherited")
|
||||||
|
|
||||||
|
T("When adding a method to the first subclass", function(T)
|
||||||
|
Thing.action = function(self, arg)
|
||||||
|
self.attribute = arg
|
||||||
|
end
|
||||||
|
|
||||||
|
T("When calling it on the first subclass", function(T)
|
||||||
|
Thing:action("new thing")
|
||||||
|
|
||||||
|
T:assert(Thing.attribute == "new thing", "Then it affect the first subclass")
|
||||||
|
T:assert(SubOtherThing.attribute == "new thing", "Then it affect children wich inherit the modified attribute")
|
||||||
|
T:assert(SubThing.attribute == "other thing", "Then it doesn't affect children which doesn't inherit the modified attribute")
|
||||||
|
end)
|
||||||
|
|
||||||
|
T("When calling it on another subclass", function(T)
|
||||||
|
SubOtherThing:action("new thing")
|
||||||
|
|
||||||
|
T:assert(Thing.attribute == "thing", "Then it doesn't affect the parent subclass")
|
||||||
|
T:assert(SubOtherThing.attribute == "new thing", "Then it affect the subclass")
|
||||||
|
T:assert(SubThing.attribute == "other thing", "Then it doesn't affect other subclasses")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Constructor
|
||||||
|
T("When subclassed with a constructor", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
attribute = "class",
|
||||||
|
|
||||||
|
new = function(self, arg)
|
||||||
|
self.attribute = arg or "object"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing.attribute == "class", "Then the class should not call the constructor itself")
|
||||||
|
|
||||||
|
T("When the subclass is instanced without arguments", function(T)
|
||||||
|
local thing = Thing:new()
|
||||||
|
|
||||||
|
T:assert(thing.attribute == "object", "Then the constructor should have been called on the object without arguments")
|
||||||
|
T:assert(Thing.attribute == "class", "Then the constructor should not be called on the class")
|
||||||
|
end)
|
||||||
|
|
||||||
|
T("When the subclass is instanced without arguments", function(T)
|
||||||
|
local thing = Thing:new("stuff")
|
||||||
|
|
||||||
|
T:assert(thing.attribute == "stuff", "Then the constructor should have been called on the object with arguments")
|
||||||
|
T:assert(Thing.attribute == "class", "Then the constructor should not be called on the class")
|
||||||
|
end)
|
||||||
|
|
||||||
|
T("When the subclass is subclassed and instanced with another constructor", function(T)
|
||||||
|
local SubThing = Thing {
|
||||||
|
new = function(self)
|
||||||
|
self.sub = true
|
||||||
|
Thing.new(self, "a whole new thing")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
local subthing = SubThing:new()
|
||||||
|
|
||||||
|
T:assert(subthing.sub, "Then the new constructor is called on the new instance")
|
||||||
|
T:assert(not SubThing.sub, "Then the new constructor is not called on the class")
|
||||||
|
|
||||||
|
T:assert(subthing.attribute == "a whole new thing", "Then the parent new method was correctly accessed and called on the new instance")
|
||||||
|
T:assert(SubThing.attribute == "class", "Then the parent new method was not called on the class")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
T("When subclassed with a constructor returning non-nil values and instanced", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
new = function(self, arg)
|
||||||
|
return true, arg
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
local thing, other = Thing:new("mostly useless but cool")
|
||||||
|
|
||||||
|
T:assert(thing == true and other == "mostly useless but cool", "Then the constructor return value should be returned instead of an object")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Usual metamethods
|
||||||
|
T("When subclassed with a metamethod", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
value = 0,
|
||||||
|
__add = function(self, other)
|
||||||
|
self.value = self.value + other
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing.value == 0, "Then the attribute is set on the subclass")
|
||||||
|
|
||||||
|
Thing = Thing + 5
|
||||||
|
|
||||||
|
T:assert(Thing.value == 5, "Then the metamethod is correctly called on the subclass")
|
||||||
|
T:assert(class.value == nil, "Then the metamethod doesn't affect the base class")
|
||||||
|
|
||||||
|
T("When the subclass is instancied", function(T)
|
||||||
|
local thing = Thing:new()
|
||||||
|
thing = thing + 5
|
||||||
|
|
||||||
|
T:assert(thing.value == 10, "Then the metamethod is correctly called on the instance")
|
||||||
|
T:assert(Thing.value == 5, "Then the metamethod doesn't affect the parent class")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Redefining usual special class methods
|
||||||
|
T("When a subclass redefine one of the default class methods", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
is = function(self)
|
||||||
|
return "mashed potatoes"
|
||||||
|
end,
|
||||||
|
|
||||||
|
__call = function(self, arg)
|
||||||
|
return "hot potatoes with "..arg
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing:is(class) == true, "Then defaults methods are still used on the class")
|
||||||
|
T:assert(Thing():is(class) == true, "Then defaults metamethods are still used on the class")
|
||||||
|
|
||||||
|
T("When the subclass is instancied", function(T)
|
||||||
|
local thing = Thing:new()
|
||||||
|
|
||||||
|
T:assert(thing:is(class) == "mashed potatoes", "Then the redefined method is used on the instance")
|
||||||
|
T:assert(thing("melted raclette") == "hot potatoes with melted raclette", "Then the redefined metamethod is used on the instance")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Redefining __index
|
||||||
|
T("When subclassed with an __index metamethod", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
value = "other",
|
||||||
|
|
||||||
|
__index = function(self, key)
|
||||||
|
if key == "attribute" then
|
||||||
|
return "thing"
|
||||||
|
elseif key == "another" then
|
||||||
|
return self.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing.attribute == "thing", "Then the metamethod is called as expected")
|
||||||
|
T:assert(Thing.value == "other", "Then attribute access still works as expected")
|
||||||
|
T:assert(Thing.another == "other", "Then the metamethod seems to correctly pass a class as the first argument")
|
||||||
|
|
||||||
|
T("When the subclass is instanced", function(T)
|
||||||
|
local thing = Thing:new()
|
||||||
|
|
||||||
|
T:assert(thing.attribute == "thing", "Then the metamethod is called as expected")
|
||||||
|
T:assert(thing.value == "other", "Then attribute access still works as expected")
|
||||||
|
T:assert(thing.another == "other", "Then the metamethod seems to correctly pass a class as the first argument")
|
||||||
|
|
||||||
|
T("When changing a value returned by the metamethod on the instance", function(T)
|
||||||
|
thing.value = "new thing"
|
||||||
|
|
||||||
|
T:assert(thing.another == "new thing", "Then the metamethod was correctly called with the instance as the first argument")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Defining __inherit
|
||||||
|
T("When subclassed with an __inherit method", function(T)
|
||||||
|
local Thing = class {
|
||||||
|
attribute = "thing",
|
||||||
|
replace = false,
|
||||||
|
__inherit = function(self, inheritingClass)
|
||||||
|
inheritingClass.works = true
|
||||||
|
if self.replace then
|
||||||
|
return { attribute = "other thing" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
T:assert(Thing.works == true, "Then the __inherit method was correctly called on the class creation")
|
||||||
|
|
||||||
|
T("When subclassing the subclass", function(T)
|
||||||
|
Thing.works = "ok"
|
||||||
|
local SubThing = Thing()
|
||||||
|
|
||||||
|
T:assert(SubThing.attribute == "thing", "Then the child class still inherit the subclass")
|
||||||
|
T:assert(SubThing.works == true, "Then the __inherit method was correctly called on the inheriting class")
|
||||||
|
T:assert(Thing.works == "ok", "Then the __inherit method was correctly called on the inherited class")
|
||||||
|
end)
|
||||||
|
|
||||||
|
T("When subclassing the subclass with a return value", function(T)
|
||||||
|
Thing.replace = true
|
||||||
|
local SubThing = Thing()
|
||||||
|
|
||||||
|
T:assert(SubThing.attribute == "other thing", "Then the child class inherited the return value")
|
||||||
|
T:assert(SubThing.works == true, "Then the __inherit method was correctly called on the inheriting class")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue