mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 23:34:10 +08:00
test(cli): add tests for stdin input (#107)
* test(cli): add tests for stdin input * test(cli): extract mock stdin to helper function * test(cli): add comprehensive tests for stdin edge cases and output file handling * refactor(test): streamline mockStdin function and relocate to utils * test(cli): remove redundant test for JSON encoding from stdin * test(cli): restore mocks consistently * test(cli): restructured output file tests and modified some assertions * chore: fix linting issues & remove redundant cleanups --------- Co-authored-by: mad-cat-lon <113548315+mad-cat-lon@users.noreply.github.com> Co-authored-by: Johann Schopplich <mail@johannschopplich.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { consola } from 'consola'
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { DEFAULT_DELIMITER, encode } from '../../toon/src'
|
import { DEFAULT_DELIMITER, encode } from '../../toon/src'
|
||||||
import { version } from '../package.json' with { type: 'json' }
|
import { version } from '../package.json' with { type: 'json' }
|
||||||
import { createCliTestContext, runCli } from './utils'
|
import { createCliTestContext, mockStdin, runCli } from './utils'
|
||||||
|
|
||||||
describe('toon CLI', () => {
|
describe('toon CLI', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -25,6 +25,29 @@ describe('toon CLI', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('encode (JSON → TOON)', () => {
|
describe('encode (JSON → TOON)', () => {
|
||||||
|
it('encodes JSON from stdin', async () => {
|
||||||
|
const data = {
|
||||||
|
title: 'TOON test',
|
||||||
|
count: 3,
|
||||||
|
nested: { ok: true },
|
||||||
|
}
|
||||||
|
const cleanup = mockStdin(JSON.stringify(data))
|
||||||
|
|
||||||
|
const stdout: string[] = []
|
||||||
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
|
stdout.push(String(message ?? ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli()
|
||||||
|
expect(stdout).toHaveLength(1)
|
||||||
|
expect(stdout[0]).toBe(encode(data))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('encodes a JSON file into a TOON file', async () => {
|
it('encodes a JSON file into a TOON file', async () => {
|
||||||
const data = {
|
const data = {
|
||||||
title: 'TOON test',
|
title: 'TOON test',
|
||||||
@@ -61,7 +84,7 @@ describe('toon CLI', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const stdout: string[] = []
|
const stdout: string[] = []
|
||||||
const logSpy = vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
stdout.push(String(message ?? ''))
|
stdout.push(String(message ?? ''))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,7 +95,26 @@ describe('toon CLI', () => {
|
|||||||
expect(stdout[0]).toBe(encode(data))
|
expect(stdout[0]).toBe(encode(data))
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
logSpy.mockRestore()
|
await context.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('encodes JSON from stdin to output file', async () => {
|
||||||
|
const data = { key: 'value' }
|
||||||
|
const context = await createCliTestContext({})
|
||||||
|
const cleanup = mockStdin(JSON.stringify(data))
|
||||||
|
|
||||||
|
const consolaSuccess = vi.spyOn(consola, 'success').mockImplementation(() => undefined)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await context.run(['--output', 'output.toon'])
|
||||||
|
|
||||||
|
const output = await context.read('output.toon')
|
||||||
|
expect(output).toBe(encode(data))
|
||||||
|
expect(consolaSuccess).toHaveBeenCalledWith(expect.stringMatching(/Encoded.*stdin[^\n\r\u2028\u2029\u2192]*\u2192.*output\.toon/))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
await context.cleanup()
|
await context.cleanup()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -102,6 +144,153 @@ describe('toon CLI', () => {
|
|||||||
await context.cleanup()
|
await context.cleanup()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('decodes TOON from stdin', async () => {
|
||||||
|
const data = { items: ['a', 'b'], count: 2 }
|
||||||
|
const toonInput = encode(data)
|
||||||
|
|
||||||
|
const cleanup = mockStdin(toonInput)
|
||||||
|
|
||||||
|
const stdout: string[] = []
|
||||||
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
|
stdout.push(String(message ?? ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: ['--decode'] })
|
||||||
|
expect(stdout).toHaveLength(1)
|
||||||
|
const result = JSON.parse(stdout?.at(0) ?? '')
|
||||||
|
expect(result).toEqual(data)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('decodes TOON from stdin to output file', async () => {
|
||||||
|
const data = { name: 'test', values: [1, 2, 3] }
|
||||||
|
const toonInput = encode(data)
|
||||||
|
const context = await createCliTestContext({})
|
||||||
|
const cleanup = mockStdin(toonInput)
|
||||||
|
|
||||||
|
const consolaSuccess = vi.spyOn(consola, 'success').mockImplementation(() => undefined)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await context.run(['--decode', '--output', 'output.json'])
|
||||||
|
|
||||||
|
const output = await context.read('output.json')
|
||||||
|
expect(JSON.parse(output)).toEqual(data)
|
||||||
|
expect(consolaSuccess).toHaveBeenCalledWith(expect.stringMatching(/Decoded.*stdin[^\n\r\u2028\u2029\u2192]*\u2192.*output\.json/))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
await context.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('stdin edge cases', () => {
|
||||||
|
it('handles invalid JSON from stdin', async () => {
|
||||||
|
const cleanup = mockStdin('{ invalid json }')
|
||||||
|
|
||||||
|
const consolaError = vi.spyOn(consola, 'error').mockImplementation(() => undefined)
|
||||||
|
const exitSpy = vi.mocked(process.exit)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: [] })
|
||||||
|
|
||||||
|
expect(exitSpy).toHaveBeenCalledWith(1)
|
||||||
|
expect(consolaError).toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles invalid TOON from stdin', async () => {
|
||||||
|
const cleanup = mockStdin('key: "unterminated string')
|
||||||
|
|
||||||
|
const consolaError = vi.spyOn(consola, 'error').mockImplementation(() => undefined)
|
||||||
|
const exitSpy = vi.mocked(process.exit)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: ['--decode'] })
|
||||||
|
|
||||||
|
expect(exitSpy).toHaveBeenCalledWith(1)
|
||||||
|
expect(consolaError).toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('stdin with options', () => {
|
||||||
|
it('encodes JSON from stdin with custom delimiter', async () => {
|
||||||
|
const data = { items: [1, 2, 3] }
|
||||||
|
const cleanup = mockStdin(JSON.stringify(data))
|
||||||
|
|
||||||
|
const stdout: string[] = []
|
||||||
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
|
stdout.push(String(message ?? ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: ['--delimiter', '|'] })
|
||||||
|
|
||||||
|
expect(stdout).toHaveLength(1)
|
||||||
|
expect(stdout[0]).toBe(encode(data, { delimiter: '|' }))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('encodes JSON from stdin with custom indent', async () => {
|
||||||
|
const data = {
|
||||||
|
nested: {
|
||||||
|
deep: { value: 1 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const cleanup = mockStdin(JSON.stringify(data))
|
||||||
|
|
||||||
|
const stdout: string[] = []
|
||||||
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
|
stdout.push(String(message ?? ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: ['--indent', '4'] })
|
||||||
|
|
||||||
|
expect(stdout).toHaveLength(1)
|
||||||
|
expect(stdout[0]).toBe(encode(data, { indent: 4 }))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('decodes TOON from stdin with --no-strict', async () => {
|
||||||
|
const data = { test: true }
|
||||||
|
const toonInput = encode(data)
|
||||||
|
const cleanup = mockStdin(toonInput)
|
||||||
|
|
||||||
|
const stdout: string[] = []
|
||||||
|
vi.spyOn(console, 'log').mockImplementation((message?: unknown) => {
|
||||||
|
stdout.push(String(message ?? ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCli({ rawArgs: ['--decode', '--no-strict'] })
|
||||||
|
|
||||||
|
expect(stdout).toHaveLength(1)
|
||||||
|
const result = JSON.parse(stdout?.at(0) ?? '')
|
||||||
|
expect(result).toEqual(data)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('error handling', () => {
|
describe('error handling', () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as fsp from 'node:fs/promises'
|
|||||||
import * as os from 'node:os'
|
import * as os from 'node:os'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
|
import { Readable } from 'node:stream'
|
||||||
import { runMain } from 'citty'
|
import { runMain } from 'citty'
|
||||||
import { mainCommand } from '../src/index'
|
import { mainCommand } from '../src/index'
|
||||||
|
|
||||||
@@ -76,3 +77,20 @@ async function writeFiles(baseDir: string, files: FileRecord): Promise<void> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockStdin(input: string): () => void {
|
||||||
|
const mockStream = Readable.from([input])
|
||||||
|
|
||||||
|
const originalStdin = process.stdin
|
||||||
|
Object.defineProperty(process, 'stdin', {
|
||||||
|
value: mockStream,
|
||||||
|
writable: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Object.defineProperty(process, 'stdin', {
|
||||||
|
value: originalStdin,
|
||||||
|
writable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user