1+ import { describe , it , expect , beforeEach , vi } from 'vitest'
2+ import * as fmt from './fmt.js'
3+
4+ // Helper to capture stdout via internal stdout.write
5+ // We will monkey-patch global process.stdout.write if available
6+ function captureStdout ( run : ( ) => void ) : string {
7+ let buf = ''
8+ const hasProcess =
9+ typeof process !== 'undefined' &&
10+ ( process as any ) . stdout &&
11+ typeof ( process as any ) . stdout . write === 'function'
12+
13+ if ( hasProcess ) {
14+ const orig = ( process as any ) . stdout . write
15+ ; ( process as any ) . stdout . write = ( chunk : any ) => {
16+ buf += typeof chunk === 'string' ? chunk : String ( chunk )
17+ return true
18+ }
19+ try {
20+ run ( )
21+ } finally {
22+ ; ( process as any ) . stdout . write = orig
23+ }
24+ } else {
25+ // Fallback: spy on console.log for environments without process
26+ const origLog = console . log
27+ ; ( console as any ) . log = ( msg : any ) => {
28+ buf += String ( msg ) + '\n'
29+ }
30+ try {
31+ run ( )
32+ } finally {
33+ console . log = origLog
34+ }
35+ }
36+
37+ return buf
38+ }
39+
40+ describe ( 'fmt basic value formatting' , ( ) => {
41+ it ( '%T approximations for primitives' , ( ) => {
42+ // routed through Sprintf which uses format parsing
43+ expect ( fmt . Sprintf ( 'Type: %T' , 123 ) ) . toBe ( 'Type: int' )
44+ expect ( fmt . Sprintf ( 'Type: %T' , 3.14 ) ) . toBe ( 'Type: float64' )
45+ expect ( fmt . Sprintf ( 'Type: %T' , 'hello' ) ) . toBe ( 'Type: string' )
46+ expect ( fmt . Sprintf ( 'Type: %T' , true ) ) . toBe ( 'Type: bool' )
47+ } )
48+
49+ it ( '%d truncation behavior including negatives' , ( ) => {
50+ expect ( fmt . Sprintf ( '%d' , 42.9 ) ) . toBe ( '42' )
51+ expect ( fmt . Sprintf ( '%d' , - 42.9 ) ) . toBe ( '-42' )
52+ } )
53+
54+ it ( '%q quoted string and rune' , ( ) => {
55+ expect ( fmt . Sprintf ( '%q' , 'hello' ) ) . toBe ( JSON . stringify ( 'hello' ) )
56+ // rune-like number
57+ expect ( fmt . Sprintf ( '%q' , 97 ) ) . toBe ( JSON . stringify ( 'a' ) )
58+ } )
59+
60+ it ( '%p pointer-ish formatting fallback' , ( ) => {
61+ expect ( fmt . Sprintf ( '%p' , { } ) ) . toBe ( '0x0' )
62+ expect ( fmt . Sprintf ( '%p' , { __address : 255 } ) ) . toBe ( '0xff' )
63+ } )
64+
65+ it ( '%v default formats for arrays/maps/sets' , ( ) => {
66+ expect ( fmt . Sprintf ( '%v' , [ 1 , 2 , 3 ] ) ) . toBe ( '[1 2 3]' )
67+ const m = new Map < any , any > ( )
68+ m . set ( 'a' , 1 )
69+ m . set ( 'b' , 2 )
70+ const out = fmt . Sprintf ( '%v' , m )
71+ // Order in Map iteration is insertion order; verify shape
72+ expect ( out . startsWith ( '{' ) ) . toBe ( true )
73+ expect ( out . includes ( 'a:1' ) ) . toBe ( true )
74+ expect ( out . includes ( 'b:2' ) ) . toBe ( true )
75+ expect ( out . endsWith ( '}' ) ) . toBe ( true )
76+
77+ const s = new Set < any > ( [ 1 , 2 , 3 ] )
78+ expect ( fmt . Sprintf ( '%v' , s ) ) . toBe ( '[1 2 3]' )
79+ } )
80+
81+ it ( 'error and stringer precedence' , ( ) => {
82+ const err = {
83+ Error ( ) {
84+ return 'some error'
85+ } ,
86+ }
87+ expect ( fmt . Sprintf ( '%v' , err ) ) . toBe ( 'some error' )
88+
89+ const stringer = {
90+ String ( ) {
91+ return 'I am stringer'
92+ } ,
93+ }
94+ expect ( fmt . Sprintf ( '%v' , stringer ) ) . toBe ( 'I am stringer' )
95+
96+ const goStringer = {
97+ GoString ( ) {
98+ return '<go stringer>'
99+ } ,
100+ }
101+ // We prefer GoString() first
102+ expect ( fmt . Sprintf ( '%v' , goStringer ) ) . toBe ( '<go stringer>' )
103+ } )
104+ } )
105+
106+ describe ( 'fmt spacing rules' , ( ) => {
107+ it ( 'Sprint: space only between non-strings' , ( ) => {
108+ // Two non-strings => one space
109+ expect ( fmt . Sprint ( 1 , 2 ) ) . toBe ( '1 2' )
110+ // If either is string => no automatic space
111+ expect ( fmt . Sprint ( 'a' , 'b' ) ) . toBe ( 'ab' )
112+ expect ( fmt . Sprint ( 'a' , 1 ) ) . toBe ( 'a1' )
113+ expect ( fmt . Sprint ( 1 , 'b' ) ) . toBe ( '1b' )
114+ // Mixed 3 args
115+ expect ( fmt . Sprint ( 'a' , 1 , 'b' ) ) . toBe ( 'a1b' )
116+ // Go's Sprint inserts a space only when both adjacent operands are non-strings.
117+ // Between 'b' (string) and 2 (number) there is no automatic space.
118+ expect ( fmt . Sprint ( 1 , 'b' , 2 ) ) . toBe ( '1b2' )
119+ } )
120+
121+ it ( 'Print: same spacing as Sprint, outputs to stdout' , ( ) => {
122+ const output = captureStdout ( ( ) => {
123+ fmt . Print ( 1 , 2 , 'x' , 3 )
124+ } )
125+ expect ( output ) . toBe ( '1 2x3' )
126+ } )
127+
128+ it ( 'Println: always separates by spaces and appends newline' , ( ) => {
129+ const output = captureStdout ( ( ) => {
130+ fmt . Println ( 'hi' , 'there' , 1 , 2 )
131+ } )
132+ expect ( output ) . toBe ( 'hi there 1 2\n' )
133+ } )
134+
135+ it ( 'Fprint/Fprintln behave like Print/Println with writers' , ( ) => {
136+ const chunks : Uint8Array [ ] = [ ]
137+ const writer = {
138+ Write ( b : Uint8Array ) : [ number , any ] {
139+ chunks . push ( b )
140+ return [ b . length , null ]
141+ } ,
142+ }
143+
144+ let [ n , err ] = fmt . Fprint ( writer , 1 , 2 , 'x' , 3 )
145+ expect ( err ) . toBeNull ( )
146+ expect ( n ) . toBe ( 5 ) // "1 2x3".length
147+ expect ( new TextDecoder ( ) . decode ( chunks [ 0 ] ) ) . toBe ( '1 2x3' )
148+
149+ ; [ n , err ] = fmt . Fprintln ( writer , 'hi' , 'there' , 1 , 2 )
150+ expect ( err ) . toBeNull ( )
151+ expect ( new TextDecoder ( ) . decode ( chunks [ 1 ] ) ) . toBe ( 'hi there 1 2\n' )
152+ } )
153+ } )
154+
155+ describe ( 'fmt parseFormat basic cases' , ( ) => {
156+ it ( 'Printf with %d, %s, %f, width and precision' , ( ) => {
157+ expect ( fmt . Sprintf ( 'n=%d s=%s f=%f' , 42 , 'ok' , 3.5 ) ) . toBe ( 'n=42 s=ok f=3.5' )
158+ expect ( fmt . Sprintf ( "'%5s'" , 'hi' ) ) . toBe ( "' hi'" )
159+ expect ( fmt . Sprintf ( "'%-.3f'" , 3.14159 ) ) . toBe ( "'3.142'" ) // JS rounds
160+ expect ( fmt . Sprintf ( "'%6.2f'" , 3.14159 ) ) . toBe ( "' 3.14'" )
161+ } )
162+
163+ it ( 'Printf with %% and missing args' , ( ) => {
164+ expect ( fmt . Sprintf ( '100%% done' ) ) . toBe ( '100% done' )
165+ // When the first argument is present but the second is missing,
166+ // Go prints the formatted first arg followed by the missing marker for the second.
167+ expect ( fmt . Sprintf ( '%d %s' , 1 ) ) . toBe ( '1 %!s(MISSING)' )
168+ } )
169+
170+ it ( 'Printf hex/octal/bin' , ( ) => {
171+ expect ( fmt . Sprintf ( '%x %X %o %b' , 255 , 255 , 8 , 5 ) ) . toBe ( 'ff FF 10 101' )
172+ } )
173+
174+ it ( 'Printf %c for code points' , ( ) => {
175+ expect ( fmt . Sprintf ( '%c' , 65 ) ) . toBe ( 'A' )
176+ } )
177+ } )
0 commit comments