mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Add typedefs and tests for custom instance properties and methods
This commit is contained in:
parent
9fe3b02d71
commit
1aa6aef679
6 changed files with 255 additions and 0 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -8,6 +8,26 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added `implementProperty` and `implementMethod` to the `roblox` built-in library to fill in missing functionality that Lune does not aim to implement itself.
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local part = roblox.Instance.new("Part")
|
||||
|
||||
roblox.implementMethod("BasePart", "TestMethod", function(_, ...)
|
||||
print("Tried to call TestMethod with", ...)
|
||||
end)
|
||||
|
||||
part:TestMethod("Hello", "world!")
|
||||
```
|
||||
|
||||
## `0.7.8` - October 5th, 2023
|
||||
|
||||
### Added
|
||||
|
|
|
@ -164,6 +164,10 @@ create_tests! {
|
|||
roblox_instance_classes_workspace: "roblox/instance/classes/Workspace",
|
||||
roblox_instance_classes_terrain: "roblox/instance/classes/Terrain",
|
||||
|
||||
roblox_instance_custom_async: "roblox/instance/custom/async",
|
||||
roblox_instance_custom_methods: "roblox/instance/custom/methods",
|
||||
roblox_instance_custom_properties: "roblox/instance/custom/properties",
|
||||
|
||||
roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren",
|
||||
roblox_instance_methods_clone: "roblox/instance/methods/Clone",
|
||||
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
||||
|
|
12
tests/roblox/instance/custom/async.luau
Normal file
12
tests/roblox/instance/custom/async.luau
Normal file
|
@ -0,0 +1,12 @@
|
|||
local roblox = require("@lune/roblox")
|
||||
|
||||
local game = roblox.Instance.new("DataModel")
|
||||
local http = game:GetService("HttpService") :: any
|
||||
|
||||
roblox.implementMethod("HttpService", "GetAsync", function()
|
||||
-- TODO: Fill in method body
|
||||
end)
|
||||
|
||||
-- TODO: Fill in rest of test cases here
|
||||
|
||||
http:GetAsync()
|
48
tests/roblox/instance/custom/methods.luau
Normal file
48
tests/roblox/instance/custom/methods.luau
Normal file
|
@ -0,0 +1,48 @@
|
|||
local roblox = require("@lune/roblox")
|
||||
|
||||
local inst = roblox.Instance.new("Instance") :: any
|
||||
local part = roblox.Instance.new("Part") :: any
|
||||
|
||||
-- Basic sanity checks for callbacks
|
||||
|
||||
local success = pcall(function()
|
||||
inst:Wat()
|
||||
end)
|
||||
assert(not success, "Nonexistent methods should error")
|
||||
|
||||
roblox.implementMethod("Instance", "Wat", function() end)
|
||||
|
||||
local success2 = pcall(function()
|
||||
inst:Wat()
|
||||
end)
|
||||
assert(success2, "Nonexistent methods should error, unless implemented")
|
||||
|
||||
-- Instance should be passed to callback
|
||||
|
||||
roblox.implementMethod("Instance", "PassingInstanceTest", function(instance)
|
||||
assert(instance == inst, "Invalid instance was passed to callback")
|
||||
end)
|
||||
roblox.implementMethod("Part", "PassingPartTest", function(instance)
|
||||
assert(instance == part, "Invalid instance was passed to callback")
|
||||
end)
|
||||
inst:PassingInstanceTest()
|
||||
part:PassingPartTest()
|
||||
|
||||
-- Any number of args passed & returned should work
|
||||
|
||||
roblox.implementMethod("Instance", "Echo", function(_, ...)
|
||||
return ...
|
||||
end)
|
||||
|
||||
local one, two, three = inst:Echo("one", "two", "three")
|
||||
assert(one == "one", "implementMethod callback should return proper values")
|
||||
assert(two == "two", "implementMethod callback should return proper values")
|
||||
assert(three == "three", "implementMethod callback should return proper values")
|
||||
|
||||
-- Methods implemented by Lune should take precedence
|
||||
|
||||
roblox.implementMethod("Instance", "FindFirstChild", function()
|
||||
error("unreachable")
|
||||
end)
|
||||
inst:FindFirstChild("Test")
|
||||
part:FindFirstChild("Test")
|
64
tests/roblox/instance/custom/properties.luau
Normal file
64
tests/roblox/instance/custom/properties.luau
Normal file
|
@ -0,0 +1,64 @@
|
|||
local roblox = require("@lune/roblox")
|
||||
|
||||
local inst = roblox.Instance.new("Instance") :: any
|
||||
local part = roblox.Instance.new("Part") :: any
|
||||
|
||||
-- Basic sanity checks for callbacks
|
||||
|
||||
local success = pcall(function()
|
||||
local _ = inst.Wat
|
||||
end)
|
||||
assert(not success, "Nonexistent properties should error")
|
||||
|
||||
roblox.implementProperty("Instance", "Wat", function()
|
||||
return nil
|
||||
end)
|
||||
|
||||
local success2 = pcall(function()
|
||||
local _ = inst.Wat
|
||||
end)
|
||||
assert(success2, "Nonexistent properties should error, unless implemented")
|
||||
|
||||
-- Instance should be passed to callback
|
||||
|
||||
roblox.implementProperty("Instance", "PassingInstanceTest", function(instance)
|
||||
assert(instance == inst, "Invalid instance was passed to callback")
|
||||
return nil
|
||||
end)
|
||||
roblox.implementProperty("Part", "PassingPartTest", function(instance)
|
||||
assert(instance == part, "Invalid instance was passed to callback")
|
||||
return nil
|
||||
end)
|
||||
local _ = inst.PassingInstanceTest
|
||||
local _ = part.PassingPartTest
|
||||
|
||||
-- Any number of args passed & returned should work
|
||||
|
||||
local counters = {}
|
||||
roblox.implementProperty("Instance", "Counter", function(instance)
|
||||
-- FIXME: Instances do not make for unique table keys for some reason ...
|
||||
local value = counters[tostring(instance)] or 0
|
||||
value += 1
|
||||
counters[tostring(instance)] = value
|
||||
return value
|
||||
end, function(instance, value)
|
||||
counters[tostring(instance)] = value
|
||||
end)
|
||||
|
||||
assert(inst.Counter == 1, "implementProperty callback should return proper values")
|
||||
assert(inst.Counter == 2, "implementProperty callback should return proper values")
|
||||
assert(inst.Counter == 3, "implementProperty callback should return proper values")
|
||||
|
||||
inst.Counter = 10
|
||||
|
||||
assert(inst.Counter == 11, "implementProperty callback should set proper values")
|
||||
assert(inst.Counter == 12, "implementProperty callback should return proper values")
|
||||
assert(inst.Counter == 13, "implementProperty callback should return proper values")
|
||||
|
||||
-- Properties implemented by Lune should take precedence
|
||||
|
||||
roblox.implementProperty("Instance", "Parent", function()
|
||||
error("unreachable")
|
||||
end)
|
||||
local _ = inst.Parent
|
||||
local _ = part.Parent
|
|
@ -391,6 +391,113 @@ function roblox.getReflectionDatabase(): Database
|
|||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Roblox
|
||||
|
||||
Implements a property for all instances of the given `className`.
|
||||
|
||||
This takes into account class hierarchies, so implementing a property
|
||||
for the `BasePart` class will also implement it for `Part` and others,
|
||||
unless a more specific implementation is added to the `Part` class directly.
|
||||
|
||||
### Behavior
|
||||
|
||||
The given `getter` callback will be called each time the property is
|
||||
indexed, with the instance as its one and only argument. The `setter`
|
||||
callback, if given, will be called each time the property should be set,
|
||||
with the instance as the first argument and the property value as second.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local part = roblox.Instance.new("Part")
|
||||
|
||||
local propertyValues = {}
|
||||
roblox.implementProperty(
|
||||
"BasePart",
|
||||
"CoolProp",
|
||||
function(instance)
|
||||
if propertyValues[instance] == nil then
|
||||
propertyValues[instance] = 0
|
||||
end
|
||||
propertyValues[instance] += 1
|
||||
return propertyValues[instance]
|
||||
end,
|
||||
function(instance, value)
|
||||
propertyValues[instance] = value
|
||||
end
|
||||
)
|
||||
|
||||
print(part.CoolProp) --> 1
|
||||
print(part.CoolProp) --> 2
|
||||
print(part.CoolProp) --> 3
|
||||
|
||||
part.CoolProp = 10
|
||||
|
||||
print(part.CoolProp) --> 11
|
||||
print(part.CoolProp) --> 12
|
||||
print(part.CoolProp) --> 13
|
||||
```
|
||||
|
||||
@param className The class to implement the property for.
|
||||
@param propertyName The name of the property to implement.
|
||||
@param getter The function which will be called to get the property value when indexed.
|
||||
@param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only.
|
||||
]=]
|
||||
function roblox.implementProperty<T>(
|
||||
className: string,
|
||||
propertyName: string,
|
||||
getter: (instance: Instance) -> T,
|
||||
setter: ((instance: Instance, value: T) -> ())?
|
||||
)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Roblox
|
||||
|
||||
Implements a method for all instances of the given `className`.
|
||||
|
||||
This takes into account class hierarchies, so implementing a method
|
||||
for the `BasePart` class will also implement it for `Part` and others,
|
||||
unless a more specific implementation is added to the `Part` class directly.
|
||||
|
||||
### Behavior
|
||||
|
||||
The given `callback` will be called every time the method is called,
|
||||
and will receive the instance it was called on as its first argument.
|
||||
The remaining arguments will be what the caller passed to the method, and
|
||||
all values returned from the callback will then be returned to the caller.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local part = roblox.Instance.new("Part")
|
||||
|
||||
roblox.implementMethod("BasePart", "TestMethod", function(instance, ...)
|
||||
print("Called TestMethod on instance", instance, "with", ...)
|
||||
end)
|
||||
|
||||
part:TestMethod("Hello", "world!")
|
||||
--> Called TestMethod on instance Part with Hello, world!
|
||||
```
|
||||
|
||||
@param className The class to implement the method for.
|
||||
@param methodName The name of the method to implement.
|
||||
@param callback The function which will be called when the method is called.
|
||||
]=]
|
||||
function roblox.implementMethod(
|
||||
className: string,
|
||||
methodName: string,
|
||||
callback: (instance: Instance, ...any) -> ...any
|
||||
)
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
-- TODO: Make typedefs for all of the datatypes as well...
|
||||
roblox.Instance = (nil :: any) :: {
|
||||
new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance),
|
||||
|
|
Loading…
Reference in a new issue