2022-03-18 13:49:38 +08:00
|
|
|
package stario
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-15 14:35:19 +08:00
|
|
|
"bufio"
|
|
|
|
|
"errors"
|
2022-03-18 13:49:38 +08:00
|
|
|
"fmt"
|
2026-04-15 14:35:19 +08:00
|
|
|
"strings"
|
2022-03-18 13:49:38 +08:00
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-15 14:35:19 +08:00
|
|
|
func installRawInputStub(t *testing.T, input string, signalErr error) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
prevFactory := rawTerminalSessionFactory
|
|
|
|
|
prevSignalHandler := inputSignalHandler
|
|
|
|
|
rawTerminalSessionFactory = func(hint string, printNewline bool) (*rawTerminalSession, error) {
|
|
|
|
|
return &rawTerminalSession{
|
|
|
|
|
reader: bufio.NewReader(strings.NewReader(input)),
|
|
|
|
|
redrawHint: promptRedrawHint(hint),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
inputSignalHandler = func(sigtype rune) error {
|
|
|
|
|
return signalErr
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
rawTerminalSessionFactory = prevFactory
|
|
|
|
|
inputSignalHandler = prevSignalHandler
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 19:15:43 +08:00
|
|
|
func TestPromptRedrawHint(t *testing.T) {
|
|
|
|
|
got := promptRedrawHint("头部提示\n 中文确认: ")
|
|
|
|
|
if got != "中文确认:" {
|
|
|
|
|
t.Fatalf("unexpected redraw hint: got %q", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStringDisplayWidth(t *testing.T) {
|
|
|
|
|
got := stringDisplayWidth("中a[]")
|
|
|
|
|
if got != 5 {
|
|
|
|
|
t.Fatalf("unexpected display width: got %d want 5", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:35:19 +08:00
|
|
|
func TestRawEchoRenderUnitPlainRune(t *testing.T) {
|
|
|
|
|
text, width, ok := rawEchoRenderUnit('中', "", 0)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatal("expected plain wide rune to use fast path")
|
|
|
|
|
}
|
|
|
|
|
if text != "中" || width != 2 {
|
|
|
|
|
t.Fatalf("unexpected render unit: got (%q, %d) want (%q, %d)", text, width, "中", 2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRawEchoRenderUnitMaskedRune(t *testing.T) {
|
|
|
|
|
text, width, ok := rawEchoRenderUnit('a', "[]", stringDisplayWidth("[]"))
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatal("expected masked rune to use fast path")
|
|
|
|
|
}
|
|
|
|
|
if text != "[]" || width != 2 {
|
|
|
|
|
t.Fatalf("unexpected render unit: got (%q, %d) want (%q, %d)", text, width, "[]", 2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRawEchoRenderUnitFallsBackForControlRune(t *testing.T) {
|
|
|
|
|
if _, _, ok := rawEchoRenderUnit('\x00', "", 0); ok {
|
|
|
|
|
t.Fatal("expected control rune to fall back to full redraw")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSignalInputResultExitSuppressesError(t *testing.T) {
|
|
|
|
|
got := signalInputResult(rawInputSignalExit, ErrSignalInterrupt)
|
|
|
|
|
if got.err != nil {
|
|
|
|
|
t.Fatalf("expected nil error, got %v", got.err)
|
|
|
|
|
}
|
|
|
|
|
if got.msg != "" {
|
|
|
|
|
t.Fatalf("expected empty message, got %q", got.msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSignalInputResultReturnErrorPreservesSignal(t *testing.T) {
|
|
|
|
|
got := signalInputResult(rawInputSignalReturnError, ErrSignalInterrupt)
|
|
|
|
|
if !errors.Is(got.err, ErrSignalInterrupt) {
|
|
|
|
|
t.Fatalf("expected signal error, got %v", got.err)
|
|
|
|
|
}
|
|
|
|
|
if got.msg != "" {
|
|
|
|
|
t.Fatalf("expected empty message, got %q", got.msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPasswdSuppressesSignalError(t *testing.T) {
|
|
|
|
|
installRawInputStub(t, string([]rune{0x03}), ErrSignalInterrupt)
|
|
|
|
|
got := Passwd("", "fallback")
|
|
|
|
|
if got.err != nil {
|
|
|
|
|
t.Fatalf("expected nil error, got %v", got.err)
|
|
|
|
|
}
|
|
|
|
|
if got.msg != "" {
|
|
|
|
|
t.Fatalf("expected empty message after signal exit, got %q", got.msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPasswdResponseSignalPreservesSignalError(t *testing.T) {
|
|
|
|
|
installRawInputStub(t, string([]rune{0x03}), ErrSignalInterrupt)
|
|
|
|
|
got := PasswdResponseSignal("", "fallback")
|
|
|
|
|
if !errors.Is(got.err, ErrSignalInterrupt) {
|
|
|
|
|
t.Fatalf("expected interrupt error, got %v", got.err)
|
|
|
|
|
}
|
|
|
|
|
if got.msg != "" {
|
|
|
|
|
t.Fatalf("expected empty message after signal exit, got %q", got.msg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStopUntilEmptyTriggerReturnsAfterFirstKey(t *testing.T) {
|
|
|
|
|
installRawInputStub(t, "abc", nil)
|
|
|
|
|
if err := StopUntil("", "", false); err != nil {
|
|
|
|
|
t.Fatalf("StopUntil returned error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 19:15:43 +08:00
|
|
|
func TestParseYesNoValue(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
input string
|
|
|
|
|
defaults bool
|
|
|
|
|
want bool
|
|
|
|
|
ok bool
|
|
|
|
|
}{
|
|
|
|
|
{name: "default", input: " ", defaults: true, want: true, ok: true},
|
|
|
|
|
{name: "yes", input: "yes", defaults: false, want: true, ok: true},
|
|
|
|
|
{name: "no", input: "No", defaults: true, want: false, ok: true},
|
|
|
|
|
{name: "invalid", input: "maybe", defaults: false, want: false, ok: false},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
got, ok := parseYesNoValue(tc.input, tc.defaults)
|
|
|
|
|
if got != tc.want || ok != tc.ok {
|
|
|
|
|
t.Fatalf("%s: got (%v, %v) want (%v, %v)", tc.name, got, ok, tc.want, tc.ok)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:35:19 +08:00
|
|
|
func TestSliceFloat32(t *testing.T) {
|
|
|
|
|
data := InputMsg{msg: "1.5,2.25", err: nil}
|
|
|
|
|
got, err := data.SliceFloat32(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SliceFloat32 returned error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(got) != 2 || got[0] != float32(1.5) || got[1] != float32(2.25) {
|
|
|
|
|
t.Fatalf("unexpected float32 slice: %#v", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTypedSliceParsingTrimsTokenWhitespace(t *testing.T) {
|
|
|
|
|
ints, err := (InputMsg{msg: "1, 2, 3"}).SliceInt(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SliceInt returned error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(ints) != 3 || ints[0] != 1 || ints[1] != 2 || ints[2] != 3 {
|
|
|
|
|
t.Fatalf("unexpected int slice: %#v", ints)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bools, err := (InputMsg{msg: "true, false, true"}).SliceBool(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SliceBool returned error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(bools) != 3 || !bools[0] || bools[1] || !bools[2] {
|
|
|
|
|
t.Fatalf("unexpected bool slice: %#v", bools)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float64s, err := (InputMsg{msg: "1.25, 2.5, 3.75"}).SliceFloat64(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("SliceFloat64 returned error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(float64s) != 3 || float64s[0] != 1.25 || float64s[1] != 2.5 || float64s[2] != 3.75 {
|
|
|
|
|
t.Fatalf("unexpected float64 slice: %#v", float64s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAdvanceTriggerIndexHandlesOverlap(t *testing.T) {
|
|
|
|
|
trigger := []rune("aba")
|
|
|
|
|
prefix := buildTriggerPrefixTable(trigger)
|
|
|
|
|
index := 0
|
|
|
|
|
complete := false
|
|
|
|
|
for _, r := range []rune("aaba") {
|
|
|
|
|
index, complete = advanceTriggerIndex(trigger, prefix, index, r)
|
|
|
|
|
if complete {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !complete {
|
|
|
|
|
t.Fatal("expected overlapped trigger to complete")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-18 13:49:38 +08:00
|
|
|
func Test_Slice(t *testing.T) {
|
|
|
|
|
var data = InputMsg{
|
|
|
|
|
msg: "true,false,true,true,false,0,1,hello",
|
|
|
|
|
err: nil,
|
|
|
|
|
skipSliceSigErr: false,
|
|
|
|
|
}
|
|
|
|
|
res, err := data.IgnoreSliceParseError(true).SliceBool(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(res)
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(res) == 0 {
|
|
|
|
|
t.Fatal(res)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println(res)
|
|
|
|
|
}
|
2024-08-30 14:51:51 +08:00
|
|
|
|
|
|
|
|
func TestSliceMsg(t *testing.T) {
|
|
|
|
|
var data = InputMsg{
|
|
|
|
|
msg: "",
|
|
|
|
|
err: nil,
|
|
|
|
|
skipSliceSigErr: false,
|
|
|
|
|
}
|
|
|
|
|
res, err := data.SliceString(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(res)
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(res) != 0 {
|
|
|
|
|
t.Fatal(res)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println(len(res))
|
|
|
|
|
res2, err := data.SliceInt64(",")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fmt.Println(res2)
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(res2) != 0 {
|
|
|
|
|
t.Fatal(res2)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println(len(res2))
|
|
|
|
|
}
|