Skip to content

Commit

Permalink
Merge pull request #25 from osyrisrblx/children
Browse files Browse the repository at this point in the history
add t.children
  • Loading branch information
osyrisrblx committed Jul 31, 2019
2 parents 2f51799 + 3d0e851 commit bb7a42f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 5 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,19 @@ print(IPlayer(myPlayer2)) --> false, "[interface] unexpected field 'A'"
## Roblox Instances
t includes two functions to check the types of Roblox Instances.

**`t.instance(className)`**\
**`t.instanceOf(className)`**\
ensures the value is an Instance and it's ClassName exactly matches `className`

**`t.instanceIsA(className)`**\
ensures the value is an Instance and it's ClassName matches `className` by a IsA comparison. ([see here](http://wiki.roblox.com/index.php?title=API:Class/Instance/FindFirstAncestorWhichIsA))

**`t.children(checkTable)`**\
Takes a table where keys are child names and values are functions to check the children against.\
Pass an instance tree into the function.\
If at least one child passes each check, the overall check passes.

**Warning! If you pass in a tree with more than one child of the same name, this function will always return false**

## Roblox Enums

t allows type checking for Roblox Enums!
Expand Down
49 changes: 48 additions & 1 deletion lib/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ end
@returns A function that will return true iff the condition is passed
**--]]
function t.instance(className)
function t.instanceOf(className)
assert(t.string(className))
return function(value)
local instanceSuccess, instanceErrMsg = t.Instance(value)
Expand All @@ -871,6 +871,7 @@ function t.instance(className)
return true
end
end
t.instance = t.instanceOf

--[[**
ensure value is an Instance and it's ClassName matches the given ClassName by an IsA comparison
Expand Down Expand Up @@ -951,4 +952,50 @@ function t.strict(check)
end
end

do
local checkChildren = t.map(t.string, t.callback)

--[[**
Takes a table where keys are child names and values are functions to check the children against.
Pass an instance tree into the function.
If at least one child passes each check, the overall check passes.
Warning! If you pass in a tree with more than one child of the same name, this function will always return false
@param checkTable The table to check against
@returns A function that checks an instance tree
**--]]
function t.children(checkTable)
assert(checkChildren(checkTable))

return function(value)
local instanceSuccess, instanceErrMsg = t.Instance(value)
if not instanceSuccess then
return false, instanceErrMsg or ""
end

local childrenByName = {}
for _, child in pairs(value:GetChildren()) do
local name = child.Name
if checkTable[name] then
if childrenByName[name] then
return false, string.format("Cannot process multiple children with the same name \"%s\"", name)
end
childrenByName[name] = child
end
end

for name, check in pairs(checkTable) do
local success, errMsg = check(childrenByName[name])
if not success then
return false, string.format("[%s.%s] %s", value:GetFullName(), name, errMsg or "")
end
end

return true
end
end
end

return t
43 changes: 42 additions & 1 deletion lib/init.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ return function()
end)

it("should support Roblox Instance types", function()
local stringValueCheck = t.instance("StringValue")
local stringValueCheck = t.instanceOf("StringValue")
local stringValue = Instance.new("StringValue")
local boolValue = Instance.new("BoolValue")

Expand Down Expand Up @@ -378,4 +378,45 @@ return function()
local myInterface = t.strictInterface({ a = t.number })
expect(myInterface({ a = 1, [{}] = 2 })).to.equal(false)
end)

it("should support children", function()
local myInterface = t.interface({
buttonInFrame = t.intersection(t.instanceOf("Frame"), t.children({
MyButton = t.instanceOf("ImageButton")
}))
})

expect(t.children({})(5)).to.equal(false)
expect(myInterface({ buttonInFrame = Instance.new("Frame") })).to.equal(false)

do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "MyButton"
expect(myInterface({ buttonInFrame = frame })).to.equal(true)
end

do
local frame = Instance.new("Frame")
local button = Instance.new("ImageButton", frame)
button.Name = "NotMyButton"
expect(myInterface({ buttonInFrame = frame })).to.equal(false)
end

do
local frame = Instance.new("Frame")
local button = Instance.new("TextButton", frame)
button.Name = "MyButton"
expect(myInterface({ buttonInFrame = frame })).to.equal(false)
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"
expect(myInterface({ buttonInFrame = frame })).to.equal(false)
end
end)
end
5 changes: 4 additions & 1 deletion lib/t.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,11 @@ interface t {
}

interface t {
instance: <T extends string>(className: T) => T extends keyof Instances ? check<Instances[T]> : boolean;
instanceOf: <T extends string>(className: T) => T extends keyof Instances ? check<Instances[T]> : boolean;
instanceIsA: <T extends string>(className: T) => T extends keyof Instances ? check<Instances[T]> : boolean;
children: <T extends { [index: string]: (value: unknown) => value is any }>(
checkTable: T
) => check<Instance & { [P in keyof T]: t.static<T[P]> }>;
}

declare namespace t {
Expand Down
37 changes: 36 additions & 1 deletion lib/ts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ do
end
end

function t.instance(className)
function t.instanceOf(className)
assert(t.string(className))
return function(value)
local instanceSuccess = t.Instance(value)
Expand All @@ -420,6 +420,7 @@ function t.instance(className)
return true
end
end
t.instance = t.instanceOf

function t.instanceIsA(className)
assert(t.string(className))
Expand Down Expand Up @@ -471,4 +472,38 @@ function t.strict(check)
end
end

do
local checkChildren = t.map(t.string, t.callback)
function t.children(checkTable)
assert(checkChildren(checkTable))

return function(value)
local instanceSuccess = t.Instance(value)
if not instanceSuccess then
return false
end

local childrenByName = {}
for _, child in pairs(value:GetChildren()) do
local name = child.Name
if checkTable[name] then
if childrenByName[name] then
return false
end
childrenByName[name] = child
end
end

for name, check in pairs(checkTable) do
local success = check(childrenByName[name])
if not success then
return false
end
end

return true
end
end
end

return t

0 comments on commit bb7a42f

Please sign in to comment.