mirror of
https://github.com/Reuh/classtoi.git
synced 2025-10-27 12:19:31 +00:00
Add classtoi-light
This commit is contained in:
parent
4db788df32
commit
3bb90f9034
6 changed files with 128 additions and 1 deletions
156
test/knife-test.lua
Normal file
156
test/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/test.lua
Normal file
246
test/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 = 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue