From 629bdf27b4a56d91491dc1b1cd2251ae4395d796 Mon Sep 17 00:00:00 2001 From: Jonathan Apodaca Date: Sat, 11 Oct 2025 16:40:07 -0600 Subject: [PATCH] Renderer.mount: add tests --- lua/u/renderer.lua | 7 ++++ spec/renderer_spec.lua | 95 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/lua/u/renderer.lua b/lua/u/renderer.lua index 3a9a13d..a10c0fe 100644 --- a/lua/u/renderer.lua +++ b/lua/u/renderer.lua @@ -60,6 +60,13 @@ function FnComponentContext:update(new_state) if self.on_change then vim.schedule(function() self.on_change() end) end end +--- @private +--- @param new_state TState +function FnComponentContext:update_immediate(new_state) + self.state = new_state + if self.on_change then self.on_change() end +end + --- @alias u.renderer.FnComponent fun(ctx: u.renderer.FnComponentContext): u.renderer.Tree --- @class u.renderer.Tag diff --git a/spec/renderer_spec.lua b/spec/renderer_spec.lua index 5c4c670..891ce29 100644 --- a/spec/renderer_spec.lua +++ b/spec/renderer_spec.lua @@ -294,4 +294,99 @@ describe('Renderer', function() assert.are.same(bounds, { start = { 0, 9 }, stop = { 0, 19 } }) end) end) + + it('should mount and rerender components', function() + withbuf({}, function() + --- @type any + local leaked_state = { app = {}, c1 = {}, c2 = {} } + + --- @param ctx u.renderer.FnComponentContext + local function Counter(ctx) + if ctx.phase == 'mount' then ctx.state = { phase = ctx.phase, count = 1 } end + local state = assert(ctx.state) + state.phase = ctx.phase + leaked_state[ctx.props.id].ctx = ctx + + return { + { 'Value: ', R.h.Number({}, tostring(state.count)) }, + } + end + + --- @param ctx u.renderer.FnComponentContext + function App(ctx) + if ctx.phase == 'mount' then ctx.state = { toggle1 = false, show2 = true } end + local state = assert(ctx.state) + leaked_state.app.ctx = ctx + + return { + state.toggle1 and 'Toggle1' or R.h(Counter, { id = 'c1' }, {}), + '\n', + + state.show2 and { + '\n', + R.h(Counter, { id = 'c2' }, {}), + }, + } + end + + local renderer = R.Renderer.new() + renderer:mount(R.h(App, {}, {})) + + local Buffer = require 'u.buffer' + local buf = Buffer.current() + assert.are.same(buf:all():lines(), { + 'Value: 1', + '', + 'Value: 1', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'mount') + assert.are.same(leaked_state.c2.ctx.state.phase, 'mount') + leaked_state.app.ctx:update_immediate { toggle1 = true, show2 = true } + assert.are.same(buf:all():lines(), { + 'Toggle1', + '', + 'Value: 1', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'unmount') + assert.are.same(leaked_state.c2.ctx.state.phase, 'update') + + leaked_state.app.ctx:update_immediate { toggle1 = true, show2 = false } + assert.are.same(buf:all():lines(), { + 'Toggle1', + '', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'unmount') + assert.are.same(leaked_state.c2.ctx.state.phase, 'unmount') + + leaked_state.app.ctx:update_immediate { toggle1 = false, show2 = true } + assert.are.same(buf:all():lines(), { + 'Value: 1', + '', + 'Value: 1', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'mount') + assert.are.same(leaked_state.c2.ctx.state.phase, 'mount') + + leaked_state.c1.ctx:update_immediate { count = 2 } + assert.are.same(buf:all():lines(), { + 'Value: 2', + '', + 'Value: 1', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'update') + assert.are.same(leaked_state.c2.ctx.state.phase, 'update') + + leaked_state.c2.ctx:update_immediate { count = 3 } + assert.are.same(buf:all():lines(), { + 'Value: 2', + '', + 'Value: 3', + }) + assert.are.same(leaked_state.c1.ctx.state.phase, 'update') + assert.are.same(leaked_state.c2.ctx.state.phase, 'update') + end) + end) end)