mirror of
https://github.com/littensy/charm.git
synced 2026-10-22 12:34:09 +00:00
feat!: use alien-signals for reactivity (#50)
* feat: use alien-signals * refactor: remove pnpm, fix tests * fix: `effectScope` should set scope * fix: check previous value before calling listener * refactor: improve imports * refactor: exclude re-exports from wally * refactor: embrace scope cleanup, restructure code * fix: update pkgs dir references * ci: simplify pesde install * build: install dev deps with wally * feat: add detached, add more tests * test: simplify mapped test update * chore: rephrase doc comment * perf: optimize subscribe and mapped * refactor: avoid ambiguous optional function type * refactor: add assertion error messages * perf: optimize mapped, add benches * chore: delete redundant benchmarks * perf: simplify mapped logic * test: prettify test library * test: try different gray * test: color brackets * refactor: sync 2.0.4 * refactor: use setCurrentSub instead of pauseTracking * test: improve test suite * test: separate parent from root * refactor: iterate over previous data in mapped * refactor: improve logic * style: use `---` doc comments * refactor: improve logic * chore: housekeeping * ci: add pnpm step * ci: init submodules * test: use lune * refactor: improve structure and alien-signals integration * ci: update test file path * feat: add peek and roblox-ts types * feat: add deep freeze flag, misc refactors * refactor: reduce type casting, housekeeping * refactor: rewrite untracked, update alien-signals * chore: update eslint, housekeeping * refactor: start charm-sync rewrite * fix: send init payload even if atom did not update * test: add all sync tests * feat: prepare for alpha release * feat: add peek, make untracked escape scope * fix: wrap hooks in untracked, update types * feat: add flag for inner effect tracking * refactor: add docs, remove some modules * refactor: align with alien-signals 3.0 * feat: add signal api * refactor: update charm sync api * feat: update alien-signals * refactor: remove unnecessary features, update docs * feat: finalize sync docs * docs: update sync section * docs: simplify examples * docs: capitalize headers * docs: add migration tips * docs: fix whitespace * docs: rephrase computed arg change * refactor: rename batch, misc fixes & perf updates * refactor: simplify charm sync validation * feat: update alien-signals, add recursion flag * feat: add `recursive()` * fix: allow dispose in observers * feat: add `trigger()`, refactor source * docs: update readme * chore: prepare release candidate * test: make some tests more reliable * docs: fix typo * feat: add `reactive()` * chore: bump versions * docs: reword reactive docs * docs: fix typo * feat: add `toRaw()` * fix: check for nil diffs * chore: bump versions * perf: do not create proxies for iteration * fix: make `toRaw` track all calls * fix: check none before patching mutable tables * chore: bump charm-sync version * docs: add fishing game example project * feat: sync signals that return reactive proxy * fix: refine reactive proxy logic * fix: prevent early reactive gc, alien-signals 3.1.1 * fix: wrap iterated values in `reactive()` * fix: loosen client signal types * chore: bump release candidate * fix: remove unsynced signals immediately * test: use lute for tests * chore: bump versions * ci: set up lute * refactor: improve source readability * ci: upgrade lute * build: loosen workspace version requirements * chore: bump deps * fix: update signal overload for setter (#58) * fix: add missing recursed check * test: add important vue reactivity tests * chore: fix react tests, run stylua * chore: update vide * docs: improve intro and explain getters * docs: adjust ordering by importance * docs: move listen nesting note * build: fix react peer dep for ci * chore: update lockfile * ci: use javascript eslint config * ci: ignore eslint config file * refactor: move test directories * feat: allow recursion in effects by default * release: charm v0.11.0-rc.4 * refactor: remove recursive() * refactor: reorganize repo, create deep-charm * docs: rephrase some sections * feat: export getter and setter types * docs: rephrase intro again * feat!: run listen and subscribe callback in untracked * refactor!: remove peek * docs: note peek in migration guide * release: charm v0.11.0-rc.5 * release: charm-sync v0.4.0-rc.7 * release: react-charm v0.4.0-rc.4 * fix: remove old keys when updating key in `mapped()` * refactor: shorten error traceback, housekeeping * refactor: keep error level in some cases * fix: check if cleanup functions disposed effect * refactor!: run effects in batched * ci: check eslint and prettier * perf: run observe fn in untracked * perf: call mapper as-is in `mapped()` * refactor: error for effect cleanup errors * refactor: use new type solver * ci: disable new solver flag until 0.714 * chore: housekeeping * refactor: improve errors in nonstrict, optimize noyield * chore: housekeeping * refactor: satisfy new solver * refactor: improve error traceback * docs: link roblox signals * chore: housekeeping * perf: remove `clearPending` inline operation * refactor: check type of cleanup * ci: remove selene * fix: always return pending value in signal setter * test: add multi benchmark * release: charm v0.11.0-rc.6 * test: add cleanup order tests * chore: bump deps * docs: add warning for deep-charm and note for `observe()` --------- Co-authored-by: safazi <safazics@gmail.com>
This commit is contained in:
parent
d0b7cff943
commit
573533058f
148 changed files with 9222 additions and 4384 deletions
79
packages/deep-charm/test/gc.test.luau
Normal file
79
packages/deep-charm/test/gc.test.luau
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
local Charm = require("../Charm")
|
||||
local DeepCharm = require("../src")
|
||||
local test = require("../../../tests/test")
|
||||
|
||||
type ReactiveProxy<T = any> = DeepCharm.ReactiveProxy<T>
|
||||
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local reactive = DeepCharm.reactive
|
||||
|
||||
local function weakRef<T>(value: T): { value: T? }
|
||||
return setmetatable({ value = value }, { __mode = "v" }) :: any
|
||||
end
|
||||
|
||||
local function gc()
|
||||
for _ = 1, 2000 do
|
||||
local _ = table.create(100, 0)
|
||||
end
|
||||
end
|
||||
|
||||
test("should release inner reactive proxy", function()
|
||||
local valueRef = weakRef({})
|
||||
local src: any = reactive({ value = valueRef.value })
|
||||
|
||||
local dispose = effect(function()
|
||||
local _ = src.value -- create proxy
|
||||
end)
|
||||
src.value = nil :: any -- release proxy
|
||||
dispose()
|
||||
|
||||
gc()
|
||||
assert(src.__signals.value == nil, "should release signals")
|
||||
assert(valueRef.value == nil, "should release inner proxy")
|
||||
end)
|
||||
|
||||
test("should release reactive property deps", function()
|
||||
local src: any = reactive({ foo = 1 })
|
||||
|
||||
local c = computed(function()
|
||||
return src.foo
|
||||
end)
|
||||
|
||||
c()
|
||||
assert(src.__signals.foo ~= nil, "should have deps for foo")
|
||||
|
||||
c = nil :: any
|
||||
gc()
|
||||
assert(src.__signals.foo == nil, "should release deps for foo")
|
||||
end)
|
||||
|
||||
test("should not release effect for reactive", function()
|
||||
local src = reactive({ foo = 1 })
|
||||
local calls = 0
|
||||
|
||||
effect(function()
|
||||
calls += 1
|
||||
local _ = src.foo
|
||||
end)
|
||||
assert(calls == 1, "should call effect once")
|
||||
|
||||
gc()
|
||||
src.foo += 1
|
||||
assert(calls == 2, "should call effect again")
|
||||
end)
|
||||
|
||||
test("should not release tracked reactive", function()
|
||||
local src = reactive({ users = {} :: { [string]: {}? } })
|
||||
local user
|
||||
|
||||
effect(function()
|
||||
user = src.users.user
|
||||
end)
|
||||
|
||||
gc()
|
||||
src.users.user = {}
|
||||
assert(user ~= nil, "src.users.user should not be released")
|
||||
end)
|
||||
|
||||
return {}
|
||||
11
packages/deep-charm/test/init.luau
Normal file
11
packages/deep-charm/test/init.luau
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local test = require("../../tests/test")
|
||||
|
||||
test.describe("reactive", function()
|
||||
require(`@self/reactive.test`)
|
||||
end)
|
||||
|
||||
test.describe("gc", function()
|
||||
require(`@self/gc.test`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
232
packages/deep-charm/test/reactive.test.luau
Normal file
232
packages/deep-charm/test/reactive.test.luau
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
local Charm = require("../Charm")
|
||||
local DeepCharm = require("../src")
|
||||
local test = require("../../../tests/test")
|
||||
|
||||
local batch = Charm.batch
|
||||
local computed = Charm.computed
|
||||
local effect = Charm.effect
|
||||
local observe = Charm.observe
|
||||
local reactive = DeepCharm.reactive
|
||||
local toRaw = DeepCharm.toRaw
|
||||
|
||||
test("should track nested properties", function()
|
||||
local src, setSrc = reactive({ a = { b = { c = 0 } } })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, `{src.a.b.c}`)
|
||||
end)
|
||||
|
||||
setSrc(function(state)
|
||||
state.a.b.c = 1
|
||||
end)
|
||||
src.a.b.c = 2
|
||||
src.a.b.c = 2
|
||||
setSrc()
|
||||
assert(table.concat(calls) == "012", table.concat(calls))
|
||||
end)
|
||||
|
||||
test("should track object type changes", function()
|
||||
local src = reactive({ a = { b = 0 } } :: { a: any })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, tostring(if type(src.a) == "table" then src.a.b else src.a))
|
||||
end)
|
||||
|
||||
src.a = { b = 0 }
|
||||
src.a = 1
|
||||
src.a = { b = 2 }
|
||||
src.a = nil
|
||||
src.a = 3
|
||||
assert(table.concat(calls) == "0012nil3", table.concat(calls))
|
||||
end)
|
||||
|
||||
test("should unwrap proxy assignments", function()
|
||||
local src = reactive({ a = { x = 0 }, b = { x = 0 } })
|
||||
local runs = 0
|
||||
|
||||
effect(function()
|
||||
local _, _ = src.a.x, src.b.x
|
||||
runs += 1
|
||||
end)
|
||||
|
||||
src.a, src.b = src.a, src.b
|
||||
assert(runs == 1, `bad runs: {runs}`)
|
||||
|
||||
src.a, src.b = src.b, src.a
|
||||
assert(runs == 3, `bad runs: {runs}`)
|
||||
end)
|
||||
|
||||
test("should track iteration", function()
|
||||
local src, setSrc = reactive({} :: { [string]: number })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
for key, value in src do
|
||||
table.insert(calls, `{key}={value};`)
|
||||
end
|
||||
end)
|
||||
|
||||
src.a = 1
|
||||
assert(table.concat(calls) == "a=1;", table.concat(calls))
|
||||
|
||||
table.clear(calls)
|
||||
src.b = 2
|
||||
assert(table.concat(calls) == "a=1;b=2;", table.concat(calls))
|
||||
|
||||
table.clear(calls)
|
||||
src.a = 2
|
||||
assert(table.concat(calls) == "a=2;b=2;", table.concat(calls))
|
||||
|
||||
table.clear(calls)
|
||||
setSrc()
|
||||
assert(table.concat(calls) == "a=2;b=2;", table.concat(calls))
|
||||
end)
|
||||
|
||||
test("should track iteration for nested tables", function()
|
||||
local src = reactive({} :: { [string]: { value: number } })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
for key, obj in src do
|
||||
table.insert(calls, `{key}={obj.value};`)
|
||||
end
|
||||
end)
|
||||
|
||||
src.a = { value = 1 }
|
||||
|
||||
table.clear(calls)
|
||||
src.b = { value = 1 }
|
||||
assert(table.concat(calls) == "a=1;b=1;", table.concat(calls))
|
||||
|
||||
table.clear(calls)
|
||||
src.a.value = 2
|
||||
assert(table.concat(calls) == "a=2;b=1;", table.concat(calls))
|
||||
end)
|
||||
|
||||
test("should track array length", function()
|
||||
local src = reactive({ array = {} :: { number } })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, #src.array)
|
||||
end)
|
||||
|
||||
src.array[#src.array + 1] = 1
|
||||
src.array[#src.array + 1] = 2
|
||||
src.array[#src.array + 1] = 3
|
||||
src.array = { 1, 2, 3, 4 }
|
||||
assert(table.concat(calls) == "01234", table.concat(calls))
|
||||
end)
|
||||
|
||||
test("should not track unread properties", function()
|
||||
local src = reactive({ value = { a = 1, b = 2 } })
|
||||
local calls = 0
|
||||
|
||||
effect(function()
|
||||
local _ = src.value.a
|
||||
calls += 1
|
||||
end)
|
||||
assert(calls == 1, `bad calls: {calls}`)
|
||||
|
||||
src.value.b = 3
|
||||
assert(calls == 1, `bad calls: {calls}`)
|
||||
end)
|
||||
|
||||
test("should overwrite with table passed to setter", function()
|
||||
local src, setSrc = reactive({ a = 1, b = 2, c = 3 } :: { [string]: number? })
|
||||
local calls = {}
|
||||
|
||||
effect(function()
|
||||
table.insert(calls, `{src.a}{src.b}{src.c}`)
|
||||
end)
|
||||
|
||||
setSrc(function(src)
|
||||
src.a = 2
|
||||
src.b = nil
|
||||
src.c = 4
|
||||
end)
|
||||
assert(table.concat(calls, ",") == "123,2nil4", table.concat(calls, ","))
|
||||
end)
|
||||
|
||||
test("should unwrap proxy", function()
|
||||
local raw = {}
|
||||
local src = reactive(raw)
|
||||
local runs = 0
|
||||
|
||||
effect(function()
|
||||
local _ = toRaw(src)
|
||||
runs += 1
|
||||
end)
|
||||
|
||||
assert(toRaw(src) == raw, "unwrap did not return the original table")
|
||||
assert(toRaw(raw) == raw, "unwrap modified a non-proxy value")
|
||||
assert(runs == 1, `bad runs: {runs}`)
|
||||
|
||||
src.x = 1
|
||||
assert(runs == 2, `bad runs: {runs}`)
|
||||
end)
|
||||
|
||||
test("should unwrap nested proxy", function()
|
||||
local raw = {}
|
||||
local inner = reactive(raw)
|
||||
local src = reactive(inner)
|
||||
local runs = 0
|
||||
|
||||
effect(function()
|
||||
local _ = toRaw(src)
|
||||
runs += 1
|
||||
end)
|
||||
|
||||
assert(toRaw(src) == raw, "unwrap did not return the original table")
|
||||
assert(runs == 1, `bad runs: {runs}`)
|
||||
|
||||
inner.x = 1
|
||||
assert(runs == 2, `bad runs: {runs}`)
|
||||
|
||||
src.y = 2
|
||||
assert(runs == 3, `bad runs: {runs}`)
|
||||
end)
|
||||
|
||||
test("should observe items", function()
|
||||
local items, updateItems = reactive({ 0, 0, 0 })
|
||||
local updates = {}
|
||||
local cleanups = {}
|
||||
|
||||
observe(function()
|
||||
return toRaw(items)
|
||||
end, function(_, key: number)
|
||||
local get = computed(function()
|
||||
return items[key]
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
table.insert(updates, get())
|
||||
end)
|
||||
|
||||
return function()
|
||||
table.insert(cleanups, key)
|
||||
end
|
||||
end)
|
||||
|
||||
assert(table.concat(updates) == "000", `bad mount: {table.concat(updates)}`)
|
||||
|
||||
table.clear(updates)
|
||||
updateItems(function(state)
|
||||
state[1] = 1
|
||||
state[2] = 2
|
||||
state[3] = 3
|
||||
end)
|
||||
assert(table.concat(updates) == "123", `bad update: {table.concat(updates)}`)
|
||||
|
||||
table.clear(updates)
|
||||
batch(function()
|
||||
items[3] = nil
|
||||
items[4] = 4
|
||||
end)
|
||||
assert(table.concat(updates) == "4", `should add 4: {table.concat(updates)}`)
|
||||
assert(table.concat(cleanups) == "3", `bad cleanup: {table.concat(cleanups)}`)
|
||||
end)
|
||||
|
||||
return {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue