@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
281 lines (217 loc) • 8.21 kB
text/typescript
import { diskUtil, versionUtil } from '@sprucelabs/spruce-skill-utils'
import { test, assert } from '@sprucelabs/test-utils'
import featuresUtil from '../../features/feature.utilities'
import {
FeatureActionResponse,
FeatureInstallResponse,
} from '../../features/features.types'
import CommandServiceImpl from '../../services/CommandService'
import AbstractSchemaTest from '../../tests/AbstractSchemaTest'
import testUtil from '../../tests/utilities/test.utility'
export default class FeatureCommandExecuterContTest extends AbstractSchemaTest {
protected static async canSkipOptionalDependencies() {
const { promise: actionPromise } =
await this.startBuildingNewErrorUntilOptionalDependencies('skip')
const results =
await this.finishBuildingUpToNamingNewError(actionPromise)
assert.isTruthy(results.files)
assert.isFalsy(results.errors)
const pluginsDir = this.resolveHashSprucePath('features')
assert.isFalse(diskUtil.doesDirExist(pluginsDir))
testUtil.assertFileByNameInGeneratedFiles(
'myNewError.schema.ts',
results.files
)
}
protected static async shouldEmitExecutionEvents() {
this.fakeInstallCommands()
const executer = this.Action('skill', 'create', {
shouldAutoHandleDependencies: true,
})
let emittedWillEvent = false
let willEventCommand = ''
let emittedDidEvent = false
let didEventCommand = ''
let willExecuteHitCount = 0
let didExecuteHitCount = 0
const emitter = this.emitter
void emitter.on('feature.will-execute', (payload) => {
emittedWillEvent = true
willExecuteHitCount++
willEventCommand = featuresUtil.generateCommand(
payload.featureCode,
payload.actionCode
)
return {}
})
void emitter.on('feature.did-execute', (payload) => {
emittedDidEvent = true
didExecuteHitCount++
didEventCommand = featuresUtil.generateCommand(
payload.featureCode,
payload.actionCode
)
return {
meta: {
taco: 'bell',
},
}
})
assert.isFalse(emittedWillEvent)
assert.isFalse(emittedDidEvent)
assert.isEqual(willExecuteHitCount, 0)
assert.isEqual(didExecuteHitCount, 0)
const promise = executer.execute({})
await this.waitForInput()
await this.ui.sendInput('My new skill')
await this.ui.sendInput('So great!')
const results = await promise
assert.isEqual(results.meta?.taco, 'bell')
assert.isEqual(willEventCommand, 'create.skill')
assert.isEqual(didEventCommand, 'create.skill')
assert.isEqual(willExecuteHitCount, 1)
assert.isEqual(didExecuteHitCount, 1)
}
protected static async canPermanentlySkipOptionalDependencies() {
const { promise: actionPromise } =
await this.startBuildingNewErrorUntilOptionalDependencies(
'alwaysSkip'
)
await this.finishBuildingUpToNamingNewError(actionPromise)
const executer = this.Action('error', 'create', {
shouldAutoHandleDependencies: true,
})
const promise = executer.execute({})
await this.waitForInput()
assert.doesNotInclude(this.ui.getLastInvocation(), {
command: 'prompt',
options: {
type: 'select',
},
})
await this.ui.sendInput('My second error')
await this.ui.sendInput('')
const secondTimeResults = await promise
assert.isTruthy(secondTimeResults.files)
testUtil.assertFileByNameInGeneratedFiles(
'myNewError.schema.ts',
secondTimeResults.files
)
}
protected static async shouldReturnProperSummary() {
this.fakeInstallCommands()
const executer = this.Action('skill', 'create', {
shouldAutoHandleDependencies: true,
})
const results = await executer.execute({
name: 'install summary test skill',
description: 'go team!',
})
assert.isTruthy(results.files)
assert.isTruthy(results.packagesInstalled)
assert.isAbove(results.files.length, 0)
assert.isAbove(results.packagesInstalled.length, 0)
}
protected static async shouldAskInstallDependentFeatures() {
this.fakeInstallCommands()
const executer = this.Action('schema', 'create', {
shouldAutoHandleDependencies: true,
})
void executer.execute({})
await this.waitForInput()
this.ui.reset()
const lastQuestion = this.ui.getLastInvocation()
assert.isEqual(lastQuestion.command, 'prompt')
assert.doesInclude(
lastQuestion.options.label,
/install the skill feature/gi
)
}
protected static async shouldAddListenerWithoutBreakingOnSkill() {
this.fakeInstallCommands()
await this.FeatureFixture().installCachedFeatures('schemas')
const executer = this.Action('event', 'listen', {
shouldAutoHandleDependencies: true,
})
const promise = executer.execute({})
await this.waitForInput()
await this.ui.sendInput('Y')
await this.waitForInput()
await this.ui.sendInput('\n')
await this.waitForInput()
await this.ui.sendInput('skill')
await this.ui.sendInput('will-boot')
const results = await promise
const version = versionUtil.generateVersion().dirValue
testUtil.assertFileByNameInGeneratedFiles(
`will-boot.${version}.listener.ts`,
results.files
)
}
private static async finishBuildingUpToNamingNewError(
actionPromise: Promise<FeatureInstallResponse & FeatureActionResponse>
) {
await this.ui.sendInput('')
await this.waitForInput()
await this.ui.sendInput('')
await this.waitForInput()
await this.ui.sendInput('My new error')
await this.ui.sendInput('')
const results = await actionPromise
return results
}
private static async startBuildingNewErrorUntilOptionalDependencies(
skillFeatureAnswer: 'skip' | 'yes' | 'alwaysSkip'
) {
const executer = this.Action('error', 'create', {
shouldAutoHandleDependencies: true,
})
const promise = executer.execute({})
await this.waitForInput()
const message =
this.ui.invocations[this.ui.invocations.length - 2].options.message
assert.isTruthy(message)
assert.doesInclude(message, /2 required/gi)
assert.doesInclude(message, /1 optional/gi)
assert.doesInclude(this.ui.getLastInvocation(), {
command: 'prompt',
options: {
type: 'select',
options: {
choices: [
{
value: 'yes',
label: 'Yes',
},
{
value: 'skip',
label: 'Skip',
},
{
value: 'alwaysSkip',
label: 'Always skip',
},
],
},
},
})
await this.ui.sendInput(skillFeatureAnswer)
await this.ui.sendInput('y')
await this.waitForInput()
await this.ui.sendInput('My new module')
await this.ui.sendInput('it is for testing')
await this.waitForInput()
return { promise }
}
private static fakeInstallCommands() {
CommandServiceImpl.fakeCommand(new RegExp(/yarn|npm/gis), {
code: 0,
})
}
}