return function() local t = require(script.Parent) 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))) 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") 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 intersection types", function() local integerMax5000 = t.intersection(t.integer, t.numberMax(5000)) assert(integerMax5000(1)) assert(not (integerMax5000(5001))) assert(not (integerMax5000(1.1))) assert(not (integerMax5000("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) -- TODO: Add this back when Lemur supports it -- it("should support Roblox RBXScriptConnection", function() -- local conn = game.ChildAdded:Connect(function() end) -- assert(t.RBXScriptConnection(conn)) -- assert(not (t.RBXScriptConnection(nil))) -- assert(not (t.RBXScriptConnection(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