u.nvim/spec/tracker_spec.lua
Jonathan Apodaca 46e3322c42
All checks were successful
NeoVim tests / plenary-tests (push) Successful in 9s
experimental: renderer
2025-03-16 22:32:43 -06:00

207 lines
6.3 KiB
Lua

local tracker = require 'u.tracker'
local Signal = tracker.Signal
local ExecutionContext = tracker.ExecutionContext
describe('Signal', function()
local signal
before_each(function() signal = Signal:new(0, 'testSignal') end)
it('should initialize with correct parameters', function()
assert.is.equal(signal.value, 0)
assert.is.equal(signal.name, 'testSignal')
assert.is.not_nil(signal.subscribers)
assert.is.equal(#signal.subscribers, 0)
assert.is.equal(signal.changing, false)
end)
it('should set new value and notify subscribers', function()
local called = false
signal:subscribe(function(value)
called = true
assert.is.equal(value, 42)
end)
signal:set(42)
assert.is.equal(called, true)
end)
it('should not notify subscribers during circular dependency', function()
signal.changing = true
local notified = false
signal:subscribe(function() notified = true end)
signal:set(42)
assert.is.equal(notified, false) -- No notification should occur
end)
it('should get current value', function()
signal:set(100)
assert.is.equal(signal:get(), 100)
end)
it('should update value with function', function()
signal:set(10)
signal:update(function(value) return value * 2 end)
assert.is.equal(signal:get(), 20)
end)
it('should dispose subscribers', function()
local called = false
local unsubscribe = signal:subscribe(function() called = true end)
unsubscribe()
signal:set(10)
assert.is.equal(called, false) -- Should not be notified
end)
describe('Signal:map', function()
it('should transform the signal value', function()
local signal = Signal:new(5)
local mapped_signal = signal:map(function(value) return value * 2 end)
assert.is.equal(mapped_signal:get(), 10) -- Initial transformation
signal:set(10)
assert.is.equal(mapped_signal:get(), 20) -- Updated transformation
end)
it('should handle empty transformations', function()
local signal = Signal:new(nil)
local mapped_signal = signal:map(function(value) return value or 'default' end)
assert.is.equal(mapped_signal:get(), 'default') -- Return default
signal:set 'new value'
assert.is.equal(mapped_signal:get(), 'new value') -- Return new value
end)
end)
describe('Signal:filter', function()
it('should only emit values that pass the filter', function()
local signal = Signal:new(5)
local filtered_signal = signal:filter(function(value) return value > 10 end)
assert.is.equal(filtered_signal:get(), nil) -- Initial value should not pass
signal:set(15)
assert.is.equal(filtered_signal:get(), 15) -- Now filtered
signal:set(8)
assert.is.equal(filtered_signal:get(), 15) -- Does not pass the filter
end)
it('should handle empty initial values', function()
local signal = Signal:new(nil)
local filtered_signal = signal:filter(function(value) return value ~= nil end)
assert.is.equal(filtered_signal:get(), nil) -- Should be nil
signal:set(10)
assert.is.equal(filtered_signal:get(), 10) -- Should pass now
end)
end)
describe('create_memo', function()
it('should compute a derived value and update when dependencies change', function()
local signal = Signal:new(2)
local memoized_signal = tracker.create_memo(function() return signal:get() * 2 end)
assert.is.equal(memoized_signal:get(), 4) -- Initially compute 2 * 2
signal:set(3)
assert.is.equal(memoized_signal:get(), 6) -- Update to 3 * 2 = 6
signal:set(5)
assert.is.equal(memoized_signal:get(), 10) -- Update to 5 * 2 = 10
end)
it('should not recompute if the dependencies do not change', function()
local call_count = 0
local signal = Signal:new(10)
local memoized_signal = tracker.create_memo(function()
call_count = call_count + 1
return signal:get() + 1
end)
assert.is.equal(memoized_signal:get(), 11) -- Compute first value
assert.is.equal(call_count, 1) -- Should compute once
memoized_signal:get() -- Call again, should use memoized value
assert.is.equal(call_count, 1) -- Still should only be one call
signal:set(10) -- Set the same value
assert.is.equal(memoized_signal:get(), 11)
assert.is.equal(call_count, 2)
signal:set(20)
assert.is.equal(memoized_signal:get(), 21)
assert.is.equal(call_count, 3)
end)
end)
describe('create_effect', function()
it('should track changes and execute callback', function()
local signal = Signal:new(5)
local call_count = 0
tracker.create_effect(function()
signal:get() -- track as a dependency
call_count = call_count + 1
end)
assert.is.equal(call_count, 1)
signal:set(10)
assert.is.equal(call_count, 2)
end)
it('should clean up signals and not call after dispose', function()
local signal = Signal:new(5)
local call_count = 0
local unsubscribe = tracker.create_effect(function()
call_count = call_count + 1
return signal:get() * 2
end)
assert.is.equal(call_count, 1) -- Initially calls
unsubscribe() -- Unsubscribe the effect
signal:set(10) -- Update signal value
assert.is.equal(call_count, 1) -- Callback should not be called again
end)
end)
end)
describe('ExecutionContext', function()
local context
before_each(function() context = ExecutionContext:new() end)
it('should initialize a new context', function()
assert.is.table(context.signals)
assert.is.table(context.subscribers)
end)
it('should track signals', function()
local signal = Signal:new(0)
context:track(signal)
assert.is.equal(next(context.signals), signal) -- Check if signal is tracked
end)
it('should subscribe to signals', function()
local signal = Signal:new(0)
local callback_called = false
context:track(signal)
context:subscribe(function() callback_called = true end)
signal:set(100)
assert.is.equal(callback_called, true) -- Callback should be called
end)
it('should dispose tracked signals', function()
local signal = Signal:new(0)
context:track(signal)
context:dispose()
assert.is.falsy(next(context.signals)) -- Should not have any tracked signals
end)
end)