Version 0.4.0: mask, eine Eingabemaske mit eigener Oberfläche

This commit is contained in:
wkh
2026-10-11 22:08:38 +02:00
parent 837a73366d
commit e7a7577900
12 changed files with 592 additions and 4 deletions
+95
View File
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require_relative 'test_helper'
require 'pty'
require_relative '../lib/rdb'
class SelectionTest < RdbCase
def test_row_numeric_condition
@@ -523,3 +525,96 @@ class ActTest < RdbCase
assert_equal [%w[b]], data(out)
end
end
class MaskTest < RdbCase
def form(text, frame: false) = Rdb::Mask::Form.parse(text, frame: frame)
def type(session, *keys)
keys.each { |k| k.start_with?("\e") || k.size == 1 ? session.feed(k) : k.each_char { |c| session.feed(c) } }
end
def test_parse_fields_buttons_and_width
f = form("Name: <Name:10> < City >\n[ Save ] [Cancel]\n")
assert_equal %w[Name City], f.names
assert_equal [0, 6, 10], f.slots[0].to_a.first(3)
assert_equal 9, f.slots[1].width # the width of "< City >"
assert_equal %w[Save Cancel], f.buttons.map(&:label)
assert_equal ["Name: #{' ' * 10} #{' ' * 9}", '[ Save ] [Cancel]'], f.lines
end
def test_command_in_form_and_frame
f = form("<!echo hi!> <A>\n", frame: true)
assert_equal '│ hi │', f.lines[1]
assert_equal [1, 5], f.slots.first.to_a.first(2)
end
def test_typing_and_navigation
s = Rdb::Mask::Session.new(form("<A:5> <B:5> [ OK ] [ Cancel ]\n"))
type(s, 'one', "\t", 'two', "\x7f", "\r")
assert_equal({ 'A' => 'one', 'B' => 'tw' }, s.values)
assert_equal :button, s.kind
assert_equal :accept, s.feed("\r")
end
def test_cancel_by_button_and_escape
s = Rdb::Mask::Session.new(form("<A:5> [ Save ] [ Abbrechen ]\n"))
s.feed("\e[Z")
assert_equal :cancel, s.feed(' ')
assert_equal :cancel, Rdb::Mask::Session.new(form("<A>\n")).feed("\e")
end
def test_enter_on_last_field_accepts_without_buttons
s = Rdb::Mask::Session.new(form("<A:3> <B:3>\n"))
assert_nil s.feed("\r")
assert_equal :accept, s.feed("\r")
end
def test_same_column_twice_is_one_field
s = Rdb::Mask::Session.new(form("<A:3> <A:3>\n"))
type(s, 'xy')
assert_equal({ 'A' => 'xy' }, s.values)
assert_equal %w[A], form("<A:3> <A:3>\n").names
end
def test_show_prints_picture_without_terminal
write('f', "Name: <Name:6>\n[ Save ] [ Cancel ]\n")
out = rdb('mask', '-s', file: 'f')
assert_includes out, "Name: ______\n[ Save ] [ Cancel ]"
assert_includes out, " Name\trow 1 col 7 width 6"
assert_includes out, " [Cancel]\trow 2 col 10 (cancels)"
assert_equal out, rdb('mask', '-s', 'f') # a file name works too
end
# Drive mask on a pseudo terminal; its table goes to a file, like in a pipe.
def run_on_pty(*keys)
out = File.join(@dir, 'mask.out')
result = nil
PTY.spawn({ 'PATH' => "#{BIN}:#{ENV['PATH']}" }, 'sh', '-c', "cd #{@dir} && mask < f > mask.out") do |r, w, pid|
sleep 0.5
keys.each do |k|
w.write k
sleep 0.1
r.read_nonblock(65_536) rescue nil
end
sleep 0.3
r.read_nonblock(65_536) rescue nil
_, st = Process.wait2(pid)
result = [File.exist?(out) ? File.read(out) : '', st.exitstatus]
end
result
end
def test_mask_on_a_terminal_writes_a_table
write('f', "Name: <Name:10>\nCity: <City:10>\n[ Save ] [ Cancel ]\n")
table, status = run_on_pty("Ann\r", "Rome\r", "\r")
assert_equal 0, status
assert_equal "Name\tCity\n----\t----\nAnn\tRome\n", table
end
def test_mask_cancel_writes_nothing
write('f', "Name: <Name:10>\n[ Save ] [ Cancel ]\n")
table, status = run_on_pty("Ann", "\e[Z", "\r")
assert_equal 1, status
assert_equal '', table
end
end