From 0a477fd288d064d265395c88dbd0f261b723804e Mon Sep 17 00:00:00 2001 From: spiperac Date: Mon, 15 Jun 2026 06:42:13 +0200 Subject: Initial release --- internal/ui/widgets/tokeniser_test.go | 213 ++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 internal/ui/widgets/tokeniser_test.go (limited to 'internal/ui/widgets/tokeniser_test.go') diff --git a/internal/ui/widgets/tokeniser_test.go b/internal/ui/widgets/tokeniser_test.go new file mode 100644 index 0000000..11c4d63 --- /dev/null +++ b/internal/ui/widgets/tokeniser_test.go @@ -0,0 +1,213 @@ +package widgets + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ── detectContentType ──────────────────────────────────────────────────────── + +func TestDetectContentType_JSON(t *testing.T) { + lines := []string{"POST /foo HTTP/1.1", "Content-Type: application/json", ""} + assert.Equal(t, "json", detectContentType(lines)) +} + +func TestDetectContentType_HTML(t *testing.T) { + lines := []string{"GET /foo HTTP/1.1", "Content-Type: text/html; charset=utf-8", ""} + assert.Equal(t, "html", detectContentType(lines)) +} + +func TestDetectContentType_Plain(t *testing.T) { + lines := []string{"GET /foo HTTP/1.1", "Content-Type: text/plain", ""} + assert.Equal(t, "text", detectContentType(lines)) +} + +func TestDetectContentType_Missing(t *testing.T) { + lines := []string{"GET /foo HTTP/1.1", "Accept: */*", ""} + assert.Equal(t, "text", detectContentType(lines)) +} + +func TestDetectContentType_AfterBlankLineIgnored(t *testing.T) { + lines := []string{"GET /foo HTTP/1.1", "", "Content-Type: application/json"} + assert.Equal(t, "text", detectContentType(lines)) +} + +func TestDetectContentType_CaseInsensitive(t *testing.T) { + lines := []string{"GET /foo HTTP/1.1", "content-type: APPLICATION/JSON", ""} + assert.Equal(t, "json", detectContentType(lines)) +} + +// ── tokeniseFirstLine ──────────────────────────────────────────────────────── + +func TestTokeniseFirstLine_Request(t *testing.T) { + tokens := tokeniseFirstLine("GET /search?q=foo HTTP/1.1") + + require.Len(t, tokens, 3) + assert.Equal(t, tvKindMethod, tokens[0].Kind) + assert.Equal(t, tvKindPath, tokens[1].Kind) + assert.Equal(t, tvKindVersion, tokens[2].Kind) +} + +func TestTokeniseFirstLine_ResponseStatusKinds(t *testing.T) { + tests := []struct { + line string + wantKind tvTokenKind + }{ + {"HTTP/1.1 200 OK", tvKindStatus2xx}, + {"HTTP/1.1 201 Created", tvKindStatus2xx}, + {"HTTP/1.1 301 Moved Permanently", tvKindStatus3xx}, + {"HTTP/1.1 404 Not Found", tvKindStatus4xx}, + {"HTTP/1.1 500 Internal Server Error", tvKindStatus5xx}, + } + + for _, tt := range tests { + t.Run(tt.line, func(t *testing.T) { + tokens := tokeniseFirstLine(tt.line) + require.GreaterOrEqual(t, len(tokens), 2) + assert.Equal(t, tt.wantKind, tokens[1].Kind) + }) + } +} + +// ── tokeniseHTTPMeta ───────────────────────────────────────────────────────── + +func TestTokeniseHTTPMeta_Header(t *testing.T) { + tokens := tokeniseHTTPMeta("Content-Type: application/json", false) + + require.Len(t, tokens, 3) + assert.Equal(t, tvKindHdrName, tokens[0].Kind) + assert.Equal(t, "Content-Type", tokens[0].Text) + assert.Equal(t, tvKindHdrColon, tokens[1].Kind) + assert.Equal(t, tvKindHdrValue, tokens[2].Kind) +} + +func TestTokeniseHTTPMeta_EmptyLine(t *testing.T) { + tokens := tokeniseHTTPMeta("", false) + + require.Len(t, tokens, 1) + assert.Equal(t, tvKindPlain, tokens[0].Kind) + assert.Equal(t, "", tokens[0].Text) +} + +func TestTokeniseHTTPMeta_NoColon(t *testing.T) { + tokens := tokeniseHTTPMeta("plaintext", false) + + require.Len(t, tokens, 1) + assert.Equal(t, tvKindPlain, tokens[0].Kind) +} + +// ── tokeniseJSON ───────────────────────────────────────────────────────────── + +func TestTokeniseJSON_KeyAndString(t *testing.T) { + tokens := tokeniseJSON(`"name": "alice"`) + + kinds := map[tvTokenKind]bool{} + for _, tok := range tokens { + kinds[tok.Kind] = true + } + assert.True(t, kinds[tvKindJSONKey], "expected tvKindJSONKey token") + assert.True(t, kinds[tvKindJSONStr], "expected tvKindJSONStr token") +} + +func TestTokeniseJSON_Number(t *testing.T) { + tokens := tokeniseJSON(`"count": 42`) + + found := false + for _, tok := range tokens { + if tok.Kind == tvKindJSONNum && tok.Text == "42" { + found = true + } + } + assert.True(t, found, "expected number token '42'") +} + +func TestTokeniseJSON_BoolAndNull(t *testing.T) { + for _, kw := range []string{"true", "false", "null"} { + t.Run(kw, func(t *testing.T) { + tokens := tokeniseJSON(`"flag": ` + kw) + found := false + for _, tok := range tokens { + if tok.Kind == tvKindJSONBool && tok.Text == kw { + found = true + } + } + assert.True(t, found, "expected tvKindJSONBool token %q", kw) + }) + } +} + +func TestTokeniseJSON_Punctuation(t *testing.T) { + for _, ch := range []string{"{", "}", "[", "]", ","} { + t.Run(ch, func(t *testing.T) { + tokens := tokeniseJSON(ch) + require.Len(t, tokens, 1) + assert.Equal(t, tvKindJSONPunct, tokens[0].Kind) + }) + } +} + +func TestTokeniseJSON_EscapedQuoteInsideString(t *testing.T) { + tokens := tokeniseJSON(`"say \"hi\""`) + + strCount := 0 + for _, tok := range tokens { + if tok.Kind == tvKindJSONStr || tok.Kind == tvKindJSONKey { + strCount++ + } + } + assert.Equal(t, 1, strCount, "escaped quote should produce exactly 1 string token") +} + +func TestTokeniseJSON_DoubleBackslashBeforeQuote(t *testing.T) { + tokens := tokeniseJSON(`"\\"`) + + strCount := 0 + for _, tok := range tokens { + if tok.Kind == tvKindJSONStr || tok.Kind == tvKindJSONKey { + strCount++ + } + } + assert.Equal(t, 1, strCount, "double backslash before closing quote should produce exactly 1 string token") +} + +func TestTokeniseJSON_Empty(t *testing.T) { + tokens := tokeniseJSON("") + + require.Len(t, tokens, 1) + assert.Equal(t, tvKindPlain, tokens[0].Kind) +} + +// ── wrapTokens ─────────────────────────────────────────────────────────────── + +func TestWrapTokens_ShortLineNoWrap(t *testing.T) { + tokens := []tvToken{{Text: "hello", Kind: tvKindPlain}} + lines := wrapTokens(tokens, "hello", 80) + + require.Len(t, lines, 1) + assert.Equal(t, "hello", lines[0].Raw) +} + +func TestWrapTokens_LongLineWraps(t *testing.T) { + long := strings.Repeat("x", 100) + tokens := []tvToken{{Text: long, Kind: tvKindPlain}} + lines := wrapTokens(tokens, long, 40) + + assert.Greater(t, len(lines), 1, "expected wrapping into multiple lines") + + total := 0 + for _, l := range lines { + total += len([]rune(l.Raw)) + } + assert.Equal(t, 100, total, "all characters must be preserved across wrapped lines") +} + +func TestWrapTokens_JSONStringNotSplit(t *testing.T) { + str := `"` + strings.Repeat("a", 50) + `"` + tokens := []tvToken{{Text: str, Kind: tvKindJSONStr}} + lines := wrapTokens(tokens, str, 20) + + assert.Len(t, lines, 1, "JSON string must not be split across lines") +} -- cgit v1.2.3