mirror of
https://github.com/Reuh/classtoi.git
synced 2025-10-27 12:19:31 +00:00
246 lines
8.8 KiB
Lua
246 lines
8.8 KiB
Lua
local T = require("knife-test")
|
|
|
|
-- luacheck: ignore T
|
|
T("Given the base class", function(T)
|
|
local class = dofile(arg[1] or "../classtoi.lua")
|
|
|
|
-- 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)
|