package stario import ( "bufio" "errors" "fmt" "strings" "testing" ) 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 }) } 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) } } 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) } } 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) } } } 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") } } 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) } 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)) }