11import process from 'node:process'
22import { consola } from 'consola'
33import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
4- import { version } from '../../toon/package.json' with { type : 'json' }
54import { DEFAULT_DELIMITER , encode } from '../../toon/src'
5+ import { version } from '../package.json' with { type : 'json' }
66import { createCliTestContext , runCli } from './utils'
77
88describe ( 'toon CLI' , ( ) => {
@@ -14,113 +14,161 @@ describe('toon CLI', () => {
1414 vi . restoreAllMocks ( )
1515 } )
1616
17- it ( 'prints the version when using --version' , async ( ) => {
18- const consolaLog = vi . spyOn ( consola , 'log' ) . mockImplementation ( ( ) => undefined )
19- const consolaError = vi . spyOn ( consola , 'error ' ) . mockImplementation ( ( ) => undefined )
17+ describe ( ' version' , ( ) => {
18+ it ( 'prints the version when using --version' , async ( ) => {
19+ const consolaLog = vi . spyOn ( consola , 'log ' ) . mockImplementation ( ( ) => undefined )
2020
21- await runCli ( { rawArgs : [ '--version' ] } )
21+ await runCli ( { rawArgs : [ '--version' ] } )
2222
23- expect ( consolaLog ) . toHaveBeenCalledWith ( version )
24- expect ( consolaError ) . not . toHaveBeenCalled ( )
23+ expect ( consolaLog ) . toHaveBeenCalledWith ( version )
24+ } )
2525 } )
2626
27- it ( 'encodes a JSON file into a TOON file' , async ( ) => {
28- const data = {
29- title : 'TOON test' ,
30- count : 3 ,
31- nested : { ok : true } ,
32- }
33- const context = await createCliTestContext ( {
34- 'input.json' : JSON . stringify ( data , undefined , 2 ) ,
35- } )
27+ describe ( 'encode (JSON → TOON)' , ( ) => {
28+ it ( 'encodes a JSON file into a TOON file' , async ( ) => {
29+ const data = {
30+ title : 'TOON test' ,
31+ count : 3 ,
32+ nested : { ok : true } ,
33+ }
34+ const context = await createCliTestContext ( {
35+ 'input.json' : JSON . stringify ( data , undefined , 2 ) ,
36+ } )
37+
38+ const consolaSuccess = vi . spyOn ( consola , 'success' ) . mockImplementation ( ( ) => undefined )
39+
40+ try {
41+ await context . run ( [ 'input.json' , '--output' , 'output.toon' ] )
42+
43+ const output = await context . read ( 'output.toon' )
44+ const expected = encode ( data , {
45+ delimiter : DEFAULT_DELIMITER ,
46+ indent : 2 ,
47+ lengthMarker : false ,
48+ } )
3649
37- const consolaSuccess = vi . spyOn ( consola , 'success' ) . mockImplementation ( ( ) => undefined )
50+ expect ( output ) . toBe ( expected )
51+ expect ( consolaSuccess ) . toHaveBeenCalledWith ( expect . stringMatching ( / E n c o d e d .* → .* / ) )
52+ }
53+ finally {
54+ await context . cleanup ( )
55+ }
56+ } )
3857
39- try {
40- await context . run ( [ 'input.json' , '--output' , 'output.toon' ] )
58+ it ( 'writes to stdout when output not specified' , async ( ) => {
59+ const data = { ok : true }
60+ const context = await createCliTestContext ( {
61+ 'input.json' : JSON . stringify ( data ) ,
62+ } )
4163
42- const output = await context . read ( 'output.toon' )
43- const expected = encode ( data , {
44- delimiter : DEFAULT_DELIMITER ,
45- indent : 2 ,
46- lengthMarker : false ,
64+ const stdout : string [ ] = [ ]
65+ const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( message ?: unknown ) => {
66+ stdout . push ( String ( message ?? '' ) )
4767 } )
4868
49- expect ( output ) . toBe ( expected )
50- expect ( consolaSuccess ) . toHaveBeenCalledWith ( 'Encoded `input.json` → `output.toon`' )
51- }
52- finally {
53- await context . cleanup ( )
54- }
55- } )
69+ try {
70+ await context . run ( [ 'input.json' ] )
5671
57- it ( 'decodes a TOON file into a JSON file' , async ( ) => {
58- const data = {
59- items : [ 'alpha' , 'beta' ] ,
60- meta : { done : false } ,
61- }
62- const toonInput = encode ( data )
63- const context = await createCliTestContext ( {
64- 'input.toon' : toonInput ,
72+ expect ( stdout ) . toHaveLength ( 1 )
73+ expect ( stdout [ 0 ] ) . toBe ( encode ( data ) )
74+ }
75+ finally {
76+ logSpy . mockRestore ( )
77+ await context . cleanup ( )
78+ }
6579 } )
80+ } )
6681
67- const consolaSuccess = vi . spyOn ( consola , 'success' ) . mockImplementation ( ( ) => undefined )
82+ describe ( 'decode (TOON → JSON)' , ( ) => {
83+ it ( 'decodes a TOON file into a JSON file' , async ( ) => {
84+ const data = {
85+ items : [ 'alpha' , 'beta' ] ,
86+ meta : { done : false } ,
87+ }
88+ const toonInput = encode ( data )
89+ const context = await createCliTestContext ( {
90+ 'input.toon' : toonInput ,
91+ } )
6892
69- try {
70- await context . run ( [ 'input.toon' , '--output' , 'output.json' ] )
93+ const consolaSuccess = vi . spyOn ( consola , 'success' ) . mockImplementation ( ( ) => undefined )
7194
72- const output = await context . read ( 'output.json' )
73- expect ( JSON . parse ( output ) ) . toEqual ( data )
74- expect ( consolaSuccess ) . toHaveBeenCalledWith ( 'Decoded `input.toon` → `output.json`' )
75- }
76- finally {
77- await context . cleanup ( )
78- }
79- } )
95+ try {
96+ await context . run ( [ 'input.toon' , '--output' , 'output.json' ] )
8097
81- it ( 'writes encoded TOON to stdout when no output file is provided' , async ( ) => {
82- const data = { ok : true }
83- const context = await createCliTestContext ( {
84- 'input.json' : JSON . stringify ( data ) ,
98+ const output = await context . read ( 'output.json' )
99+ expect ( JSON . parse ( output ) ) . toEqual ( data )
100+ expect ( consolaSuccess ) . toHaveBeenCalledWith ( expect . stringMatching ( / D e c o d e d .* → .* / ) )
101+ }
102+ finally {
103+ await context . cleanup ( )
104+ }
85105 } )
106+ } )
107+
108+ describe ( 'error handling' , ( ) => {
109+ it ( 'rejects invalid delimiter' , async ( ) => {
110+ const context = await createCliTestContext ( {
111+ 'input.json' : JSON . stringify ( { value : 1 } ) ,
112+ } )
113+
114+ const consolaError = vi . spyOn ( consola , 'error' ) . mockImplementation ( ( ) => undefined )
115+ const exitSpy = vi . mocked ( process . exit )
116+
117+ try {
118+ await context . run ( [ 'input.json' , '--delimiter' , ';' ] )
119+
120+ expect ( exitSpy ) . toHaveBeenCalledWith ( 1 )
86121
87- const stdout : string [ ] = [ ]
88- const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( message ?: unknown ) => {
89- stdout . push ( String ( message ?? '' ) )
122+ const errorCall = consolaError . mock . calls . at ( 0 )
123+ expect ( errorCall ) . toBeDefined ( )
124+ const [ error ] = errorCall !
125+ expect ( error ) . toBeInstanceOf ( Error )
126+ expect ( error . message ) . toContain ( 'Invalid delimiter' )
127+ }
128+ finally {
129+ await context . cleanup ( )
130+ }
90131 } )
91132
92- try {
93- await context . run ( [ 'input.json' ] )
133+ it ( 'rejects invalid indent value' , async ( ) => {
134+ const context = await createCliTestContext ( {
135+ 'input.json' : JSON . stringify ( { value : 1 } ) ,
136+ } )
94137
95- expect ( stdout ) . toHaveLength ( 1 )
96- expect ( stdout [ 0 ] ) . toBe ( encode ( data ) )
97- }
98- finally {
99- logSpy . mockRestore ( )
100- await context . cleanup ( )
101- }
102- } )
138+ const consolaError = vi . spyOn ( consola , 'error' ) . mockImplementation ( ( ) => undefined )
139+ const exitSpy = vi . mocked ( process . exit )
140+
141+ try {
142+ await context . run ( [ 'input.json' , '--indent' , 'abc' ] )
103143
104- it ( 'throws on an invalid delimiter argument' , async ( ) => {
105- const context = await createCliTestContext ( {
106- 'input.json' : JSON . stringify ( { value : 1 } ) ,
144+ expect ( exitSpy ) . toHaveBeenCalledWith ( 1 )
145+
146+ const errorCall = consolaError . mock . calls . at ( 0 )
147+ expect ( errorCall ) . toBeDefined ( )
148+ const [ error ] = errorCall !
149+ expect ( error ) . toBeInstanceOf ( Error )
150+ expect ( error . message ) . toContain ( 'Invalid indent value' )
151+ }
152+ finally {
153+ await context . cleanup ( )
154+ }
107155 } )
108156
109- const consolaError = vi . spyOn ( consola , 'error' ) . mockImplementation ( ( ) => undefined )
157+ it ( 'handles missing input file' , async ( ) => {
158+ const context = await createCliTestContext ( { } )
110159
111- try {
112- await expect ( context . run ( [ 'input.json' , '--delimiter' , ';' ] ) ) . resolves . toBeUndefined ( )
160+ const consolaError = vi . spyOn ( consola , 'error' ) . mockImplementation ( ( ) => undefined )
161+ const exitSpy = vi . mocked ( process . exit )
113162
114- const exitMock = vi . mocked ( process . exit )
115- expect ( exitMock ) . toHaveBeenCalledWith ( 1 )
163+ try {
164+ await context . run ( [ 'nonexistent.json' ] )
116165
117- const errorCall = consolaError . mock . calls . at ( 0 )
118- expect ( errorCall ) . toBeDefined ( )
119- const [ error ] = errorCall !
120- expect ( error . message ) . toContain ( 'Invalid delimiter' )
121- }
122- finally {
123- await context . cleanup ( )
124- }
166+ expect ( exitSpy ) . toHaveBeenCalledWith ( 1 )
167+ expect ( consolaError ) . toHaveBeenCalled ( )
168+ }
169+ finally {
170+ await context . cleanup ( )
171+ }
172+ } )
125173 } )
126174} )
0 commit comments