This commit is contained in:
Aaron Smith 2023-08-10 13:18:16 +01:00
parent 2050c2585f
commit cc10c80a90
10 changed files with 174 additions and 220 deletions

View file

@ -15,10 +15,10 @@ vide.strict = true
What strict mode will do:
1. Run derived callbacks twice when re-evaluating.
2. Run watcher callbacks twice when a state changes.
1. Run derived sources twice a source updates.
2. Run watchers twice when a source updates.
3. Throw an error if yields occur where they are not allowed.
4. Checks for `map()` returning primitive values.
4. Checks for `indexes()` and `values()` returning primitive values.
5. Better error reporting and stack traces.
It is recommend to develop UI with strict mode and to disable it when pushing to

View file

@ -43,7 +43,7 @@ function setup(instance: Instance, setter: (Instance) -> ())
local trace = traceback()
setter = function(instance)
local ok, err: string? = pcall(fn, instance)
if not ok then warn(`error occured updating state binding:\n{err}\nset from:{trace}`) end
if not ok then warn(`error occured updating property:\n{err}\nset from:{trace}`) end
end
end
@ -68,8 +68,6 @@ function setup(instance: Instance, setter: (Instance) -> ())
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
end
-- todo: move `fn` as arg?
local function bind_property(instance: Instance, property: string, fn: () -> unknown)
setup(instance, function(instance_weak: any)
instance_weak[property] = fn()
@ -80,13 +78,13 @@ local function bind_parent(instance: Instance, fn: () -> Instance?)
instance.Destroying:Connect(function()
instance= nil :: any -- allow gc when destroyed
end)
setup(instance, function(instance)
local _ = instance -- state will strongly reference instance when parent is bound
instance.Parent = fn()
end)
end
-- todo: could optimize, see: maps.luau values()
local function bind_children(parent: Instance, fn: () -> { Instance })
local current_child_set: { [Instance]: true } = {} -- cache of all children parented before update
local new_child_set: { [Instance]: true } = {} -- cache of all children parented after update

View file

@ -12,7 +12,7 @@ local defaults = require(script.Parent.defaults)
local apply = require(script.Parent.apply)
local memoize = require(script.Parent.memoize)
local function createInstance(className: string)
local function create_instance(className: string)
local success, instance: Instance = pcall(Instance.new, className :: any)
if success == false then throw(`invalid class name, could not create instance of class { className }`) end
@ -26,9 +26,9 @@ local function createInstance(className: string)
return function(properties: { [any]: unknown }): Instance
return apply(instance:Clone(), properties)
end
end; createInstance = memoize(createInstance)
end; create_instance = memoize(create_instance)
local function cloneInstance(instance: Instance)
local function clone_instance(instance: Instance)
return function(properties: { [any]: unknown }): Instance
local clone = instance:Clone()
if not clone then error("Attempt to clone a non-archivable instance", 3) end
@ -38,9 +38,9 @@ end
local function create(classNameOrInstance: string|Instance)
if type(classNameOrInstance) == "string" then
return createInstance(classNameOrInstance)
return create_instance(classNameOrInstance)
elseif typeof(classNameOrInstance) == "Instance" then
return cloneInstance(classNameOrInstance)
return clone_instance(classNameOrInstance)
else
error("Bad argument #1, expected string or instance, got "..typeof(classNameOrInstance), 2)
end

View file

@ -1,5 +1,6 @@
if not game then script = require "test/wrap-require" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
export type Node<T> = {
@ -13,28 +14,30 @@ local reff = false
local refs = {} :: { Node<unknown> }
local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
local EVALUATION_ERR = "error while evaluating node:\n\n"
local EVALUATION_ERR = "error while evaluating source:\n\n"
setmetatable(refs :: any, WEAK_VALUES_RESIZABLE)
local check_for_yield do
local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
local t = { __mode = "kv" }
setmetatable(t, t)
check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: any)
check_for_yield = function(fn, ...: any)
local args = { ... }
t.__unm = function()
t.__unm = function(_)
fn(unpack(args))
end
local ok, err = pcall(function()
return -t :: any
local _ = -t
end)
if not ok then
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then
error(EVALUATION_ERR .. "cannot yield when deriving node in watcher", 3)
throw(EVALUATION_ERR .. "cannot yield when deriving node in watcher")
else
error(EVALUATION_ERR..err, 3)
throw(EVALUATION_ERR .. err)
end
end
end
@ -45,10 +48,16 @@ local function set_effect<T>(node: Node<unknown>, fn: (T) -> (), key: T)
end
local function run_effects(node: Node<unknown>)
for effect, key in next, node.effects do
if flags.strict then effect(key) end
effect(key)
end
if flags.strict then
for effect, key in next, node.effects do
effect(key)
effect(key)
end
else
for effect, key in next, node.effects do
effect(key)
end
end
end
-- retrieves a node's cached value
@ -71,8 +80,10 @@ end
local function update(node: Node<unknown>)
run_effects(node)
if node.children then
local strict = flags.strict
for _, child in node.children do
if flags.strict then check_for_yield(child.derive) end
if strict then check_for_yield(child.derive) end
child.cache = child.derive()
update(child)
end
@ -108,7 +119,7 @@ local function capture<T, U>(fn: (U?) -> T, arg: U?): ({ Node<unknown> }, T)
reff = false
if not ok then error("error while detecting watcher: " .. result :: string, 0) end
if not ok then throw(EVALUATION_ERR .. result :: string) end
return refs, result :: T
end

View file

@ -14,6 +14,7 @@ local indexes, values = require(script.maps)()
local spring, update_springs = require(script.spring)()
local action = require(script.action)()
local throw = require(script.throw)
local flags = require(script.flags)
local vide = {
@ -49,15 +50,20 @@ local vide = {
}
setmetatable(vide :: any, {
__index = function(_, index: unknown)
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
__index = function(_, index: unknown): ()
if index == "strict" then
return flags.strict
else
throw(`{tostring(index)} is not a valid member of vide`)
end
end,
__newindex = function(_, index: unknown, value: unknown)
if index == "strict" then
flags.strict = if type(value) == "boolean" then value else error("strict must be a boolean", 2)
if value ~= true then throw "strict mode can only be set to true" end
flags.strict = true
else
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
throw(`{tostring(index)} is not a valid member of vide`)
end
end
})

View file

@ -1,5 +1,7 @@
if not game then script = require "test/wrap-require" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local create = graph.create
@ -9,6 +11,15 @@ local link = graph.link
type Map<K, V> = { [K]: V }
local function check_primitives(t: {})
if not flags.strict then return end
for _, v in next, t do
if type(v) == "table" or type(v) == "userdata" then continue end
throw("table source map cannot return primitives")
end
end
-- todo: optimize output array
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local input_cache = {} :: Map<K, VI>
@ -54,7 +65,8 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
for _, v in next, output_cache do
table.insert(output_array, v)
end
check_primitives(output_array)
return output_array
end
@ -86,6 +98,16 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local function recompute(data: Map<K, VI>)
local cur_input_cache, new_input_cache = cur_input_cache_up, new_input_cache_up
if flags.strict then
local cache = {}
for _, v in next, data do
if cache[v] ~= nil then
throw "duplicate table value detected"
end
cache[v] = true
end
end
-- process data
for i, v in next, data do
@ -136,6 +158,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
for _, node in next, nodes do
link(node, output, derive)
end
check_primitives(output_array)
output.cache = recompute(value)

View file

@ -157,7 +157,7 @@ local function update_springs(dt: number)
typeof(initial_position),
target_type
))
throw(`Cannot tween type { typeof(initial_position) } and { target_type }`)
throw(`cannot tween type { typeof(initial_position) } and { target_type }`)
continue
end
@ -165,14 +165,13 @@ local function update_springs(dt: number)
if lerp == nil then
springs[data] = nil
throw(`Cannot animate type { target_type }`)
throw(`cannot animate type { target_type }`)
continue
end
local new_time = data.duration + dt
local new_alpha = solve(data.period, data.damping_ratio, data.initial_velocity, new_time)
local new_velocity = -(new_alpha - data.alpha)/dt
local acceleration = (new_velocity - data.velocity)/dt
data.velocity = new_velocity
@ -181,7 +180,8 @@ local function update_springs(dt: number)
local value = lerp(initial_position, target_position, new_alpha)
if math.abs(acceleration) < 0.01 then
if math.abs(acceleration) < (0.01 / data.period) then
-- close enough to target, remove and set value to target
table.insert(remove_queue, data)
set(output, target_position)
else

