return function()
local t = require(script.Parent)
it("should support type checking with type()", function()
assert(t.type("nil")(nil))
assert(t.type("boolean")(true))
assert(t.type("string")("foo"))
assert(t.type("number")(123))
assert(t.type("table")({}))
assert(t.type("userdata")(newproxy()))
assert(t.type("function")(function() end))
assert(t.type("thread")(coroutine.create(function() end)))
assert(not t.type("nil")(true))
assert(not t.type("boolean")("true"))
assert(not t.type("string")(123))
assert(not t.type("number")("123"))
assert(not t.type("table")("{}"))
assert(not t.type("userdata")(nil))
assert(not t.type("function")("function"))
assert(not t.type("thread")("thread"))
end)
it("should support basic types", function()
assert(t.any(""))
assert(t.boolean(true))
assert(t.none(nil))
assert(t.number(1))
assert(t.string("foo"))
assert(t.table({}))
assert(not (t.any(nil)))
assert(not (t.boolean("true")))
assert(not (t.none(1)))
assert(not (t.number(true)))
assert(not (t.string(true)))
assert(not (t.table(82)))
end)
it("should support special number types", function()
local maxTen = t.numberMax(10)
local minTwo = t.numberMin(2)
local maxTenEx = t.numberMaxExclusive(10)
local minTwoEx = t.numberMinExclusive(2)
local constrainedEightToEleven = t.numberConstrained(8, 11)
local constrainedEightToElevenEx = t.numberConstrainedExclusive(8, 11)
assert(maxTen(5))
assert(maxTen(10))
assert(not (maxTen(11)))
assert(not (maxTen()))
assert(minTwo(5))
assert(minTwo(2))
assert(not (minTwo(1)))
assert(not (minTwo()))
assert(maxTenEx(5))
assert(maxTenEx(9))
assert(not (maxTenEx(10)))
assert(not (maxTenEx()))
assert(minTwoEx(5))
assert(minTwoEx(3))
assert(not (minTwoEx(2)))
assert(not (minTwoEx()))
assert(not (constrainedEightToEleven(7)))
assert(constrainedEightToEleven(8))
assert(constrainedEightToEleven(9))
assert(constrainedEightToEleven(11))
assert(not (constrainedEightToEleven(12)))
assert(not (constrainedEightToEleven()))
assert(not (constrainedEightToElevenEx(7)))
assert(not (constrainedEightToElevenEx(8)))
assert(constrainedEightToElevenEx(9))
assert(not (constrainedEightToElevenEx(11)))
assert(not (constrainedEightToElevenEx(12)))
assert(not (constrainedEightToElevenEx()))
end)
it("should support optional types", function()
local check = t.optional(t.string)
assert(check(""))
assert(check())
assert(not (check(1)))
end)
it("should support tuple types", function()
local myTupleCheck = t.tuple(t.number, t.string, t.optional(t.number))
assert(myTupleCheck(1, "2", 3))
assert(myTupleCheck(1, "2"))
assert(not (myTupleCheck(1, "2", "3")))
end)
it("should support union types", function()
local numberOrString = t.union(t.number, t.string)
assert(numberOrString(1))
assert(numberOrString("1"))
assert(not (numberOrString(nil)))
local numberOrStringList = t.unionList({ t.number, t.string })
assert(numberOrStringList(1))
assert(numberOrStringList("1"))
assert(not (numberOrStringList(nil)))
end)
it("should support literal types", function()
local checkSingle = t.literal("foo")
local checkUnion = t.union(t.literal("foo"), t.literal("bar"), t.literal("oof"))
assert(checkSingle("foo"))
assert(checkUnion("foo"))
assert(checkUnion("bar"))
assert(checkUnion("oof"))
assert(not (checkSingle("FOO")))
assert(not (checkUnion("FOO")))
assert(not (checkUnion("BAR")))
assert(not (checkUnion("OOF")))
end)
it("should support multiple literal types", function()
local checkSingle = t.literal("foo")
local checkUnion = t.literal("foo", "bar", "oof")
local checkList = t.literalList({ "foo", "bar", "oof" })
assert(checkSingle("foo"))
assert(checkUnion("foo"))
assert(checkUnion("bar"))
assert(checkUnion("oof"))
assert(checkList("foo"))
assert(checkList("bar"))
assert(checkList("oof"))
assert(not (checkSingle("FOO")))
assert(not (checkUnion("FOO")))
assert(not (checkUnion("BAR")))
assert(not (checkUnion("OOF")))
assert(not (checkList("FOO")))
assert(not (checkList("BAR")))
assert(not (checkList("OOF")))
end)
it("should support intersection types", function()
local integerMax5000 = t.intersection(t.integer, t.numberMax(5000))
local integerMax5000List = t.intersectionList({ t.integer, t.numberMax(5000) })
assert(integerMax5000(1))
assert(integerMax5000List(1))
assert(not (integerMax5000(5001)))
assert(not (integerMax5000(1.1)))
assert(not (integerMax5000("1")))
assert(not (integerMax5000List(5001)))
assert(not (integerMax5000List(1.1)))
assert(not (integerMax5000List("1")))
end)
describe("array", function()
it("should support array types", function()
local stringArray = t.array(t.string)
local anyArray = t.array(t.any)
local stringValues = t.values(t.string)
assert(not (anyArray("foo")))
assert(anyArray({1, "2", 3}))
assert(not (stringArray({1, "2", 3})))
assert(not (stringArray()))
assert(not (stringValues()))
assert(anyArray({"1", "2", "3"}, t.string))
assert(not (anyArray({
foo = "bar"
})))
assert(not (anyArray({
[1] = "non",
[5] = "sequential"
})))
end)
it("should not be fooled by sparse arrays", function()
local anyArray = t.array(t.any)
assert(not (anyArray({
[1] = 1,
[2] = 2,
[4] = 4,
})))
end)
end)
it("should support map types", function()
local stringNumberMap = t.map(t.string, t.number)
assert(stringNumberMap({}))
assert(stringNumberMap({a = 1}))
assert(not (stringNumberMap({[1] = "a"})))
assert(not (stringNumberMap({a = "a"})))
assert(not (stringNumberMap()))
end)
it("should support set types", function()
local stringSet = t.set(t.string)
assert(stringSet({}))
assert(stringSet({a = true}))
assert(not (stringSet({[1] = "a"})))
assert(not (stringSet({a = "a"})))
assert(not (stringSet({a = false})))
assert(not (stringSet()))
end)
it("should support interface types", function()
local IVector3 = t.interface({
x = t.number,
y = t.number,
z = t.number,
})
assert(IVector3({
w = 0,
x = 1,
y = 2,
z = 3,
}))
assert(not (IVector3({
w = 0,
x = 1,
y = 2,
})))
end)
it("should support strict interface types", function()
local IVector3 = t.strictInterface({
x = t.number,
y = t.number,
z = t.number,
})
assert(not (IVector3(0)))
assert(not (IVector3({
w = 0,
x = 1,
y = 2,
z = 3,
})))
assert(not (IVector3({
w = 0,
x = 1,
y = 2,
})))
assert(IVector3({
x = 1,
y = 2,
z = 3,
}))
end)
it("should support deep interface types", function()
local IPlayer = t.interface({
name = t.string,
inventory = t.interface({
size = t.number
})
})
assert(IPlayer({
name = "TestPlayer",
inventory = {
size = 1
}
}))
assert(not (IPlayer({
inventory = {
size = 1
}
})))
assert(not (IPlayer({
name = "TestPlayer",
inventory = {
}
})))
assert(not (IPlayer({
name = "TestPlayer",
})))
end)
it("should support deep optional interface types", function()
local IPlayer = t.interface({
name = t.string,
inventory = t.optional(t.interface({
size = t.number
}))
})
assert(IPlayer({
name = "TestPlayer"
}))
assert(not (IPlayer({
name = "TestPlayer",
inventory = {
}
})))
assert(IPlayer({
name = "TestPlayer",
inventory = {
size = 1
}
}))
end)
it("should support Roblox Instance types", function()
local stringValueCheck = t.instanceOf("StringValue")
local stringValue = Instance.new("StringValue")
local boolValue = Instance.new("BoolValue")
assert(stringValueCheck(stringValue))
assert(not (stringValueCheck(boolValue)))
assert(not (stringValueCheck()))
end)
it("should support Roblox Instance types inheritance", function()
local guiObjectCheck = t.instanceIsA("GuiObject")
local frame = Instance.new("Frame")
local textLabel = Instance.new("TextLabel")
local stringValue = Instance.new("StringValue")
assert(guiObjectCheck(frame))
assert(guiObjectCheck(textLabel))
assert(not (guiObjectCheck(stringValue)))
assert(not (guiObjectCheck()))
end)
it("should support Roblox Enum types", function()
local sortOrderEnumCheck = t.enum(Enum.SortOrder)
assert(t.Enum(Enum.SortOrder))
assert(not (t.Enum("Enum.SortOrder")))
assert(t.EnumItem(Enum.SortOrder.Name))
assert(not (t.EnumItem("Enum.SortOrder.Name")))
assert(sortOrderEnumCheck(Enum.SortOrder.Name))
assert(sortOrderEnumCheck(Enum.SortOrder.Custom))
assert(not (sortOrderEnumCheck(Enum.EasingStyle.Linear)))
assert(not (sortOrderEnumCheck()))
end)
it("should support Roblox RBXScriptSignal", function()
assert(t.RBXScriptSignal(game.ChildAdded))
assert(not (t.RBXScriptSignal(nil)))
assert(not (t.RBXScriptSignal(Vector3.new())))
end)
it("should support wrapping function types", function()
local checkFoo = t.tuple(t.string, t.number, t.optional(t.string))
local foo = t.wrap(function(a, b, c)
local result = string.format("%s %d", a, b)
if c then
result = result .. " " .. c
end
return result
end, checkFoo)
assert(not (pcall(foo)))
assert(not (pcall(foo, "a")))
assert(not (pcall(foo, 2)))
assert(pcall(foo, "a", 1))
assert(pcall(foo, "a", 1, "b"))
end)
it("should support strict types", function()
local myType = t.strict(t.tuple(t.string, t.number))
assert(not (pcall(function()
myType("a", "b")
end)))
assert(pcall(function()
myType("a", 1)
end))
end)
it("should support common OOP types", function()
local MyClass = {}
MyClass.__index = MyClass
function MyClass.new()
local self = setmetatable({}, MyClass)
return self
end
local function instanceOfClass(class)
return function(value)
local tableSuccess, tableErrMsg = t.table(value)
if not tableSuccess then
return false, tableErrMsg or ""
end
local mt = getmetatable(value)
if not mt or mt.__index ~= class then
return false, "bad member of class"
end
return true
end
end
local instanceOfMyClass = instanceOfClass(MyClass)
local myObject = MyClass.new()
assert(instanceOfMyClass(myObject))
assert(not (instanceOfMyClass({})))
assert(not (instanceOfMyClass()))
end)
it("should not treat NaN as numbers", function()
assert(t.number(1))
assert(not (t.number(0/0)))
assert(not (t.number("1")))
end)
it("should not treat numbers as NaN", function()
assert(not (t.nan(1)))
assert(t.nan(0/0))
assert(not (t.nan("1")))
end)
it("should allow union of number and NaN", function()
local numberOrNaN = t.union(t.number, t.nan)
assert(numberOrNaN(1))
assert(numberOrNaN(0/0))
assert(not (numberOrNaN("1")))
end)
it("should support non-string keys for interfaces", function()
local key = {}
local myInterface = t.interface({ [key] = t.number })
assert(myInterface({ [key] = 1 }))
assert(not (myInterface({ [key] = "1" })))
end)
it("should support failing on non-string keys for strict interfaces", function()
local myInterface = t.strictInterface({ a = t.number })
assert(not (myInterface({ a = 1, [{}] = 2 })))
end)
it("should support children", function()
local myInterface = t.interface({
buttonInFrame = t.intersection(t.instanceOf("Frame"), t.children({
MyButton = t.instanceOf("ImageButton")
}))
})
assert(not (t.children({})(5)))
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "MyButton"
assert(myInterface({ buttonInFrame = frame }))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "NotMyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("TextButton", frame)
button.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button1 = Instance.new("ImageButton", frame)
button1.Name = "MyButton"
local button2 = Instance.new("ImageButton", frame)
button2.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
end)
it("should support t.instanceOf shorthand", function()
local myInterface = t.interface({
buttonInFrame = t.instanceOf("Frame", {
MyButton = t.instanceOf("ImageButton")
})
})
assert(not (t.children({})(5)))
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "MyButton"
assert(myInterface({ buttonInFrame = frame }))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "NotMyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("TextButton", frame)
button.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button1 = Instance.new("ImageButton", frame)
button1.Name = "MyButton"
local button2 = Instance.new("ImageButton", frame)
button2.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
end)
it("should support t.instanceIsA shorthand", function()
local myInterface = t.interface({
buttonInFrame = t.instanceIsA("Frame", {
MyButton = t.instanceIsA("ImageButton")
})
})
assert(not (t.children({})(5)))
assert(not (myInterface({ buttonInFrame = Instance.new("Frame") })))
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "MyButton"
assert(myInterface({ buttonInFrame = frame }))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "NotMyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button = Instance.new("TextButton", frame)
button.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
do
local frame = Instance.new("Frame")
local button1 = Instance.new("ImageButton", frame)
button1.Name = "MyButton"
local button2 = Instance.new("ImageButton", frame)
button2.Name = "MyButton"
assert(not (myInterface({ buttonInFrame = frame })))
end
end)
it("should support t.match", function()
local check = t.match("%d+")
assert(check("123"))
assert(not (check("abc")))
assert(not (check()))
end)
it("should support t.keyOf", function()
local myNewEnum = {
OptionA = {},
OptionB = {},
}
local check = t.keyOf(myNewEnum)
assert(check("OptionA"))
assert(not (check("OptionC")))
end)
it("should support t.valueOf", function()
local myNewEnum = {
OptionA = {},
OptionB = {},
}
local check = t.valueOf(myNewEnum)
assert(check(myNewEnum.OptionA))
assert(not (check(1010)))
end)
it("should support t.strictArray", function()
local fixedArrayCheck = t.strictArray(t.number, t.number)
assert(fixedArrayCheck({1, 2}))
assert(not fixedArrayCheck({1, 2, 3}))
assert(not fixedArrayCheck({10}))
assert(not fixedArrayCheck({"Hello", 10}))
assert(not fixedArrayCheck({ Foo = "Bar" }))
local fixedArrayCheck2 = t.strictArray(t.number, t.number, t.optional(t.string))
assert(fixedArrayCheck2({10, 20}))
assert(fixedArrayCheck2({10, 20, "Hello"}))
assert(not fixedArrayCheck2({10, 20, 30}))
end)
end