1+ import { describe , test , expect } from "bun:test"
2+ import { capitalize , truncate , reverse } from "../src/string-utils"
3+
4+ describe ( "capitalize" , ( ) => {
5+ test ( "capitalizes the first letter" , ( ) => {
6+ expect ( capitalize ( "hello" ) ) . toBe ( "Hello" )
7+ } )
8+
9+ test ( "preserves the rest of the string as-is" , ( ) => {
10+ expect ( capitalize ( "hELLO" ) ) . toBe ( "HELLO" )
11+ } )
12+
13+ test ( "returns empty string unchanged" , ( ) => {
14+ expect ( capitalize ( "" ) ) . toBe ( "" )
15+ } )
16+
17+ test ( "handles single character" , ( ) => {
18+ expect ( capitalize ( "a" ) ) . toBe ( "A" )
19+ } )
20+
21+ test ( "handles already-capitalized string" , ( ) => {
22+ expect ( capitalize ( "Hello" ) ) . toBe ( "Hello" )
23+ } )
24+ } )
25+
26+ describe ( "reverse" , ( ) => {
27+ test ( "reverses a string" , ( ) => {
28+ expect ( reverse ( "hello" ) ) . toBe ( "olleh" )
29+ } )
30+
31+ test ( "returns empty string unchanged" , ( ) => {
32+ expect ( reverse ( "" ) ) . toBe ( "" )
33+ } )
34+
35+ test ( "palindrome stays the same" , ( ) => {
36+ expect ( reverse ( "racecar" ) ) . toBe ( "racecar" )
37+ } )
38+ } )
39+
40+ describe ( "truncate" , ( ) => {
41+ test ( "returns string unchanged when at maxLength" , ( ) => {
42+ expect ( truncate ( "Hello" , 5 ) ) . toBe ( "Hello" )
43+ } )
44+
45+ test ( "returns string unchanged when under maxLength" , ( ) => {
46+ expect ( truncate ( "Hi" , 10 ) ) . toBe ( "Hi" )
47+ } )
48+
49+ test ( "returns empty string unchanged" , ( ) => {
50+ expect ( truncate ( "" , 5 ) ) . toBe ( "" )
51+ } )
52+
53+ test ( "truncates at word boundary and appends '...'" , ( ) => {
54+ // "Hello world foo" maxLength=11 → window is "Hello wo" (8 chars), last space at 5
55+ // → "Hello" + "..." = "Hello..." (8 chars ≤ 11)
56+ const result = truncate ( "Hello world foo" , 11 )
57+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
58+ expect ( result . length ) . toBeLessThanOrEqual ( 11 )
59+ } )
60+
61+ test ( "'...' counts toward maxLength" , ( ) => {
62+ const result = truncate ( "Hello world" , 8 )
63+ expect ( result . length ) . toBeLessThanOrEqual ( 8 )
64+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
65+ } )
66+
67+ test ( "does not cut in the middle of a word" , ( ) => {
68+ const result = truncate ( "Hello beautiful world" , 14 )
69+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
70+ // The text before '...' must be a whole word (or multiple whole words)
71+ const textPart = result . slice ( 0 , - 3 )
72+ // Every token in textPart must appear as a complete word in the original
73+ const originalWords = "Hello beautiful world" . split ( " " )
74+ const resultWords = textPart . split ( " " )
75+ for ( const word of resultWords ) {
76+ expect ( originalWords ) . toContain ( word )
77+ }
78+ } )
79+
80+ test ( "handles maxLength === 3 (no '...' appended, just hard cut)" , ( ) => {
81+ expect ( truncate ( "Hello" , 3 ) ) . toBe ( "Hel" )
82+ } )
83+
84+ test ( "handles maxLength === 2" , ( ) => {
85+ expect ( truncate ( "Hello" , 2 ) ) . toBe ( "He" )
86+ } )
87+
88+ test ( "handles maxLength === 1" , ( ) => {
89+ expect ( truncate ( "Hello" , 1 ) ) . toBe ( "H" )
90+ } )
91+
92+ test ( "handles maxLength === 0" , ( ) => {
93+ expect ( truncate ( "Hello" , 0 ) ) . toBe ( "" )
94+ } )
95+
96+ test ( "handles a single long word with no spaces" , ( ) => {
97+ // No space to break on — falls back to hard cut at window boundary
98+ const result = truncate ( "Superlongword" , 8 )
99+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
100+ expect ( result . length ) . toBeLessThanOrEqual ( 8 )
101+ } )
102+
103+ test ( "handles exact boundary — one char over" , ( ) => {
104+ // "Hello!" length 6, maxLength 5 → window "He" (2 chars), no space → cut at 2 → "He..."
105+ const result = truncate ( "Hello!" , 5 )
106+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
107+ expect ( result . length ) . toBeLessThanOrEqual ( 5 )
108+ } )
109+
110+ test ( "preserves full string when length exactly equals maxLength" , ( ) => {
111+ expect ( truncate ( "exact" , 5 ) ) . toBe ( "exact" )
112+ } )
113+
114+ test ( "truncates a realistic sentence" , ( ) => {
115+ const sentence = "The quick brown fox jumps over the lazy dog"
116+ const result = truncate ( sentence , 20 )
117+ expect ( result . length ) . toBeLessThanOrEqual ( 20 )
118+ expect ( result . endsWith ( "..." ) ) . toBe ( true )
119+ // Verify no partial words
120+ const originalWords = sentence . split ( " " )
121+ const textPart = result . slice ( 0 , - 3 )
122+ const resultWords = textPart . split ( " " )
123+ for ( const word of resultWords ) {
124+ expect ( originalWords ) . toContain ( word )
125+ }
126+ } )
127+ } )
0 commit comments