diff options
| author | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 17:48:35 +0200 |
|---|---|---|
| committer | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 17:48:35 +0200 |
| commit | fae69149ec18b2f75e124976623fc99ee3cb090a (patch) | |
| tree | 8ece3e3b6b38482eb8b53828b0af319215efd2a9 /tests | |
| parent | ae503e9498553d50cc145d26f4b3457276fc14ba (diff) | |
Add public mode and encoding matrices
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/phase16.lua | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/tests/phase16.lua b/tests/phase16.lua new file mode 100644 index 0000000..d285d28 --- /dev/null +++ b/tests/phase16.lua @@ -0,0 +1,234 @@ +local script = debug.getinfo(1, "S").source:sub(2) +local root = script:match("^(.*)/tests/phase16%.lua$") or "." +package.path = table.concat({ + root .. "/lua/?.lua", + root .. "/lua/?/init.lua", + package.path, +}, ";") + +local action_facade = require("clever_f.action_facade") +local domain = require("clever_f.domain") +local sequence_state = require("clever_f.sequence_state") +local state_transitions = require("clever_f.state_transitions") +local text_topology = require("clever_f.text_topology") +local MemoryHost = require("clever_f.testing.memory_host") + +local assertions = dofile(root .. "/tests/assertions.lua") +local same = assertions.same +local truthy = assertions.truthy + +local tests = {} +local passed = 0 + +local function test(name, body) + tests[#tests + 1] = { name = name, body = body } +end + +local function fresh_state() + local transitions = state_transitions.new() + transitions:ClearTemporaryOverlays() + transitions:DiagnosticFullReset() + return sequence_state.get() +end + +local function motion_host(options) + options.configuration = vim.tbl_extend("force", { + mark_char = false, + mark_cursor = false, + mark_direct = false, + show_prompt = false, + }, options.configuration or {}) + options.emit_movement_events = false + local host = MemoryHost.new(options) + return host, action_facade.new({ host = host }) +end + +test("Public actions execute in Normal and every Visual context", function() + local cases = { + { mode = "n" }, + { mode = "v", kind = domain.SelectionKind.CHARACTER }, + { mode = "V", kind = domain.SelectionKind.LINE }, + { mode = string.char(0x16), kind = domain.SelectionKind.BLOCK }, + } + + for _, case in ipairs(cases) do + local state = fresh_state() + local origin = domain.Position.new(1, 1) + local active_selection = case.kind and domain.Selection.active( + case.kind, + origin, + origin, + domain.SelectionOption.INCLUSIVE + ) or nil + local host, facade = motion_host({ + buffer_lines = { "axaxa" }, + cursor = origin, + mode = case.mode, + selection = active_selection, + count = 2, + input_packets = { { kind = "text", text = "x" } }, + }) + + local outcome = facade:start_find_forward() + same(domain.ActionKind.MOVEMENT, outcome.kind, case.mode) + same(domain.Position.new(1, 4), outcome.position, case.mode) + same(domain.Position.new(1, 4), host:read_cursor(), case.mode) + same(domain.Position.new(1, 4), state:get_previous_landing(case.mode), case.mode) + if case.kind ~= nil then + local selected = host:read_selection() + truthy(selected.active, case.mode) + same(case.kind, selected.kind, case.mode) + same(origin, selected.anchor, case.mode) + same(domain.Position.new(1, 4), selected.focus, case.mode) + end + end +end) + +test("Visual selection options apply only their specified endpoint adjustment", function() + local modes = { + { mode = "v", kind = domain.SelectionKind.CHARACTER, adjusted = true }, + { mode = "V", kind = domain.SelectionKind.LINE, adjusted = true }, + { mode = string.char(0x16), kind = domain.SelectionKind.BLOCK, adjusted = false }, + } + for _, option in ipairs({ + domain.SelectionOption.INCLUSIVE, + domain.SelectionOption.EXCLUSIVE, + }) do + for _, case in ipairs(modes) do + fresh_state() + local origin = domain.Position.new(1, 1) + local host, facade = motion_host({ + buffer_lines = { "axz" }, + cursor = origin, + mode = case.mode, + selection = domain.Selection.active( + case.kind, + origin, + origin, + option + ), + input_packets = { { kind = "text", text = "x" } }, + }) + + local outcome = facade:start_find_forward() + local expected_column = option == domain.SelectionOption.EXCLUSIVE + and case.adjusted + and 3 + or 2 + same(domain.ActionKind.MOVEMENT, outcome.kind, case.mode .. option.value) + same(expected_column, outcome.position.byte_column, case.mode .. option.value) + same(option, host:read_selection().option, case.mode .. option.value) + end + end +end) + +test("Public actions execute in every Select context", function() + local cases = { + { mode = "s", kind = domain.SelectionKind.CHARACTER }, + { mode = "S", kind = domain.SelectionKind.LINE }, + { mode = string.char(0x13), kind = domain.SelectionKind.BLOCK }, + } + + for _, case in ipairs(cases) do + local state = fresh_state() + local origin = domain.Position.new(1, 1) + local host, facade = motion_host({ + buffer_lines = { "axaxa" }, + cursor = origin, + mode = case.mode, + selection = domain.Selection.active( + case.kind, + origin, + origin, + domain.SelectionOption.INCLUSIVE + ), + count = 2, + input_packets = { { kind = "text", text = "x" } }, + }) + + local outcome = facade:start_find_forward() + same(domain.ActionKind.MOVEMENT, outcome.kind, case.mode) + same(domain.Position.new(1, 4), outcome.position, case.mode) + same(domain.Position.new(1, 4), host:read_cursor(), case.mode) + same(domain.Position.new(1, 4), state:get_previous_landing(case.mode), case.mode) + same(domain.Descriptor.FIND_FORWARD, state:get_previous_descriptor(case.mode), case.mode) + end +end) + +test("Public actions keep byte columns valid in every supported encoding", function() + local multibyte = "\227\129\130" + local cases = { + { encoding = "utf-8", final_column = 5 }, + { encoding = "cp932", final_column = 4 }, + { encoding = "euc-jp", final_column = 4 }, + } + + for _, case in ipairs(cases) do + fresh_state() + local host, facade = motion_host({ + buffer_lines = { "a" .. multibyte .. "a" }, + effective_encoding = case.encoding, + cursor = { line = 1, byte_column = 1 }, + input_packets = { { kind = "text", text = "a" } }, + }) + local forward = facade:start_find_forward() + same(domain.ActionKind.MOVEMENT, forward.kind, case.encoding) + same(case.final_column, forward.position.byte_column, case.encoding) + truthy( + text_topology.from_host(host):is_character_start(forward.position), + case.encoding + ) + + fresh_state() + host, facade = motion_host({ + buffer_lines = { "a" .. multibyte .. "a" }, + effective_encoding = case.encoding, + cursor = { line = 1, byte_column = case.final_column }, + input_packets = { { kind = "text", text = "a" } }, + }) + local backward = facade:start_find_backward() + same(domain.ActionKind.MOVEMENT, backward.kind, case.encoding) + same(domain.Position.new(1, 1), backward.position, case.encoding) + truthy( + text_topology.from_host(host):is_character_start(backward.position), + case.encoding + ) + end +end) + +test("Public actions share one record across operator-pending variants", function() + local variants = { + "no", + "nov", + "noV", + "no" .. string.char(0x16), + } + + for _, mode in ipairs(variants) do + local state = fresh_state() + local host, facade = motion_host({ + buffer_lines = { "axaxa" }, + cursor = { line = 1, byte_column = 1 }, + mode = mode, + pending_operator = "delete", + input_packets = { { kind = "text", text = "x" } }, + }) + + local outcome = facade:start_find_forward() + same(domain.ActionKind.MOVEMENT, outcome.kind, mode) + same("axa", host:read_text():line(1), mode) + same(domain.Descriptor.FIND_FORWARD, state:get_previous_descriptor("no"), mode) + same(domain.TargetValue.character("x", string.byte("x")), state:get_previous_target("no"), mode) + end +end) + +for _, item in ipairs(tests) do + local ok, failure = xpcall(item.body, debug.traceback) + if not ok then + io.stderr:write("FAIL: " .. item.name .. "\n" .. tostring(failure) .. "\n") + os.exit(1) + end + passed = passed + 1 +end + +io.stdout:write(string.format("Phase 16 matrices: %d tests passed\n", passed)) |