View file

@ -1,14 +1,10 @@
------------------------------------------------------------------------------------------
-- benchmark.lua
------------------------------------------------------------------------------------------
local BENCH, START = require("test/testkit").benchmark()
local vide = require "src/init"
local N = 2^18 -- 262144
BENCH("Create state", function()
BENCH("create state", function()
local cache = table.create(N)
local source = vide.source
@ -17,7 +13,7 @@ BENCH("Create state", function()
end
end)
BENCH("Get value", function()
BENCH("get value", function()
local state = vide.source(1)
for i = 1, START(N) do
@ -25,7 +21,7 @@ BENCH("Get value", function()
end
end)
BENCH("Set value", function()
BENCH("set value", function()
local state = vide.source(1)
for i = 1, START(N) do
@ -33,7 +29,7 @@ BENCH("Set value", function()
end
end)
BENCH("Derive 1 state", function()
BENCH("derive 1 state", function()
local cache = table.create(N)
local state = vide.source(1)
local derive = vide.derive
@ -45,7 +41,7 @@ BENCH("Derive 1 state", function()
end
end)
BENCH("Derive 4 states", function()
BENCH("derive 4 states", function()
local cache = table.create(N)
local state = vide.source(1)
local state2 = vide.source(2)
@ -60,7 +56,7 @@ BENCH("Derive 4 states", function()
end
end)
BENCH("Set derived value", function()
BENCH("set derived value", function()
local state = vide.source(1)
local _derived = vide.derive(state)
@ -69,7 +65,7 @@ BENCH("Set derived value", function()
end
end)
BENCH("Apply 0 properties", function()
BENCH("apply 0 properties", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
@ -78,7 +74,7 @@ BENCH("Apply 0 properties", function()
end
end)
BENCH("Apply 8 properties", function()
BENCH("apply 8 properties", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
@ -96,7 +92,7 @@ BENCH("Apply 8 properties", function()
end
end)
BENCH("Bind state", function()
BENCH("bind source", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
local state = vide.source(1)
@ -108,7 +104,7 @@ BENCH("Bind state", function()
end
end)
BENCH("Update binding", function()
BENCH("update binding", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
local state = vide.source(1)

View file

@ -1,13 +1,11 @@
local testkit = require("test/testkit")
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
local TEST, CASE, CHECK, FINISH = testkit.test()
local mock = require "test/mock"
local Instance, Signal = mock.Instance, mock.Signal
local vide = require "src/init"
-- force run garbage collector cycles
local function gc(n: number?)
for i = 1, n or 3 do
(collectgarbage :: any)("collect")
@ -29,12 +27,12 @@ TEST("graph", function()
local link = graph.link
local set_effect = graph.set_effect
do CASE "Node creation"
do CASE "node creation"
local node = create(1)
CHECK(get(node) == 1)
end
do CASE "Node value"
do CASE "node value"
local node = create(0)
set(node, 1)
CHECK(get(node) == 1)
@ -42,7 +40,7 @@ TEST("graph", function()
CHECK(get(node) == 2)
end
do CASE "Capture nodes"
do CASE "capture nodes"
local node1 = create(nil)
local node2 = create(nil)
local nodes = capture(function()
@ -52,7 +50,7 @@ TEST("graph", function()
CHECK(nodes[2] == node2)
end
do CASE "Linking nodes"
do CASE "linking nodes"
local parent = create(1)
local child = create(0)
@ -64,7 +62,7 @@ TEST("graph", function()
CHECK(get(child) == 2) -- child should automatically update
end
do CASE "Capture and link nodes"
do CASE "capture and link nodes"
local parent = create(1)
local child = create()
@ -76,38 +74,13 @@ TEST("graph", function()
CHECK(get(child) == "2")
end
--[[
do CASE "Scoped captures"
local a = create(0)
local b = create(nil :: any)
local count = 0
b.cache = capture_and_link(b, function()
count += 1
local data = get(a)
local c = create(data)
get(c)
return c
end)
local c = get(b)
CHECK(count == 1)
set(c, 1)
CHECK(count == 1)
set(a, 1)
CHECK(count == 2)
end
]]
do CASE "Nodes garbage collection"
do CASE "nodes garbage collection"
local wref = weak { create(1) }
gc()
CHECK(not wref[1])
end
do CASE "Node effect garbage collection"
do CASE "node effect garbage collection"
do
local wref
@ -179,18 +152,18 @@ TEST("source()", function()
local source = vide.source
local watch = vide.watch
do CASE "Create source"
do CASE "create source"
local state = source(1)
CHECK(state() == 1)
end
do CASE "Set and get source value"
do CASE "set and get source value"
local state = source(1)
state(2)
CHECK(state() == 2)
end
do CASE "Does not update if same value"
do CASE "does not update if same value"
local state = source(1)
local updates = -1
@ -206,7 +179,7 @@ TEST("source()", function()
CHECK(updates == 1)
end
do CASE "Does update if same value is table"
do CASE "does update if same value is table"
local state = source {}
local updates = -1
@ -225,7 +198,7 @@ TEST("derive()", function()
local source = vide.source
local derive = vide.derive
do CASE "Derive new value on source change"
do CASE "derive new value on source change"
local inputA = source(1)
local inputB = source(2)
@ -238,7 +211,7 @@ TEST("derive()", function()
CHECK(output() == "4")
end
do CASE "Derive transformed source"
do CASE "derive wrapped source"
local input = source(1)
local transform = function()
@ -254,25 +227,7 @@ TEST("derive()", function()
CHECK(output() == 2)
end
--[[
do CASE "Cleanup"
local count, set = source(1)
local derived = derive(function(from)
return { Value = from(count), Destroyed = false }
end, function(v)
v.Destroyed = true
end)
local first = derived()
CHECK(first.Destroyed == false)
set(2)
local _ = derived() -- trigger recalc
CHECK(first.Destroyed == true)
end
]]
do CASE "Garbage collection"
do CASE "garbage collection"
do -- check that `b` does not allow gc of `a`
local wref, b
@ -309,7 +264,7 @@ TEST("derive()", function()
end
end
do CASE "Garbage collection 2"
do CASE "garbage collection 2"
-- creats a chain `a -> b -> c` where `a` is the source
local function setup()
local a = source(0)
@ -362,7 +317,7 @@ TEST("watch()", function()
local watch = vide.watch
local cleanup = vide.cleanup
do CASE "Capture states"
do CASE "capture sourcess"
local a = source(1)
local b = source(1)
@ -380,7 +335,7 @@ TEST("watch()", function()
CHECK(runcount == 2)
end
do CASE "Stop watch"
do CASE "stop watch"
local a = source(1)
local runcount = -1
@ -394,7 +349,7 @@ TEST("watch()", function()
CHECK(runcount == 0)
end
do CASE "Side-effect cleanup"
do CASE "side-effect cleanup"
local state = source(1)
local effect_runcount = 0
@ -421,7 +376,7 @@ TEST("watch()", function()
CHECK(cleanup_runcount == 2)
end
do CASE "Garbage collection"
do CASE "garbage collection"
local function factory(p)
return function()
p()
@ -486,7 +441,7 @@ TEST("cleanup()", function()
local watch = vide.watch
local cleanup = vide.cleanup
do CASE "Cleanup runs for watcher"
do CASE "cleanup runs for watcher"
local state = source(1)
local watched = 0
@ -520,7 +475,7 @@ TEST("cleanup()", function()
CHECK(cleaned == 2)
end
do CASE "Scoped"
do CASE "scoped"
local function setup()
local state = source(1)
local obj = { cleaned = 0 }
@ -562,7 +517,7 @@ TEST("cleanup()", function()
CHECK(objB.cleaned == 2)
end
do CASE "Multiple cleanup"
do CASE "multiple cleanup"
local state = source(1)
local queue = {}
@ -595,14 +550,14 @@ TEST("create()", function()
local create = vide.create
local source = vide.source
do CASE "Apply default properties"
do CASE "apply default properties"
local defaults = require("src/defaults")
local frame = create "Frame" {} :: Instance & { BorderSizePixel: any, BorderColor3: any }
CHECK(frame.BorderSizePixel == defaults.Frame.BorderSizePixel)
CHECK(frame.BorderColor3 == defaults.Frame.BorderColor3)
end
do CASE "Set properties"
do CASE "set properties"
local text = create "TextLabel" {
Name = "Label",
Text = "test"
@ -611,7 +566,7 @@ TEST("create()", function()
CHECK(text.Text == "test")
end
do CASE "Set nested properties"
do CASE "set nested properties"
local text = create "TextLabel" {
{ Name = "Label" },
Group = { Text = "test" }
@ -620,12 +575,12 @@ TEST("create()", function()
CHECK(text.Text == "test")
end
do CASE "Independent"
do CASE "independent"
local frame = create "Frame"
CHECK(frame {} ~= frame {})
end
do CASE "Set children"
do CASE "set children"
local frame = create "Frame" {
create "TextLabel" { Name = "A" },
create "TextLabel" { Name = "B" },
@ -652,7 +607,7 @@ TEST("create()", function()
CHECK(frame:FindFirstChild "G")
end
do CASE "Binding properties to state"
do CASE "binding properties to source"
local name = source("Hi")
local text = source("Bye")
@ -671,7 +626,7 @@ TEST("create()", function()
CHECK(label.Text == "Bar")
end
do CASE "Binding garbage collection"
do CASE "binding garbage collection"
do -- instance should gc when unparented
local state = source("Hi")
@ -774,7 +729,7 @@ TEST("create()", function()
end
end
do CASE "Bind same state to multiple instance properties"
do CASE "bind same state to multiple instance properties"
local state = source "1"
local text = create "TextBox" {
@ -790,7 +745,7 @@ TEST("create()", function()
CHECK(text.PlaceholderText == "2")
end
do CASE "Bind children"
do CASE "bind children"
local state = source()
local a, b, c =
@ -844,7 +799,7 @@ TEST("create()", function()
end
]]
do CASE "GC test"
do CASE "garbage collection test"
local wref
do
@ -871,13 +826,11 @@ TEST("create()", function()
end
end)
-- todo: more comprehensive tests for maps
TEST("indexes()", function()
local source = vide.source
local indexes = vide.indexes
do CASE "Use state"
do CASE "use source"
local input = source { 1, 2, 3 }
local output = indexes(input, function(v, k)
@ -889,7 +842,7 @@ TEST("indexes()", function()
CHECK("" .. input()[3] == output()[3])
end
do CASE "Cache result"
do CASE "cache result"
local input = source { 1, 2, 3 }
local runcount = table.create(3, 0)
@ -910,7 +863,7 @@ TEST("indexes()", function()
CHECK(runcount[3] == 1)
end
do CASE "Removal reflected"
do CASE "removal reflected"
local input = source { 1, 2, 3 }
local output = indexes(input, function(v, i)
@ -926,95 +879,44 @@ TEST("indexes()", function()
CHECK(t[3] == nil)
end
--[[
do CASE "Bind children"
local state, set = source { "A", "B", "C" }
do CASE "garbage collection"
do -- check that `output` does not allow gc of `input`
local input = source {}
local derived = map(state, function(i, v)
return create "TextLabel" {
Name = v,
Text = tostring(i)
}
end)
local frame = create "Frame" {
Name = "21",
[Children] = derived
}
local function find(childname: string): TextLabel
return frame:FindFirstChild(childname) :: TextLabel
end
CHECK(find "A".Text == "1")
CHECK(find "B".Text == "2")
CHECK(find "C".Text == "3")
set { "A", "C", "D" }
CHECK(find "A".Text == "1")
CHECK(not find "B")
CHECK(find "C".Text == "2")
CHECK(find "D".Text == "3")
end
do CASE "Use optional destructor"
local state, set = source { 1, 2, 3 }
local derived = map(state, function(i, v)
return { Value = v, Destroyed = false }
end, function(v)
v.Destroyed = true
end)
local first = derived()
CHECK(first[1].Destroyed == false)
set { 1, 2, 4 }
local _ = derived()
CHECK(first[1].Destroyed == false)
CHECK(first[2].Destroyed == false)
CHECK(first[3].Destroyed == true)
end
do CASE "Garbage collection"
do -- check that `derived` does not allow gc of `state`
local state = source {}
local derived = map(state, function(i, v)
local _derived = indexes(input, function(i, v)
return v
end)
wref.state, state = state, nil :: any
wref.derived = derived
local wref = weak { input }
input = nil :: any
gc()
CHECK(wref.state)
CHECK(wref[1])
end
do -- check that `state` allows gc of `derived`
local state = source {}
do -- check that `input` allows gc of `output`
local input = source {}
local derived = map(state, function(i, v)
local output = indexes(input, function(i, v)
return i, v
end) :: State?
end)
wref.state = state
wref.derived, derived = derived, nil
local wref = weak { output }
output = nil :: any
gc()
CHECK(not wref.derived)
CHECK(not wref[1])
end
end]]
end
end)
TEST("values()", function()
local source = vide.source
local values = vide.values
do CASE "Use state"
do CASE "use source"
local input = source { 1, 2, 3 }
local output = values(input, function(v, k)
@ -1026,7 +928,7 @@ TEST("values()", function()
CHECK("" .. input()[3] == output()[3])
end
do CASE "Cache result"
do CASE "cache result"
local input = source { 1, 2, 3 }
local runcount = table.create(3, 0)
@ -1047,7 +949,7 @@ TEST("values()", function()
CHECK(runcount[3] == 1)
end
do CASE "Removal reflected"
do CASE "removal reflected"
local input = source { 1, 2, 3 }
local output = values(input, function(v, i)
@ -1063,7 +965,7 @@ TEST("values()", function()
CHECK(t[3] == nil)
end
do CASE "Removal reflected 2"
do CASE "removal reflected 2"
local input = source { 1 }
local output = values(input, function(v, i)
@ -1088,7 +990,7 @@ TEST("spring()", function()
local spring = vide.spring
local watch = vide.watch
do CASE "Update state (on next hearbeat resumption cycle)"
do CASE "update source (on next step)"
local value = source(10)
local springed = spring(value, 1, 1)
@ -1099,7 +1001,7 @@ TEST("spring()", function()
CHECK(springed() > 10)
end
do CASE "Garbage collection"
do CASE "garbage collection"
do -- `output` should not allow gc of `input`
local input = source(10)
local _output = spring(input)
@ -1124,7 +1026,7 @@ TEST("spring()", function()
end
do CASE "Garbage collection (binded)"
do CASE "garbage collection (binded)"
local input = source(10)
local output = spring(input, 1, 1)
@ -1139,7 +1041,7 @@ TEST("spring()", function()
CHECK(wref[1]) -- `output` should not gc
end
do CASE "Spring finished"
do CASE "spring finished"
local input = source(0)
local output = spring(input)
@ -1166,11 +1068,11 @@ TEST("spring()", function()
end
end)
TEST("Events", function()
TEST("events", function()
local create = vide.create
local function Thing(props)
local instance = Instance.new("Thing") :: any
local instance = Instance.new("Thing")
instance.Signal = Signal.new()
local clone = create(instance)(props)
@ -1178,7 +1080,7 @@ TEST("Events", function()
return clone
end
do CASE "Connect event"
do CASE "connect event"
local connected = false
local val = Thing {
@ -1188,8 +1090,6 @@ TEST("Events", function()
end
}
-- testkit.print2(getmetatable(val))
CHECK(not connected)
val.Value = 1; Signal.fire(val.Signal, val.Value)
CHECK(connected)
@ -1200,7 +1100,7 @@ TEST("actions", function()
local create = vide.create
local action = vide.action
do CASE "Run action"
do CASE "run action"
local ran = false
create "Frame" {
@ -1212,7 +1112,7 @@ TEST("actions", function()
CHECK(ran)
end
do CASE "Priorities"
do CASE "priorities"
local queue = {}
create "Frame" {
@ -1235,8 +1135,9 @@ TEST("strict", function()
local source = vide.source
local derive = vide.derive
local watch = vide.watch
local indexes, values = vide.indexes, vide.values
do CASE "Error on derived callback yield"
do CASE "error on derived callback yield"
local state = source(1)
local ok = pcall(function()
@ -1249,7 +1150,7 @@ TEST("strict", function()
CHECK(not ok)
end
do CASE "Error on watcher callback yield"
do CASE "error on watcher callback yield"
local state = source(1)
local ok = pcall(function()
@ -1262,7 +1163,7 @@ TEST("strict", function()
CHECK(not ok)
end
do CASE "Run derived callback twice"
do CASE "run derived callback twice"
local state = source(1)
local runcount = 0
@ -1276,7 +1177,7 @@ TEST("strict", function()
CHECK(runcount == 4)
end
do CASE "Run watcher callback twice"
do CASE "run watcher callback twice"
local state = source(1)
local runcount = 0
@ -1290,7 +1191,25 @@ TEST("strict", function()
CHECK(runcount == 4)
end
-- todo: add case for strict mode bindings
do CASE "indexes() error if primitive"
local state = source { 1 }
local ok = pcall(function()
indexes(state, function() return 1 end)
end)
CHECK(not ok)
end
do CASE "values() error if duplicate"
local state = source { 1, 2, 1 }
local ok = pcall(function()
values(state, function() return {} end)
end)
CHECK(not ok)
end
end)
local ok = FINISH()

View file

@ -3,9 +3,10 @@
- cleanup codebase
- setup site, improve docs
- cleanup within `values()` and `indexes()`
- behavior when a table source value is set to the same table
- address behavior of binding property to multiples states
- strict mode
- better error reporting
- better error reporting and stack traces
- warn when `values()` returns primitive
- warn when `values()` returns duplicate object
- implement from solid