From ceae0a225c02f5ae223d6aae3d0bee8a527734d7 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Tue, 10 Feb 2026 23:51:26 +0100 Subject: [PATCH 1/7] tests(helpers): add helper functionality for testing --- spec/00-helpers_spec.lua | 50 +++++++++++++++++++++++++++++++++++++++ spec/helpers.lua | 51 ++++++++++++++++++++++++++++++++++++++++ src/terminal/init.lua | 3 ++- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 spec/00-helpers_spec.lua create mode 100644 spec/helpers.lua diff --git a/spec/00-helpers_spec.lua b/spec/00-helpers_spec.lua new file mode 100644 index 0000000..47f340b --- /dev/null +++ b/spec/00-helpers_spec.lua @@ -0,0 +1,50 @@ +describe("Spec helpers", function() + + local helpers + + setup(function() + helpers = require("spec.helpers") + end) + + + + it("removes terminal and system from package.loaded", function() + helpers.load() + assert.is_not_nil(package.loaded["terminal"]) + helpers.unload() + assert.is_nil(package.loaded["terminal"]) + assert.is_nil(package.loaded["system"]) + end) + + + it("allows load() to return terminal again after unload", function() + helpers.unload() + local _ = helpers.load() + assert.is_not_nil(package.loaded["terminal"]) + helpers.unload() + assert.is_nil(package.loaded["terminal"]) + local t2 = helpers.load() + assert.is_table(t2) + assert.is_not_nil(package.loaded["terminal"]) + end) + + + it("returns terminal module with expected API after unload then load", function() + helpers.unload() + local terminal = helpers.load() + assert.is_table(terminal.input) + end) + + + it("returns valid terminal table on second load after unload", function() + helpers.unload() + local t1 = helpers.load() + helpers.unload() + local t2 = helpers.load() + assert.is_table(t2) + assert.is_table(t2.utils) + assert.is_table(t2.input) + assert.not_equal(t1, t2) + end) + +end) diff --git a/spec/helpers.lua b/spec/helpers.lua new file mode 100644 index 0000000..3114307 --- /dev/null +++ b/spec/helpers.lua @@ -0,0 +1,51 @@ +--- Test helper for Busted specs: load and unload the terminal library (and LuaSystem) +-- for test isolation. Cleans from `package.loaded`: `"terminal"`, any `"terminal.*"`, +-- and `"system"`. Intended for use from Busted setup/teardown or before_each/after_each. +-- This module does not require terminal or system at load time. +-- @module spec.helpers + + + +local M = {} + + + +-- Remove terminal and LuaSystem from package.loaded and run GC. +-- Used internally by load() and unload(). Cleans `"terminal"`, `"system"`, and +-- any key starting with `"terminal."`. +local function clean() + for key, _ in pairs(package.loaded) do + if key == "terminal" or + key == "system" or + (type(key) == "string" and key:sub(1, 11) == "terminal.") then + package.loaded[key] = nil + end + end + -- call twice to ensure finalization is complete + collectgarbage() + collectgarbage() +end + + + +--- Load the main terminal module after cleaning package.loaded. +-- Cleans terminal and LuaSystem from `package.loaded`, then requires the main +-- `terminal` module and returns it. Call from Busted setup or before_each. +-- @treturn table the main terminal module +function M.load() + clean() + return require("terminal") +end + + + +--- Remove terminal and LuaSystem from package.loaded. +-- Does not require terminal. Call from Busted teardown or after_each so the +-- next load() gets a fresh terminal. Idempotent. +function M.unload() + clean() +end + + + +return M diff --git a/src/terminal/init.lua b/src/terminal/init.lua index c33a3d1..2a77aeb 100644 --- a/src/terminal/init.lua +++ b/src/terminal/init.lua @@ -32,7 +32,7 @@ local sys = require "system" -- Push the module table already in `package.loaded` to avoid circular dependencies package.loaded["terminal"] = M --- load the submodules +-- load the submodules; all but object; editline, sequence, cli.*, ui.* M.input = require("terminal.input") M.output = require("terminal.output") M.clear = require("terminal.clear") @@ -41,6 +41,7 @@ M.cursor = require("terminal.cursor") M.text = require("terminal.text") M.draw = require("terminal.draw") M.progress = require("terminal.progress") +M.utils = require("terminal.utils") -- create locals local output = M.output local scroll = M.scroll From d2275bf30c415f26161d0fbc5f23f144995f106a Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 11:39:22 +0100 Subject: [PATCH 2/7] test(helpers): added mocking for termsize() --- spec/00-helpers_spec.lua | 92 +++++++++++++++++++---------- spec/helpers.lua | 122 +++++++++++++++++++++++++++++++++------ 2 files changed, 166 insertions(+), 48 deletions(-) diff --git a/spec/00-helpers_spec.lua b/spec/00-helpers_spec.lua index 47f340b..6c21d71 100644 --- a/spec/00-helpers_spec.lua +++ b/spec/00-helpers_spec.lua @@ -8,43 +8,75 @@ describe("Spec helpers", function() - it("removes terminal and system from package.loaded", function() - helpers.load() - assert.is_not_nil(package.loaded["terminal"]) - helpers.unload() - assert.is_nil(package.loaded["terminal"]) - assert.is_nil(package.loaded["system"]) - end) + describe("(un)loading", function() + it("removes terminal and system from package.loaded", function() + helpers.load() + assert.is_not_nil(package.loaded["terminal"]) + helpers.unload() + assert.is_nil(package.loaded["terminal"]) + assert.is_nil(package.loaded["system"]) + end) + + + it("reloads terminal after unload", function() + helpers.unload() + local _ = helpers.load() + assert.is_not_nil(package.loaded["terminal"]) + helpers.unload() + assert.is_nil(package.loaded["terminal"]) + local t2 = helpers.load() + assert.is_table(t2) + assert.is_not_nil(package.loaded["terminal"]) + end) - it("allows load() to return terminal again after unload", function() - helpers.unload() - local _ = helpers.load() - assert.is_not_nil(package.loaded["terminal"]) - helpers.unload() - assert.is_nil(package.loaded["terminal"]) - local t2 = helpers.load() - assert.is_table(t2) - assert.is_not_nil(package.loaded["terminal"]) - end) + it("returns terminal module with expected API", function() + helpers.unload() + local terminal = helpers.load() + assert.is_table(terminal.input) + end) + + + it("returns a fresh terminal table on reload", function() + helpers.unload() + local t1 = helpers.load() + helpers.unload() + local t2 = helpers.load() + assert.is_table(t2) + assert.is_table(t2.utils) + assert.is_table(t2.input) + assert.not_equal(t1, t2) + end) - it("returns terminal module with expected API after unload then load", function() - helpers.unload() - local terminal = helpers.load() - assert.is_table(terminal.input) end) - it("returns valid terminal table on second load after unload", function() - helpers.unload() - local t1 = helpers.load() - helpers.unload() - local t2 = helpers.load() - assert.is_table(t2) - assert.is_table(t2.utils) - assert.is_table(t2.input) - assert.not_equal(t1, t2) + + describe("termsize", function() + + it("defaults to 25x80", function() + helpers.unload() + helpers.load() + + local rows, cols = helpers.get_termsize() + assert.equals(25, rows) + assert.equals(80, cols) + end) + + + it("patches system.termsize to use mocked values", function() + helpers.unload() + helpers.load() + + helpers.set_termsize(30, 90) + + local system = require("system") + local rows, cols = system.termsize() + assert.equals(30, rows) + assert.equals(90, cols) + end) + end) end) diff --git a/spec/helpers.lua b/spec/helpers.lua index 3114307..705d6e0 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -8,42 +8,128 @@ local M = {} +local terminal -- to hold the terminal module if loaded +local system -- to hold the system module if loaded --- Remove terminal and LuaSystem from package.loaded and run GC. --- Used internally by load() and unload(). Cleans `"terminal"`, `"system"`, and --- any key starting with `"terminal."`. -local function clean() - for key, _ in pairs(package.loaded) do - if key == "terminal" or - key == "system" or - (type(key) == "string" and key:sub(1, 11) == "terminal.") then - package.loaded[key] = nil + +local get_config do + -- this config table holds config values as configured for the mock functions. + -- the key is the terminal module-table, the value a hash-table of config values. + -- Because it is set to weak-key, the table is only kept alive if the terminal module + -- is still in use, aftre a reload it will be a fresh copy again. + local config = setmetatable({}, { __mode = "k" }) + + + -- returns the mock config for the current modules (system and terminal) + -- every mock function should call this to ensure we run the asserts. + function get_config() + assert(terminal, "modules not loaded yet, first call 'load()'") + + local cfg = config[terminal] + if not cfg then + cfg = setmetatable({}, { + __index = function(self, key) + -- the top-level is an "auto-table", if there is no key, we create and return an empty table. + -- so we can reduce extensive checking elsewhere in this module + self[key] = {} + return self[key] + end + }) + config[terminal] = cfg end + return cfg end - -- call twice to ensure finalization is complete - collectgarbage() - collectgarbage() end ---- Load the main terminal module after cleaning package.loaded. +-- ==================================================================================================== +-- Mock functions for system and terminal +-- ==================================================================================================== + + + +-- Sets the terminal size. +-- @tparam number rows number of rows +-- @tparam number columns number of columns +function M.set_termsize(rows, columns) + assert(type(rows) == "number", "rows must be a number, got " .. type(rows)) + assert(type(columns) == "number", "columns must be a number, got " .. type(columns)) + + get_config().termsize = { + rows = rows, + columns = columns, + } +end + + + +--- Gets the terminal size. +-- This is the mock function for `system.termsize()`. +-- If the terminal size wasn't set yet, returns 25x80. +-- @treturn number rows number of rows +-- @treturn number columns number of columns +function M.get_termsize() + local cfg = get_config() + return cfg.termsize.rows or 25, cfg.termsize.columns or 80 +end + + + +-- ==================================================================================================== +-- (Un)loading and patching system and terminal to enable mocks +-- ==================================================================================================== + + +-- Patches system to enable mocking +local function patch_system() + system.termsize = M.get_termsize +end + + + +-- Patches terminal to enable mocking +local function patch_terminal() +end + + + +--- Load the main terminal and system modules after cleaning package.loaded. -- Cleans terminal and LuaSystem from `package.loaded`, then requires the main --- `terminal` module and returns it. Call from Busted setup or before_each. +-- `terminal` module. Will patch both libraries to enable the mocks. +-- Call from Busted setup or before_each. -- @treturn table the main terminal module function M.load() - clean() - return require("terminal") + M.unload() + + system = require("system") + patch_system() + + terminal = require("terminal") + patch_terminal() + + return terminal end --- Remove terminal and LuaSystem from package.loaded. --- Does not require terminal. Call from Busted teardown or after_each so the +-- Does not load them again. Call from Busted teardown or after_each so the -- next load() gets a fresh terminal. Idempotent. function M.unload() - clean() + terminal = nil + system = nil + for key, _ in pairs(package.loaded) do + if key == "terminal" or + key == "system" or + (type(key) == "string" and key:sub(1, 11) == "terminal.") then + package.loaded[key] = nil + end + end + -- call twice to ensure finalization is complete + collectgarbage() + collectgarbage() end From 4f9bbae524af81b7e0aab74278c6626dab48ed1b Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 12:12:49 +0100 Subject: [PATCH 3/7] test(helpers): added mocking for system._readkey() --- spec/00-helpers_spec.lua | 55 ++++++++++++++++++++++++++++++++++++++-- spec/helpers.lua | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/spec/00-helpers_spec.lua b/spec/00-helpers_spec.lua index 6c21d71..8ad7936 100644 --- a/spec/00-helpers_spec.lua +++ b/spec/00-helpers_spec.lua @@ -56,7 +56,6 @@ describe("Spec helpers", function() describe("termsize", function() it("defaults to 25x80", function() - helpers.unload() helpers.load() local rows, cols = helpers.get_termsize() @@ -66,7 +65,6 @@ describe("Spec helpers", function() it("patches system.termsize to use mocked values", function() - helpers.unload() helpers.load() helpers.set_termsize(30, 90) @@ -79,4 +77,57 @@ describe("Spec helpers", function() end) + + describe("keyboard input mock", function() + + it("returns bytes from the helper _readkey buffer", function() + helpers.load() + + helpers._push_input("ab") + + local b1 = helpers._readkey() + local b2 = helpers._readkey() + local b3 = helpers._readkey() + + assert.equals(string.byte("a"), b1) + assert.equals(string.byte("b"), b2) + assert.is_nil(b3) + end) + + + it("returns nil and error when pushing an error entry", function() + helpers.load() + + helpers._push_input(nil, "some-error") + + local b, err = helpers._readkey() + assert.is_nil(b) + assert.equals("some-error", err) + end) + + + it("patches system._readkey to use the mock buffer", function() + helpers.load() + + helpers._push_input("X") + + local system = require("system") + local b = system._readkey() + + assert.equals(string.byte("X"), b) + end) + + + it("terminal.input.readansi() returns data read from the mock buffer", function() + local terminal = helpers.load() + + helpers._push_input("X") + + local rawkey, keytype = terminal.input.readansi(0.01) + assert.equals("X", rawkey) + assert.equals("char", keytype) + end) + + end) + end) diff --git a/spec/helpers.lua b/spec/helpers.lua index 705d6e0..4087484 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -13,6 +13,12 @@ local system -- to hold the system module if loaded +-- nil safe versions of pack and unpack +local pack = require("pl.utils").pack +local unpack = require("pl.utils").unpack + + + local get_config do -- this config table holds config values as configured for the mock functions. -- the key is the terminal module-table, the value a hash-table of config values. @@ -77,6 +83,42 @@ end +--- Reads a single byute from the keyboard buffer, which is mocked. +-- This is the mock for `system._readkey()` +-- @treturn number the byte read from the keyboard buffer, or nil if the buffer is empty +function M._readkey() + local buffer = get_config().keyboardbuffer + local entry = table.remove(buffer, 1) or {} + return unpack(entry) +end + + + +-- Pushes input into the keyboard buffer mock. +-- @tparam string seq the sequence of input, individual bytes of this string will be returned +-- @tparam string err an eror to return, in this case `seq` MUST be nil. +function M._push_input(seq, err) + local buffer = get_config().keyboardbuffer + + if type(seq) == "string" then + assert(err == nil, "error must be nil if seq is a string") + assert(seq ~= "", "seq must be a non-empty string") + for i = 1, #seq do + table.insert(buffer, pack(string.byte(seq, i))) + end + + elseif seq == nil then + assert(type(err) == "string", "err must be a string if seq is nil") + assert(err ~= "", "err must be a non-empty string") + table.insert(buffer, pack(nil, err)) + + else + error("invalid type for seq, must be a string or nil") + end +end + + + -- ==================================================================================================== -- (Un)loading and patching system and terminal to enable mocks -- ==================================================================================================== @@ -85,6 +127,7 @@ end -- Patches system to enable mocking local function patch_system() system.termsize = M.get_termsize + system._readkey = M._readkey end @@ -106,6 +149,7 @@ function M.load() system = require("system") patch_system() + _G._TEST = true -- some modules export some private internals for testing terminal = require("terminal") patch_terminal() @@ -118,8 +162,9 @@ end -- Does not load them again. Call from Busted teardown or after_each so the -- next load() gets a fresh terminal. Idempotent. function M.unload() + _G._TEST = nil terminal = nil - system = nil + system = nil -- luacheck: ignore for key, _ in pairs(package.loaded) do if key == "terminal" or key == "system" or From e18b5b7e8d5f7cba8c2925fc081255ab1ed0b9fb Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 13:17:44 +0100 Subject: [PATCH 4/7] test(helpers): add mocking of the output stream to a tmp file --- spec/00-helpers_spec.lua | 34 +++++++++++++++++ spec/helpers.lua | 81 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/spec/00-helpers_spec.lua b/spec/00-helpers_spec.lua index 8ad7936..bf3334e 100644 --- a/spec/00-helpers_spec.lua +++ b/spec/00-helpers_spec.lua @@ -78,6 +78,7 @@ describe("Spec helpers", function() end) + describe("keyboard input mock", function() it("returns bytes from the helper _readkey buffer", function() @@ -130,4 +131,37 @@ describe("Spec helpers", function() end) + + + describe("output capture", function() + + it("accumulates writes between reads", function() + local terminal = helpers.load() + + terminal.output.write("one") + local first = helpers.get_output() + + terminal.output.write("two") + local second = helpers.get_output() + + assert.equals("one", first) + assert.equals("onetwo", second) + end) + + + it("clears output and starts fresh", function() + local terminal = helpers.load() + + terminal.output.write("abc") + assert.equals("abc", helpers.get_output()) + + helpers.clear_output() + terminal.output.write("xyz") + + local out = helpers.get_output() + assert.equals("xyz", out) + end) + + end) + end) diff --git a/spec/helpers.lua b/spec/helpers.lua index 4087484..1fc84dd 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -94,10 +94,11 @@ end --- Pushes input into the keyboard buffer mock. +--- Pushes input into the keyboard buffer mock. -- @tparam string seq the sequence of input, individual bytes of this string will be returned -- @tparam string err an eror to return, in this case `seq` MUST be nil. function M._push_input(seq, err) + -- TODO: rename this function, remove underscore local buffer = get_config().keyboardbuffer if type(seq) == "string" then @@ -119,6 +120,44 @@ end +-- Gets the output written to the output stream. +-- @treturn string the output written to the output stream, empty string if no output +-- was written yet. +function M.get_output() + local cfg = get_config() + if not cfg.output.filename then + return "" + end + return assert(require("pl.utils").readfile(cfg.output.filename)) +end + + + +--- Clears the output written to the output stream. +-- and recreates an empty output file. +function M.clear_output() + local cfg = get_config() + + -- close an existing file + if cfg.output.filehandle then + cfg.output.filehandle:close() + cfg.output.filehandle = nil + end + + -- remove an existing file, define name if none set yet + if cfg.output.filename then + os.remove(cfg.output.filename) + else + cfg.output.filename = require("pl.path").tmpname() + end + + -- reopen file, and set it as the output stream + cfg.output.filehandle = assert(io.open(cfg.output.filename, "wb")) + terminal.output.set_stream(cfg.output.filehandle) +end + + + -- ==================================================================================================== -- (Un)loading and patching system and terminal to enable mocks -- ==================================================================================================== @@ -134,6 +173,35 @@ end -- Patches terminal to enable mocking local function patch_terminal() + M.clear_output() + + -- disable changing the output stream + local set_stream = terminal.output.set_stream + terminal.output.set_stream = function(filehandle) + local cfg = get_config() -- the upvalue cfg might be outdated, need to get the latest one + if filehandle ~= cfg.output.filehandle then + return true + end + -- only set it if it matches the mocked filehandle + return set_stream(filehandle) + end +end + + + +-- Cleanup a config entry +local function clean_config() + local cfg = get_config() + + -- cleanup output files + if cfg.output.filehandle then + cfg.output.filehandle:close() + cfg.output.filehandle = nil + end + if cfg.output.filename then + os.remove(cfg.output.filename) + cfg.output.filename = nil + end end @@ -162,9 +230,7 @@ end -- Does not load them again. Call from Busted teardown or after_each so the -- next load() gets a fresh terminal. Idempotent. function M.unload() - _G._TEST = nil - terminal = nil - system = nil -- luacheck: ignore + -- clean up package.loaded for key, _ in pairs(package.loaded) do if key == "terminal" or key == "system" or @@ -172,6 +238,13 @@ function M.unload() package.loaded[key] = nil end end + -- cleanup any dangling stuff + if terminal then + clean_config() + end + terminal = nil + system = nil -- luacheck: ignore + _G._TEST = nil -- call twice to ensure finalization is complete collectgarbage() collectgarbage() From e7480711764b8ca37e07e28235178bf292616c28 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 14:39:29 +0100 Subject: [PATCH 5/7] test(helpers): fix unloading of submodules --- spec/helpers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers.lua b/spec/helpers.lua index 1fc84dd..a5d0967 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -234,7 +234,7 @@ function M.unload() for key, _ in pairs(package.loaded) do if key == "terminal" or key == "system" or - (type(key) == "string" and key:sub(1, 11) == "terminal.") then + (type(key) == "string" and key:match("^terminal%.")) then package.loaded[key] = nil end end From a746e0051cf230d2983ddd3a3be726c78251800a Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 15:35:29 +0100 Subject: [PATCH 6/7] test(helpers): add keyboard code lookup table --- spec/00-helpers_spec.lua | 34 ++++++++++++++++++++++++++++++++++ spec/helpers.lua | 24 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/spec/00-helpers_spec.lua b/spec/00-helpers_spec.lua index bf3334e..1672be6 100644 --- a/spec/00-helpers_spec.lua +++ b/spec/00-helpers_spec.lua @@ -164,4 +164,38 @@ describe("Spec helpers", function() end) + + + describe("keys lookup", function() + + it("is read-only", function() + helpers.load() + + assert.has_error(function() + helpers.keys.enter = "something" + end, "table is read-only") + end) + + + it("returns a raw sequence that maps back to the same keyname", function() + local terminal = helpers.load() + + local raw = helpers.keys.enter + local keyname_from_map = terminal.input.keymap.default_key_map[raw] + local keyname_from_keys = terminal.input.keymap.default_keys.enter + + assert.equals(keyname_from_keys, keyname_from_map) + end) + + + it("errors on unknown key name", function() + helpers.load() + + assert.has_error(function() + local _ = helpers.keys.this_key_does_not_exist + end, "Unknown key-name: this_key_does_not_exist") + end) + + end) + end) diff --git a/spec/helpers.lua b/spec/helpers.lua index a5d0967..7297e6a 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -158,6 +158,30 @@ end +--- A lookup table for key sequences to push into the keyboard buffer. +-- @table keys +-- @usage +-- helper._push_input(helper.keys.esc) +-- helper._push_input(helper.keys.ctrl_c) +-- helper._push_input(helper.keys.enter) +M.keys = setmetatable({}, { + __newindex = function(self, key, value) + error("table is read-only", 2) + end, + __index = function(self, key) + get_config() -- to assert the module was loaded + local keyname = terminal.input.keymap.default_keys[key] -- should error if an unknown key + for raw, name in pairs(terminal.input.keymap.default_key_map) do + if name == keyname then + return raw + end + end + error("key " .. tostring(key) .. " not found in default key map", 2) -- should be impossible to reach + end +}) + + + -- ==================================================================================================== -- (Un)loading and patching system and terminal to enable mocks -- ==================================================================================================== From ad0ed5b1324e84d89da0fa0fabb8f00a5f195f70 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Wed, 11 Feb 2026 09:37:13 +0100 Subject: [PATCH 7/7] chore(test): refactor to use new test helpers --- spec/05-scroll_stack_spec.lua | 30 ++++-------- spec/06-cursor_spec.lua | 25 +++------- spec/11-screen_spec.lua | 11 ++--- spec/13-bar_spec.lua | 6 ++- spec/14-text_panel_spec.lua | 6 ++- spec/15-key_bar_spec.lua | 6 ++- spec/17-tab_strip_spec.lua | 6 ++- spec/18-prompt_spec.lua | 87 ++++------------------------------- 8 files changed, 48 insertions(+), 129 deletions(-) diff --git a/spec/05-scroll_stack_spec.lua b/spec/05-scroll_stack_spec.lua index 917a336..95502b1 100644 --- a/spec/05-scroll_stack_spec.lua +++ b/spec/05-scroll_stack_spec.lua @@ -1,33 +1,19 @@ -describe("Scroll stack", function() +local helpers = require "spec.helpers" - local stack, scroll, old_sys_termsize - before_each(function() - _G._TEST = true +describe("Scroll stack", function() - local sys = require "system" - old_sys_termsize = sys.termsize - if os.getenv("GITHUB_ACTIONS") then - sys.termsize = function() - return 25, 80 - end - end + local terminal, stack, scroll - stack = require "terminal.scroll.stack" - scroll = require "terminal.scroll" + before_each(function() + terminal = helpers.load() + stack = terminal.scroll.stack + scroll = terminal.scroll end) after_each(function() - _G._TEST = nil - - require("system").termsize = old_sys_termsize - - for mod in pairs(package.loaded) do - if mod:match("^terminal") then - package.loaded[mod] = nil - end - end + helpers.unload() end) diff --git a/spec/06-cursor_spec.lua b/spec/06-cursor_spec.lua index a72498d..24fdf12 100644 --- a/spec/06-cursor_spec.lua +++ b/spec/06-cursor_spec.lua @@ -1,33 +1,22 @@ -describe("Cursor", function() +local helpers = require "spec.helpers" - local cursor, old_sys_termsize - before_each(function() - for mod in pairs(package.loaded) do - if mod:match("^terminal") then - package.loaded[mod] = nil - end - end +describe("Cursor", function() - local sys = require "system" - old_sys_termsize = sys.termsize - if os.getenv("GITHUB_ACTIONS") then - sys.termsize = function() - return 25, 80 - end - end + local terminal, cursor - cursor = require "terminal.cursor" + before_each(function() + terminal = helpers.load() + cursor = terminal.cursor end) after_each(function() - require("system").termsize = old_sys_termsize + helpers.unload() end) - describe("visible.set()", function() it("returns ANSI sequence for hiding the cursor", function() diff --git a/spec/11-screen_spec.lua b/spec/11-screen_spec.lua index 8be9316..085a3a5 100644 --- a/spec/11-screen_spec.lua +++ b/spec/11-screen_spec.lua @@ -1,7 +1,5 @@ -#!/usr/bin/env lua +local helpers = require "spec.helpers" ---- Tests for terminal.ui.screen module --- @module spec.11-screen_spec describe("terminal.ui.screen", function() @@ -10,15 +8,14 @@ describe("terminal.ui.screen", function() local terminal before_each(function() + terminal = helpers.load() Screen = require("terminal.ui.panel.screen") Panel = require("terminal.ui.panel") - terminal = require("terminal") end) + after_each(function() - Screen = nil - Panel = nil - terminal = nil + helpers.unload() end) diff --git a/spec/13-bar_spec.lua b/spec/13-bar_spec.lua index b11a3cb..9076b90 100644 --- a/spec/13-bar_spec.lua +++ b/spec/13-bar_spec.lua @@ -1,3 +1,6 @@ +local helpers = require "spec.helpers" + + describe("terminal.ui.panel.bar", function() local Bar @@ -5,8 +8,8 @@ describe("terminal.ui.panel.bar", function() setup(function() -- Load modules + terminal = helpers.load() Bar = require("terminal.ui.panel.bar") - terminal = require("terminal") -- Mock terminal functions for testing terminal.cursor = { @@ -29,6 +32,7 @@ describe("terminal.ui.panel.bar", function() -- Unset modules for clean test isolation Bar = nil terminal = nil -- luacheck: ignore + helpers.unload() end) diff --git a/spec/14-text_panel_spec.lua b/spec/14-text_panel_spec.lua index 0532e06..6488a40 100644 --- a/spec/14-text_panel_spec.lua +++ b/spec/14-text_panel_spec.lua @@ -1,3 +1,6 @@ +local helpers = require "spec.helpers" + + describe("terminal.ui.panel.text", function() local TextPanel @@ -5,8 +8,8 @@ describe("terminal.ui.panel.text", function() local text setup(function() + terminal = helpers.load() TextPanel = require("terminal.ui.panel.text") - terminal = require("terminal") text = require("terminal.text") -- Mock terminal functions @@ -39,6 +42,7 @@ describe("terminal.ui.panel.text", function() TextPanel = nil terminal = nil -- luacheck: ignore text = nil + helpers.unload() end) diff --git a/spec/15-key_bar_spec.lua b/spec/15-key_bar_spec.lua index 73576a9..67e8aa6 100644 --- a/spec/15-key_bar_spec.lua +++ b/spec/15-key_bar_spec.lua @@ -1,3 +1,6 @@ +local helpers = require "spec.helpers" + + describe("terminal.ui.panel.key_bar", function() local KeyBar @@ -6,8 +9,8 @@ describe("terminal.ui.panel.key_bar", function() setup(function() -- Load modules + terminal = helpers.load() KeyBar = require("terminal.ui.panel.key_bar") - terminal = require("terminal") text = require("terminal.text") -- Mock terminal functions for testing @@ -30,6 +33,7 @@ describe("terminal.ui.panel.key_bar", function() -- Unset modules for clean test isolation KeyBar = nil terminal = nil -- luacheck: ignore + helpers.unload() end) diff --git a/spec/17-tab_strip_spec.lua b/spec/17-tab_strip_spec.lua index 6b049f7..de724eb 100644 --- a/spec/17-tab_strip_spec.lua +++ b/spec/17-tab_strip_spec.lua @@ -1,3 +1,6 @@ +local helpers = require "spec.helpers" + + describe("terminal.ui.panel.tab_strip", function() local TabStrip @@ -5,8 +8,8 @@ describe("terminal.ui.panel.tab_strip", function() setup(function() -- Load modules + terminal = helpers.load() TabStrip = require("terminal.ui.panel.tab_strip") - terminal = require("terminal") -- Mock terminal functions for testing terminal.cursor = { position = { @@ -36,6 +39,7 @@ describe("terminal.ui.panel.tab_strip", function() -- Unset modules for clean test isolation TabStrip = nil terminal = nil -- luacheck: ignore + helpers.unload() end) diff --git a/spec/18-prompt_spec.lua b/spec/18-prompt_spec.lua index fd37e96..b330938 100644 --- a/spec/18-prompt_spec.lua +++ b/spec/18-prompt_spec.lua @@ -1,87 +1,18 @@ ---- Tests for terminal.cli.prompt edge cases --- Covers: empty value, cancelled/returned behavior +local helpers = require "spec.helpers" + describe("terminal.cli.prompt", function() local Prompt - local t - local old_size, old_write, old_print, old_readansi - local input_queue - local ENTER_KEY, ESC_KEY, CTRL_C_KEY - local queue_key - - setup(function() - local keymap_module = require("terminal.input.keymap") - local keys = keymap_module.default_keys - local default_key_map = keymap_module.default_key_map - - for raw_key, key_name in pairs(default_key_map) do - if key_name == keys.enter then - ENTER_KEY = raw_key - break - end - end - assert(ENTER_KEY, "Could not find Enter key in keymap") - - for raw_key, key_name in pairs(default_key_map) do - if key_name == keys.escape then - ESC_KEY = raw_key - break - end - end - assert(ESC_KEY, "Could not find Escape key in keymap") - - for raw_key, key_name in pairs(default_key_map) do - if key_name == keys.ctrl_c then - CTRL_C_KEY = raw_key - break - end - end - assert(CTRL_C_KEY, "Could not find Ctrl+C key in keymap") - - queue_key = function(key, keytype) - assert(type(key) == "string", "queue_key: 'key' must be a string, got " .. type(key)) - assert(type(keytype) == "string", "queue_key: 'keytype' must be a string, got " .. type(keytype)) - table.insert(input_queue, { key = key, keytype = keytype }) - end - end) - before_each(function() - t = require("terminal") - - -- mock terminal - old_size = t.size - t.size = function() return 24, 80 end - - old_write = t.output.write - t.output.write = function() end - - old_print = t.output.print - t.output.print = function() end - - old_readansi = t.input.readansi - t.input.readansi = function() - if #input_queue > 0 then - local entry = table.remove(input_queue, 1) - return entry.key, entry.keytype - end - return nil, "timeout" - end - - input_queue = {} + helpers.load() Prompt = require("terminal.cli.prompt") end) after_each(function() - t.size = old_size - t.output.write = old_write - t.output.print = old_print - t.input.readansi = old_readansi - - package.loaded["terminal.cli.prompt"] = nil - Prompt = nil + helpers.unload() end) @@ -121,7 +52,7 @@ describe("terminal.cli.prompt", function() } -- Queue Esc key (from keymap) - queue_key(ESC_KEY, "ansi") + helpers._push_input(helpers.keys.esc) local result, err = prompt:run() @@ -138,7 +69,7 @@ describe("terminal.cli.prompt", function() } -- Queue Ctrl+C key (from keymap) - queue_key(CTRL_C_KEY, "char") + helpers._push_input(helpers.keys.ctrl_c) local result, err = prompt:run() @@ -159,7 +90,7 @@ describe("terminal.cli.prompt", function() } -- Queue Enter key (platform-specific) - queue_key(ENTER_KEY, "char") + helpers._push_input(helpers.keys.enter) local result, err = prompt:run() @@ -175,7 +106,7 @@ describe("terminal.cli.prompt", function() } -- Queue Enter key (platform-specific) - queue_key(ENTER_KEY, "char") + helpers._push_input(helpers.keys.enter) local result, err = prompt:run() @@ -185,4 +116,4 @@ describe("terminal.cli.prompt", function() end) -end) \ No newline at end of file +end)