Skip to content

Commit

Permalink
test: get tests passing
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed May 15, 2024
1 parent 5fa48c2 commit 5e9bbcd
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 109 deletions.
10 changes: 2 additions & 8 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
{
"require": [
"test/helpers/init.js",
"ts-node/register",
"source-map-support/register"
],
"watch-extensions": [
"ts"
],
"import": ["tsx"],
"watch-extensions": ["ts"],
"recursive": true,
"reporter": "spec",
"timeout": 5000
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
"@commitlint/config-conventional": "^18.6.3",
"@oclif/core": "^4.0.0-beta.6",
"@oclif/prettier-config": "^0.2.1",
"@types/chai": "^4.3.16",
"@types/debug": "^4.1.12",
"@types/mocha": "^10",
"@types/node": "^18",
"chai": "^5.1.1",
"commitlint": "^18.6.1",
"eslint": "^8.57.0",
"eslint-config-oclif": "^5.2.0",
Expand All @@ -28,7 +30,7 @@
"mocha": "^10",
"prettier": "^3.2.5",
"shx": "^0.3.3",
"ts-node": "^10.9.2",
"tsx": "^4.10.2",
"typescript": "^5.4.5"
},
"engines": {
Expand Down
34 changes: 23 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ansis from 'ansis'
import makeDebug from 'debug'
import {dirname} from 'node:path'

const debug = makeDebug('test')
const debug = makeDebug('oclif-test')

type CaptureOptions = {
print?: boolean
Expand Down Expand Up @@ -83,18 +83,23 @@ function traverseFilePathUntil(filename: string, predicate: (filename: string) =
return current
}

function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOptions {
function findRoot(): string {
return (
loadOpts ?? {
root: traverseFilePathUntil(
// eslint-disable-next-line unicorn/prefer-module
require.main?.path ?? module.path,
(p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')),
),
}
process.env.OCLIF_TEST_ROOT ??
// eslint-disable-next-line unicorn/prefer-module
Object.values(require.cache).find((m) => m?.children.includes(module))?.filename ??
traverseFilePathUntil(
// eslint-disable-next-line unicorn/prefer-module
require.main?.path ?? module.path,
(p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn')),
)
)
}

function makeLoadOptions(loadOpts?: Interfaces.LoadOptions): Interfaces.LoadOptions {
return loadOpts ?? {root: findRoot()}
}

export async function captureOutput<T>(
fn: () => Promise<unknown>,
opts?: CaptureOptions,
Expand Down Expand Up @@ -130,7 +135,7 @@ export async function captureOutput<T>(
}

export async function runCommand<T>(
args: string[],
args: string | string[],
loadOpts?: Interfaces.LoadOptions,
captureOpts?: CaptureOptions,
): Promise<{
Expand All @@ -140,8 +145,15 @@ export async function runCommand<T>(
stdout: string
}> {
const loadOptions = makeLoadOptions(loadOpts)
const argsArray = (Array.isArray(args) ? args : [args]).join(' ').split(' ')

const [id, ...rest] = argsArray
const finalArgs = id === '.' ? rest : argsArray

debug('loadOpts: %O', loadOpts)
return captureOutput<T>(async () => run(args, loadOptions), captureOpts)
debug('args: %O', finalArgs)

return captureOutput<T>(async () => run(finalArgs, loadOptions), captureOpts)
}

export async function runHook<T>(
Expand Down
57 changes: 24 additions & 33 deletions test/command.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,38 @@
import {expect} from 'chai'
import {join} from 'node:path'

import {expect, test} from '../src'
import {runCommand} from '../src'

describe('command', () => {
// eslint-disable-next-line unicorn/prefer-module
const root = join(__dirname, 'fixtures/multi')
test
.loadConfig({root})
.stdout()
.command(['foo:bar'])
.do((output) => {
expect(output.stdout).to.equal('hello world!\n')
const {name} = output.returned as {name: string}
expect(name).to.equal('world')
})
.it()

test
.loadConfig({root})
.stdout()
.command(['foo:bar', '--name=foo'])
.do((output) => expect(output.stdout).to.equal('hello foo!\n'))
.it()
it('should run a command', async () => {
const {result, stdout} = await runCommand<{name: string}>(['foo:bar'], {root})
expect(stdout).to.equal('hello world!\n')
expect(result?.name).to.equal('world')
})

test
.loadConfig({root})
.stdout()
.command(['foo bar', '--name=foo'])
.do((output) => expect(output.stdout).to.equal('hello foo!\n'))
.it()
it('should run a command with a flag', async () => {
const {result, stdout} = await runCommand<{name: string}>(['foo:bar', '--name=foo'], {root})
expect(stdout).to.equal('hello foo!\n')
expect(result?.name).to.equal('foo')
})

it('should run a command using spaces', async () => {
const {result, stdout} = await runCommand<{name: string}>(['foo bar', '--name=foo'], {root})
expect(stdout).to.equal('hello foo!\n')
expect(result?.name).to.equal('foo')
})
})

describe('single command cli', () => {
// eslint-disable-next-line unicorn/prefer-module
const root = join(__dirname, 'fixtures/single')
test
.loadConfig({root})
.stdout()
.command(['.'])
.do((output) => {
expect(output.stdout).to.equal('hello world!\n')
const {name} = output.returned as {name: string}
expect(name).to.equal('world')
})
.it()

it('should run a single command cli', async () => {
const {result, stdout} = await runCommand<{name: string}>(['.'], {root})
expect(stdout).to.equal('hello world!\n')
expect(result?.name).to.equal('world')
})
})
16 changes: 8 additions & 8 deletions test/exit.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {expect} from 'chai'
import {join} from 'node:path'

import {expect, test} from '../src'
import {runCommand} from '../src'

// eslint-disable-next-line unicorn/prefer-module
const root = join(__dirname, 'fixtures/multi')

describe('exit', () => {
test
.loadConfig({root})
.stdout()
.command(['exit', '--code=101'])
.exit(101)
.do(output => expect(output.stdout).to.equal('exiting with code 101\n'))
.it()
it('should handle expected exit codes', async () => {
const {error, stdout} = await runCommand(['exit', '--code=101'], {root})
expect(stdout).to.equal('exiting with code 101\n')
expect(error?.message).to.equal('EEXIT: 101')
expect(error?.oclif?.exit).to.equal(101)
})
})
2 changes: 0 additions & 2 deletions test/helpers/init.js

This file was deleted.

13 changes: 6 additions & 7 deletions test/hook.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import {expect} from 'chai'
import {join} from 'node:path'

import {expect, test} from '../src'
import {runHook} from '../src'

// eslint-disable-next-line unicorn/prefer-module
const root = join(__dirname, 'fixtures/multi')

describe('hooks', () => {
test
.loadConfig({root})
.stdout()
.hook('foo', {argv: ['arg']}, {root})
.do(output => expect(output.stdout).to.equal('foo hook args: arg\n'))
.it()
it('should run a hook', async () => {
const {stdout} = await runHook('foo', {argv: ['arg']}, {root})
expect(stdout).to.equal('foo hook args: arg\n')
})
})
80 changes: 41 additions & 39 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
import {expect, test} from '../src'
import {Command, Flags} from '@oclif/core'
import {expect} from 'chai'

describe('stdout', () => {
test
.stdout()
.end('logs', output => {
console.log('foo')
expect(output.stdout).to.equal('foo\n')
})
import {captureOutput} from '../src'

test
.stdout()
.end('logs twice', output => {
console.log('foo')
expect(output.stdout).to.equal('foo\n')
console.log('bar')
expect(output.stdout).to.equal('foo\nbar\n')
})
})
class MyCommand extends Command {
static flags = {
channel: Flags.option({
char: 'c',
multiple: true,
options: ['stdout', 'stderr'] as const,
required: true,
})(),
}

describe('stdout + stderr', () => {
test
.stdout()
.stderr()
.end('logs and errors', output => {
console.log('foo')
console.error('bar')
expect(output.stdout).to.equal('foo\n')
expect(output.stderr).to.equal('bar\n')
})
})
async run() {
const {flags} = await this.parse(MyCommand)
if (flags.channel.includes('stdout')) {
this.log('hello world!')
}

// eslint-disable-next-line unicorn/no-static-only-class
class MockOs {
static platform() {
return 'not-a-platform'
if (flags.channel.includes('stderr')) {
this.logToStderr('hello world!')
}
}
}

for (const os of ['darwin', 'win32', 'linux']) {
describe(os, () => {
test
.stub(MockOs, 'platform', stub => stub.returns(os))
.end('sets os', () => {
expect(MockOs.platform()).to.equal(os)
describe('captureOutput', () => {
it('should capture stdout', async () => {
const {stdout} = await captureOutput(async () => {
await MyCommand.run(['-c=stdout'])
})
expect(stdout).to.equal('hello world!\n')
})
}

it('should capture stderr', async () => {
const {stderr} = await captureOutput(async () => {
await MyCommand.run(['-c=stderr'])
})
expect(stderr).to.equal('hello world!\n')
})

it('should capture both', async () => {
const {stderr, stdout} = await captureOutput(async () => {
await MyCommand.run(['-c=stdout', '-c=stderr'])
})
expect(stdout).to.equal('hello world!\n')
expect(stderr).to.equal('hello world!\n')
})
})
Loading

0 comments on commit 5e9bbcd

Please sign in to comment.