mirror of
https://github.com/littensy/charm.git
synced 2026-10-21 19:54:09 +00:00
* 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>
220 lines
5 KiB
Text
220 lines
5 KiB
Text
local Charm = require("../src")
|
|
local test = require("../../../tests/test")
|
|
|
|
local atom = Charm.atom
|
|
local computed = Charm.computed
|
|
local effect = Charm.effect
|
|
local signal = Charm.signal
|
|
|
|
test("should correctly propagate changes through computed core", function()
|
|
local src, setSrc = signal(0)
|
|
local c1 = computed(function()
|
|
return src() % 2
|
|
end)
|
|
local c2 = computed(function()
|
|
return c1()
|
|
end)
|
|
local c3 = computed(function()
|
|
return c2()
|
|
end)
|
|
|
|
c3()
|
|
setSrc(1) -- c1 -> dirty, c2 -> toCheckDirty, c3 -> toCheckDirty
|
|
c2() -- c1 -> none, c2 -> none
|
|
setSrc(3) -- c1 -> dirty, c2 -> toCheckDirty
|
|
|
|
assert(c3() == 1, "bad final value")
|
|
end)
|
|
|
|
test("should propagate updated source value through chained computations", function()
|
|
local src, setSrc = signal(0)
|
|
local a = computed(function()
|
|
return src()
|
|
end)
|
|
local b = computed(function()
|
|
return a() % 2
|
|
end)
|
|
local c = computed(function()
|
|
return src()
|
|
end)
|
|
local d = computed(function()
|
|
return b() + c()
|
|
end)
|
|
|
|
assert(d() == 0, "bad initial value")
|
|
setSrc(2)
|
|
assert(d() == 2, "bad updated value")
|
|
end)
|
|
|
|
test("should handle flags are indirectly updated during checkDirty", function()
|
|
local a, setA = signal(false)
|
|
local b = computed(function()
|
|
return a()
|
|
end)
|
|
local c = computed(function()
|
|
b()
|
|
return 0
|
|
end)
|
|
local d = computed(function()
|
|
c()
|
|
return b()
|
|
end)
|
|
|
|
assert(d() == false, "bad initial value")
|
|
setA(true)
|
|
assert(d() == true, "bad updated value")
|
|
end)
|
|
|
|
test("should not update if the signal value is reverted", function()
|
|
local times = 0
|
|
|
|
local src, setSrc = signal(0)
|
|
local c1 = computed(function()
|
|
times += 1
|
|
return src()
|
|
end)
|
|
c1()
|
|
assert(times == 1, "computed did not run")
|
|
setSrc(1)
|
|
setSrc(0)
|
|
c1()
|
|
assert(times == 1, "computed ran when it should not")
|
|
end)
|
|
|
|
test("should receive previous value", function()
|
|
local calls = {}
|
|
local src, setSrc = signal(0)
|
|
local c1 = computed(function(current: number?): number
|
|
local incoming = src()
|
|
return if current == incoming or incoming % 2 == 1 then current or incoming else incoming
|
|
end)
|
|
|
|
effect(function()
|
|
table.insert(calls, c1())
|
|
end)
|
|
|
|
setSrc(2)
|
|
setSrc(3)
|
|
assert(c1() == 2, "should skip odd value")
|
|
|
|
setSrc(4)
|
|
setSrc(5)
|
|
assert(c1() == 4, "should skip odd value")
|
|
assert(table.concat(calls) == "024", `should only call on even updates, got {table.concat(calls)}`)
|
|
end)
|
|
|
|
-- https://github.com/vuejs/core/blob/minor/packages/reactivity/__tests__/computed.spec.ts#L875
|
|
test("should chained computeds keep reactivity when computed effect happens", function()
|
|
local v = atom("Hello")
|
|
local c = computed(function()
|
|
v(v() .. " World")
|
|
return v()
|
|
end)
|
|
local d = computed(function()
|
|
return c()
|
|
end)
|
|
local e = computed(function()
|
|
return d()
|
|
end)
|
|
local res = computed(function()
|
|
return d() .. " | " .. e()
|
|
end)
|
|
|
|
assert(res() == "Hello World | Hello World", res())
|
|
|
|
v(v() .. " World")
|
|
assert(res() == "Hello World World World World | Hello World World World World", res())
|
|
end)
|
|
|
|
-- https://github.com/vuejs/core/blob/minor/packages/reactivity/__tests__/computed.spec.ts#L580
|
|
test("should not trigger effect scheduler by recursive computed effect", function()
|
|
local v = atom("Hello")
|
|
local c = computed(function()
|
|
v(v() .. " World")
|
|
return v()
|
|
end)
|
|
local comp = computed(function()
|
|
return c()
|
|
end)
|
|
|
|
assert(comp() == "Hello World", comp())
|
|
|
|
v(v() .. " World")
|
|
assert(comp() == "Hello World World World World", comp())
|
|
end)
|
|
|
|
-- https://github.com/vuejs/core/blob/minor/packages/reactivity/__tests__/computed.spec.ts#L528
|
|
test("should not override queried MaybeDirty result", function()
|
|
local v1 = atom(nil :: any)
|
|
local v2 = atom(false)
|
|
local c1 = computed(function()
|
|
local c = v1()
|
|
if not v1() then
|
|
c = { v = atom(0) }
|
|
v1(c)
|
|
end
|
|
return c.v()
|
|
end)
|
|
local c2 = computed(function()
|
|
if not v2() then
|
|
return "no"
|
|
end
|
|
return if c1() then "yes" else "no"
|
|
end)
|
|
local c3 = computed(function()
|
|
return c2()
|
|
end)
|
|
|
|
c3()
|
|
v2(true)
|
|
|
|
c3()
|
|
v1().v(999)
|
|
|
|
assert(c3() == "yes", "should return correct value after update")
|
|
end)
|
|
|
|
-- https://github.com/vuejs/core/blob/minor/packages/reactivity/__tests__/computed.spec.ts#L503
|
|
test("should trigger effect even computed already dirty", function()
|
|
local calls: { string } = {}
|
|
local v = atom(0)
|
|
local c1 = computed(function()
|
|
if v() == 0 then
|
|
v(1)
|
|
end
|
|
return "foo"
|
|
end)
|
|
local c2 = computed(function()
|
|
return v() .. c1()
|
|
end)
|
|
|
|
effect(function()
|
|
table.insert(calls, c2())
|
|
end)
|
|
assert(table.concat(calls, ",") == "0foo", table.concat(calls, ","))
|
|
assert(v() == 1, `expected 1 for v(), got {v()}`)
|
|
v(2)
|
|
assert(table.concat(calls, ",") == "0foo,2foo", table.concat(calls, ","))
|
|
assert(v() == 2, `expected 2 for v(), got {v()}`)
|
|
end)
|
|
|
|
test("should handle inner effect clearing outer computed's deps on rerun", function()
|
|
local src, setSrc = signal(false)
|
|
local stopEffect: () -> ()
|
|
local c = computed(function()
|
|
src()
|
|
effect(function()
|
|
return function()
|
|
stopEffect()
|
|
end
|
|
end)
|
|
effect(function() end)
|
|
return 0
|
|
end)
|
|
stopEffect = effect(function()
|
|
c()
|
|
end)
|
|
setSrc(true)
|
|
end)
|
|
|
|
return {}
|