summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/phase16.lua234
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))