gitlink-cli/internal/output/formatter_test.go

263 lines
7.8 KiB
Go

package output
import (
"bytes"
"strings"
"testing"
)
func TestPrintTable_NestedSliceRendersAsTable(t *testing.T) {
data := map[string]interface{}{
"tags": []interface{}{
map[string]interface{}{"name": "v1", "id": "a"},
map[string]interface{}{"name": "v2", "id": "b"},
},
"total_count": 2,
}
env := SuccessEnvelope(data, nil)
var buf bytes.Buffer
if err := PrintTo(&buf, env, "table"); err != nil {
t.Fatalf("PrintTo returned error: %v", err)
}
out := buf.String()
for _, want := range []string{"name", "v1", "v2", "id"} {
if !strings.Contains(out, want) {
t.Errorf("expected output to contain %q, got:\n%s", want, out)
}
}
// Must NOT start with JSON envelope.
if strings.HasPrefix(strings.TrimSpace(out), "{") {
t.Errorf("expected table output, not JSON; got:\n%s", out)
}
}
func TestPrintTable_NestedMapFallsBackToJSON(t *testing.T) {
data := map[string]interface{}{
"outer": map[string]interface{}{"inner": "value"},
}
env := SuccessEnvelope(data, nil)
var buf bytes.Buffer
if err := PrintTo(&buf, env, "table"); err != nil {
t.Fatalf("PrintTo returned error: %v", err)
}
out := buf.String()
if !strings.Contains(out, `"ok"`) {
t.Errorf("expected JSON fallback containing \"ok\"; got:\n%s", out)
}
}
func TestPrintTable_SimpleMapRendersAsMapTable(t *testing.T) {
data := map[string]interface{}{
"name": "x",
"count": 5,
}
env := SuccessEnvelope(data, nil)
var buf bytes.Buffer
if err := PrintTo(&buf, env, "table"); err != nil {
t.Fatalf("PrintTo returned error: %v", err)
}
out := buf.String()
if !strings.Contains(out, "KEY") || !strings.Contains(out, "VALUE") {
t.Errorf("expected KEY/VALUE map table; got:\n%s", out)
}
}
func TestPrintTable_ErrorEnvelope(t *testing.T) {
env := ErrorEnvelope("E_FAIL", "boom", "")
var buf bytes.Buffer
if err := PrintTo(&buf, env, "table"); err != nil {
t.Fatalf("PrintTo returned error: %v", err)
}
out := buf.String()
if !strings.Contains(out, "Error:") {
t.Errorf("expected output to contain \"Error:\"; got:\n%s", out)
}
if !strings.Contains(out, "boom") {
t.Errorf("expected output to contain error message \"boom\"; got:\n%s", out)
}
}
func TestPrintTable_PicksLargestSlice(t *testing.T) {
data := map[string]interface{}{
"small": []interface{}{
map[string]interface{}{"a": 1},
},
"big": []interface{}{
map[string]interface{}{"x": 1},
map[string]interface{}{"x": 2},
map[string]interface{}{"x": 3},
},
}
env := SuccessEnvelope(data, nil)
var buf bytes.Buffer
if err := PrintTo(&buf, env, "table"); err != nil {
t.Fatalf("PrintTo returned error: %v", err)
}
out := buf.String()
// Must not be a JSON envelope fallback.
if strings.HasPrefix(strings.TrimSpace(out), "{") {
t.Errorf("expected table output, not JSON; got:\n%s", out)
}
if !strings.Contains(out, "x") {
t.Errorf("expected output to render \"big\" list with x column; got:\n%s", out)
}
for _, want := range []string{"1", "2", "3"} {
if !strings.Contains(out, want) {
t.Errorf("expected output to contain row value %q; got:\n%s", want, out)
}
}
lines := strings.Split(strings.TrimSpace(out), "\n")
// header + dash line + 3 rows = 5 lines minimum
if len(lines) < 5 {
t.Errorf("expected at least 5 lines (header/dash/3 rows); got %d:\n%s", len(lines), out)
}
// The "a" column from the smaller slice must not appear as a header.
for _, line := range lines {
// header line is tab-separated; ensure "a" is not one of the columns.
cols := strings.Split(line, "\t")
for _, c := range cols {
if strings.TrimSpace(c) == "a" {
t.Errorf("did not expect \"a\" column from smaller slice; got line: %q", line)
}
}
}
}
// --- Readability improvements (truncation, column limits, nested-map summaries) ---
func TestFormatValue_TruncatesLongString(t *testing.T) {
long := strings.Repeat("a", 100)
got := formatValue(long)
if len(got) > 40 {
t.Errorf("expected output <= 40 chars, got %d: %q", len(got), got)
}
if !strings.HasSuffix(got, "...") {
t.Errorf("expected output to end with \"...\", got %q", got)
}
}
func TestFormatValue_ShortStringUntouched(t *testing.T) {
got := formatValue("hello")
if got != "hello" {
t.Errorf("expected short string unchanged, got %q", got)
}
}
func TestFormatValue_NestedMapShowsSummary(t *testing.T) {
m := map[string]interface{}{
"login": "alice",
"id": 123,
"image_url": "http://example.com/avatar.png",
}
got := formatValue(m)
if !strings.Contains(got, "alice") {
t.Errorf("expected summary to contain \"alice\", got %q", got)
}
if strings.Contains(got, "image_url") {
t.Errorf("expected summary to omit noisy field \"image_url\", got %q", got)
}
}
func TestFormatValue_NestedMapWithoutKnownFields(t *testing.T) {
m := map[string]interface{}{
"foo": "bar",
"baz": 42,
}
got := formatValue(m)
// No recognized key -> should be a short placeholder, not a JSON blob.
if strings.HasPrefix(got, "{") {
t.Errorf("expected placeholder for object without known fields, got JSON: %q", got)
}
}
func TestFormatValue_FloatNoScientificNotation(t *testing.T) {
got := formatValue(1.77920097e+09)
if strings.Contains(got, "e+") || strings.Contains(got, "E+") {
t.Errorf("expected float without scientific notation, got %q", got)
}
if !strings.Contains(got, "1779200970") {
t.Errorf("expected full decimal expansion, got %q", got)
}
}
func TestPrintSliceTable_LimitsColumns(t *testing.T) {
// Build a single row with 12 columns.
row := map[string]interface{}{}
cols := []string{"id", "name", "login", "title", "subject", "number",
"status", "state", "extra1", "extra2", "extra3", "extra4"}
for _, c := range cols {
row[c] = "v_" + c
}
var buf bytes.Buffer
if err := printSliceTable(&buf, []interface{}{row}); err != nil {
t.Fatalf("printSliceTable returned error: %v", err)
}
out := buf.String()
// The header line is the first non-empty line.
lines := strings.Split(out, "\n")
var header string
for _, l := range lines {
if strings.TrimSpace(l) != "" {
header = l
break
}
}
colCount := len(strings.Split(header, "\t"))
if colCount > 8 {
t.Errorf("expected <= 8 columns in header, got %d:\n%s", colCount, header)
}
// And the output should note that columns were omitted.
if !strings.Contains(out, "省略") && !strings.Contains(strings.ToLower(out), "omitted") {
t.Errorf("expected output to mention omitted columns (省略/omitted), got:\n%s", out)
}
}
func TestPrintSliceTable_PreservesPriorityHeaders(t *testing.T) {
// Even with >8 columns, priority fields like id/name should survive.
row := map[string]interface{}{}
cols := []string{"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "id", "name"}
for _, c := range cols {
row[c] = "v"
}
var buf bytes.Buffer
if err := printSliceTable(&buf, []interface{}{row}); err != nil {
t.Fatalf("printSliceTable returned error: %v", err)
}
out := buf.String()
if !strings.Contains(out, "id") || !strings.Contains(out, "name") {
t.Errorf("expected priority headers id/name to survive; got:\n%s", out)
}
}
func TestTruncateString(t *testing.T) {
if got := truncateString("abc", 10); got != "abc" {
t.Errorf("expected short string unchanged, got %q", got)
}
got := truncateString("abcdefghij", 5)
if len(got) > 5 {
t.Errorf("expected len <= 5, got %d: %q", len(got), got)
}
if !strings.HasSuffix(got, "...") {
t.Errorf("expected ... suffix, got %q", got)
}
}
func TestFormatNestedMap_LoginAndSha(t *testing.T) {
m := map[string]interface{}{
"login": "bob",
"sha": "0123456789abcdef0123456789abcdef",
}
got := formatNestedMap(m)
if !strings.Contains(got, "bob") {
t.Errorf("expected login in summary, got %q", got)
}
if !strings.Contains(got, "0123456") {
t.Errorf("expected truncated sha prefix in summary, got %q", got)
}
// Should not contain the full sha.
if strings.Contains(got, "0123456789abcdef0123456789abcdef") {
t.Errorf("expected sha to be truncated, got %q", got)
}
}