summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:50:32 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:50:32 +0200
commit03757a7355bbad885a9f3c86115f0374dd2cb854 (patch)
tree393b9500fe95fe83e96776c946dbb87936f693fa /tests
parentaa2c42761a91e8eca2d551fe14f1bc1dd3fc8593 (diff)
Complete Nvim adapter contract
Diffstat (limited to 'tests')
-rw-r--r--tests/host_adapter_contract.lua179
-rw-r--r--tests/plugin_smoke.lua104
-rw-r--r--tests/run.lua2
3 files changed, 284 insertions, 1 deletions
diff --git a/tests/host_adapter_contract.lua b/tests/host_adapter_contract.lua
new file mode 100644
index 0000000..ff369d5
--- /dev/null
+++ b/tests/host_adapter_contract.lua
@@ -0,0 +1,179 @@
+local script = debug.getinfo(1, "S").source:sub(2)
+local root = script:match("^(.*)/tests/host_adapter_contract%.lua$") or "."
+package.path = table.concat({
+ root .. "/lua/?.lua",
+ root .. "/lua/?/init.lua",
+ package.path,
+}, ";")
+
+local capabilities = require("clever_f.capabilities")
+local composition_root = require("clever_f.composition_root")
+local domain = require("clever_f.domain")
+local host_adapter = require("clever_f.host_adapter")
+
+local function same(expected, actual, message)
+ if expected ~= actual then
+ error((message or "values differ")
+ .. ": expected " .. tostring(expected)
+ .. ", got " .. tostring(actual), 2)
+ end
+end
+
+local function truthy(value, message)
+ if not value then
+ error(message or "value must be true", 2)
+ end
+end
+
+local exercised = {}
+local host = host_adapter.new()
+capabilities.assert_implements(host)
+
+local function call(name, ...)
+ exercised[name] = true
+ return host[name](host, ...)
+end
+
+vim.api.nvim_buf_set_lines(0, 0, -1, true, { "ababa" })
+vim.api.nvim_win_set_cursor(0, { 1, 0 })
+
+same("ababa", call("read_text"):line(1))
+same(vim.api.nvim_get_current_buf(), call("read_buffer"))
+same(vim.api.nvim_get_current_win(), call("read_window"))
+same(domain.Position.new(1, 1), call("read_cursor"))
+same("n", call("read_mode"))
+truthy(not call("read_selection").active)
+same(1, call("read_count").value)
+
+local ignore_case_global = host_adapter.configuration_global("ignore_case")
+vim.g[ignore_case_global] = 0
+truthy(call("configuration_present", "ignore_case"))
+same(false, call("read_configuration", "ignore_case"))
+call("write_configuration", "ignore_case", true)
+same(true, call("read_configuration", "ignore_case"))
+same("utf-8", call("read_encoding"))
+same(vim.fn.tolower("AbC"), call("lowercase", "AbC"))
+truthy(not call("read_macro_state").executing)
+truthy(domain.FoldState.is(call("read_fold_state")))
+truthy(call("read_time_ms") > 0)
+
+same("", call("read_pending_operator"))
+call("apply_cursor", domain.Position.new(1, 2))
+same(domain.Position.new(1, 2), call("read_cursor"))
+call("apply_selection", domain.Position.new(1, 3), domain.SelectionKind.CHARACTER)
+same(domain.Position.new(1, 3), call("read_cursor"))
+call("set_operator_inclusive", false)
+
+vim.api.nvim_feedkeys("z", "n", false)
+local packet = call("read_input")
+same(domain.InputPacketKind.TEXT, packet.kind)
+same("z", packet.text)
+same(false, call("open_fold", domain.Position.new(1, 3)))
+call("show_prompt", "clever-f: ")
+call("redraw", "screen")
+call("redraw", "full")
+same(false, call("redraw", "suppressed"))
+
+local notifications = {}
+vim.notify = function(text, level, options)
+ notifications[#notifications + 1] = {
+ text = text,
+ level = level,
+ title = options.title,
+ }
+end
+call("emit_diagnostic", "info", "adapter contract")
+same("adapter contract", notifications[1].text)
+same("clever-f", notifications[1].title)
+
+call("define_highlight_group", "CleverFContract", {
+ guifg = "red",
+ guibg = "NONE",
+ gui = { bold = true },
+}, { force = true })
+truthy(call("read_highlight_group", "CleverFContract") ~= nil)
+local highlight = call("create_highlight", {
+ group = "CleverFContract",
+ window = call("read_window"),
+ positions = { domain.Position.new(1, 1), domain.Position.new(1, 3) },
+ priority = "high",
+})
+truthy(call("remove_highlight", highlight))
+
+truthy(call("supports_timers"))
+local timer = call("start_timer", 100000, function() end)
+truthy(timer ~= nil)
+truthy(call("stop_timer", timer))
+
+local prior_guicursor = vim.o.guicursor
+local prior_terminal_cursor = vim.fn.eval("&t_ve")
+local presentation_supported = call("supports_cursor_presentation")
+local lease = call("suppress_cursor_presentation")
+if presentation_supported then
+ truthy(lease ~= nil)
+ truthy(call("restore_cursor_presentation", lease))
+ same(prior_guicursor, vim.o.guicursor)
+ same(prior_terminal_cursor, vim.fn.eval("&t_ve"))
+else
+ same(nil, lease)
+ same(false, call("restore_cursor_presentation", lease))
+end
+
+local delivered
+local registration = call("register_events", "CursorMoved", function(name, payload)
+ delivered = { name = name, payload = payload }
+end)
+local transition = call("begin_action_transition")
+call("deliver_event", "CursorMoved", {
+ buffer = call("read_buffer"),
+ window = call("read_window"),
+})
+same(nil, delivered)
+call("commit_action_transition", transition)
+same("CursorMoved", delivered.name)
+truthy(call("remove_event_registration", registration))
+
+local neutral_position = call("read_cursor")
+call("register_action", "ContractNeutral", function()
+ return domain.ActionOutcome.neutral(neutral_position)
+end)
+local mapping = call(
+ "register_mapping",
+ { "n" },
+ "<Plug>(CleverFContract)",
+ "ContractNeutral",
+ { silent = true, remap = false, preserve_count = true }
+)
+truthy(mapping ~= nil)
+local mapping_info = vim.fn.maparg("<Plug>(CleverFContract)", "n", false, true)
+same(1, mapping_info.silent)
+same(1, mapping_info.noremap)
+
+local target = domain.TargetValue.character("a", string.byte("a"))
+local payload = domain.DotPayload.new("f", target)
+same(payload, call("register_dot_repeat", payload, function()
+ return domain.ActionOutcome.neutral(neutral_position)
+end))
+
+for _, method_name in ipairs(capabilities.required_methods()) do
+ truthy(exercised[method_name], "adapter capability was not exercised: " .. method_name)
+end
+
+local suppression_global = host_adapter.configuration_global(
+ "suppress_default_mappings"
+)
+same("clever_f_not_overwrites_standard_mappings", suppression_global)
+for _, value in ipairs({ false, 0 }) do
+ vim.g[suppression_global] = value
+ local suppressed = composition_root.new({
+ host = host_adapter.new(),
+ }):activate()
+ same(false, suppressed.setup.install_default_mappings)
+ same(nil, next(suppressed.mappings))
+end
+vim.g[suppression_global] = nil
+
+io.stdout:write(string.format(
+ "Phase 15 adapter contract: %d capabilities passed\n",
+ #capabilities.required_methods()
+))
diff --git a/tests/plugin_smoke.lua b/tests/plugin_smoke.lua
new file mode 100644
index 0000000..3028723
--- /dev/null
+++ b/tests/plugin_smoke.lua
@@ -0,0 +1,104 @@
+local script = debug.getinfo(1, "S").source:sub(2)
+local root = script:match("^(.*)/tests/plugin_smoke%.lua$") or "."
+vim.opt.runtimepath:prepend(root)
+
+local function same(expected, actual, message)
+ if expected ~= actual then
+ error((message or "values differ")
+ .. ": expected " .. tostring(expected)
+ .. ", got " .. tostring(actual), 2)
+ end
+end
+
+local function truthy(value, message)
+ if not value then
+ error(message or "value must be true", 2)
+ end
+end
+
+local function feed(keys)
+ vim.api.nvim_feedkeys(vim.keycode(keys), "xt", false)
+ vim.cmd("redraw")
+end
+
+vim.g.clever_f_mark_cursor = 0
+vim.g.clever_f_mark_char = 0
+vim.g.clever_f_mark_direct = 0
+vim.g.clever_f_hide_cursor_on_cmdline = 0
+vim.g.clever_f_clean_labels_eagerly = 0
+vim.api.nvim_buf_set_lines(0, 0, -1, true, { "ababa" })
+vim.api.nvim_win_set_cursor(0, { 1, 0 })
+
+vim.cmd.runtime("plugin/clever_f.lua")
+same(1, vim.g.loaded_clever_f_lua)
+local clever_f = require("clever_f")
+local first_root = clever_f.root()
+truthy(first_root ~= nil)
+
+for _, mode in ipairs({ "n", "x", "o" }) do
+ for _, lhs in ipairs({ "f", "F", "t", "T" }) do
+ local mapping = vim.fn.maparg(lhs, mode, false, true)
+ same(1, mapping.silent, mode .. lhs)
+ same(1, mapping.noremap, mode .. lhs)
+ end
+end
+
+feed("2fb")
+same(1, vim.api.nvim_win_get_cursor(0)[1])
+same(3, vim.api.nvim_win_get_cursor(0)[2])
+same(
+ 4,
+ clever_f.state():to_table().contexts.n.previous_landing.byte_column
+)
+clever_f.Reset()
+
+vim.api.nvim_buf_set_lines(0, 0, -1, true, { "ababa" })
+vim.api.nvim_win_set_cursor(0, { 1, 0 })
+feed("vfb")
+same("v", vim.api.nvim_get_mode().mode)
+same(1, vim.api.nvim_win_get_cursor(0)[2])
+feed("<Esc>")
+clever_f.Reset()
+
+vim.api.nvim_buf_set_lines(0, 0, -1, true, { "hoge fuge piye poye" })
+vim.api.nvim_win_set_cursor(0, { 1, 0 })
+feed("dfe")
+same(" fuge piye poye", vim.api.nvim_get_current_line())
+truthy(clever_f.root():host():dot_repeat_payload() ~= nil)
+feed(".")
+same(" piye poye", vim.api.nvim_get_current_line())
+feed(".")
+same(" poye", vim.api.nvim_get_current_line())
+feed(".")
+same("", vim.api.nvim_get_current_line())
+
+local ok, diagnostic = pcall(clever_f.invoke_descriptor, "X")
+same(false, ok)
+same("clever-f: Invalid mapping 'X'", diagnostic)
+
+vim.g.clever_f_mark_char = 1
+vim.g.clever_f_mark_char_color = "Search"
+vim.api.nvim_exec_autocmds("ColorScheme", {})
+same(
+ "Search",
+ vim.api.nvim_get_hl(0, { name = "CleverFChar", link = true }).link
+)
+
+for _, name in ipairs({
+ "StartFindForward",
+ "StartFindBackward",
+ "StartTillForward",
+ "StartTillBackward",
+ "Reset",
+ "RepeatSameDirection",
+ "RepeatOppositeDirection",
+}) do
+ same("function", type(clever_f[name]), name)
+end
+same("function", type(clever_f.invoke_descriptor))
+same("function", type(clever_f._diagnostic_full_reset))
+
+vim.cmd.runtime("plugin/clever_f.lua")
+same(first_root, clever_f.root())
+
+io.stdout:write("Phase 15 plugin smoke: startup, mappings, actions, and dot passed\n")
diff --git a/tests/run.lua b/tests/run.lua
index fb84dd3..03aa725 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -8756,4 +8756,4 @@ for _, item in ipairs(tests) do
passed = passed + 1
end
-io.stdout:write(string.format("Phase 14: %d tests passed\n", passed))
+io.stdout:write(string.format("Phase 15 core: %d tests passed\n", passed))