diff --git a/docs/api/strict-mode.md b/docs/api/strict-mode.md index f5ca78a..d69b527 100644 --- a/docs/api/strict-mode.md +++ b/docs/api/strict-mode.md @@ -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 diff --git a/src/bind.luau b/src/bind.luau index 4a36a34..d136697 100644 --- a/src/bind.luau +++ b/src/bind.luau @@ -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 diff --git a/src/create.luau b/src/create.luau index 06ded51..3c3ed71 100644 --- a/src/create.luau +++ b/src/create.luau @@ -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 diff --git a/src/graph.luau b/src/graph.luau index 60e1158..168d8e3 100644 --- a/src/graph.luau +++ b/src/graph.luau @@ -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 = { @@ -13,28 +14,30 @@ local reff = false local refs = {} :: { Node } 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: (fn: (T...) -> unknown, T...) -> () do local t = { __mode = "kv" } setmetatable(t, t) - check_for_yield = function(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(node: Node, fn: (T) -> (), key: T) end local function run_effects(node: Node) - 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) 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(fn: (U?) -> T, arg: U?): ({ Node }, 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 diff --git a/src/init.luau b/src/init.luau index cabd2f2..8192958 100644 --- a/src/init.luau +++ b/src/init.luau @@ -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 }) diff --git a/src/maps.luau b/src/maps.luau index 553b495..fe6f8be 100644 --- a/src/maps.luau +++ b/src/maps.luau @@ -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 = graph.Node local create = graph.create @@ -9,6 +11,15 @@ local link = graph.link type Map = { [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(input: () -> Map, transform: (() -> VI, K) -> VO): () -> { VO } local input_cache = {} :: Map @@ -54,7 +65,8 @@ local function indexes(input: () -> Map, 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(input: () -> Map, transform: (VI, () -> local function recompute(data: Map) 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(input: () -> Map, transform: (VI, () -> for _, node in next, nodes do link(node, output, derive) end + check_primitives(output_array) output.cache = recompute(value) diff --git a/src/spring.luau b/src/spring.luau index 2326e51..b5f573c 100644 --- a/src/spring.luau +++ b/src/spring.luau @@ -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 diff --git a/test/benchmark.luau b/test/benchmark.luau index 1ea533e..f9bc37a 100644 --- a/test/benchmark.luau +++ b/test/benchmark.luau @@ -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) diff --git a/test/tests.luau b/test/tests.luau index 1de2f7e..068d33b 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -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" } - - 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") + do CASE "garbage collection" + do -- check that `output` does not allow gc of `input` + local input = source {} - 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() diff --git a/todo.md b/todo.md index 34a4b15..2cfdc63 100644 --- a/todo.md +++ b/todo.md @@ -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