@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
191 lines (161 loc) • 5.54 kB
text/typescript
import { StackCleaner } from '@sprucelabs/test-utils'
import chalk from 'chalk'
import durationUtil from '../../utilities/duration.utility'
import {
SpruceTestFile,
SpruceTestFileTest,
TestRunnerStatus,
} from './test.types'
export default class TestLogItemGenerator {
private startTimes: Record<string, number> = {}
private testRunnerStatus!: TestRunnerStatus
public generateLogItemForFile(
file: SpruceTestFile,
status: TestRunnerStatus
): string {
this.testRunnerStatus = status
let logContent = ''
const duration = this.calculateDurationInMsForFile(file)
logContent += `${this.generateStatusBlock(file.status)} ${file.path}${
duration ? ` ${this.generateDuration(file.status, duration)}` : ''
}\n`
if (file.tests) {
file.tests.forEach((test) => {
const bullet = this.bulletBasedOnStatus(test.status)
logContent += ` ${bullet} ^-${
test.name
}^ ${this.generateDuration(test.status, test.duration)}\n`
})
}
if (file.status === 'running') {
const pendingKey = `${file.path}-pending-${file.tests?.length ?? 0}`
logContent += ` ^-${'Running next test... ⚡️⚡️⚡️'}^ ${this.generateDuration(
'running',
this.calculateDurationInMs(pendingKey)
)}\n`
}
return logContent
}
private generateDuration(status: any, duration: number) {
if (duration === 0) {
return ''
}
const durationColor = this.colorBasedOnStatus(status)
return `^${durationColor}(${durationUtil.msToFriendly(duration)})^`
}
private bulletBasedOnStatus(
status: SpruceTestFileTest['status'] | 'running'
) {
let bullet = 'y'
switch (status) {
case 'running':
bullet = '^g(running)^'
break
case 'passed':
bullet = '^g√^'
break
case 'failed':
bullet = '^rx^'
break
case 'pending':
case 'skipped':
bullet = '^y(skipped)^'
break
case 'todo':
bullet = '^y(todo)^'
break
default:
bullet = '??'
break
}
return bullet
}
private calculateDurationInMsForFile(file: SpruceTestFile): number {
if (file.status !== 'running' && file.tests) {
return file.tests.reduce((time, test) => {
time += test.duration
return time
}, 0)
}
const key = file.path
return this.calculateDurationInMs(key)
}
private calculateDurationInMs(key: string) {
if (!this.startTimes[key]) {
this.startTimes[key] = new Date().getTime()
}
const delta = new Date().getTime() - this.startTimes[key]
return delta
}
public generateErrorLogItemForFile(file: SpruceTestFile): string {
let errorContent = ''
file.tests?.forEach((test) => {
test.errorMessages?.forEach((message) => {
const cleaned = message ?? StackCleaner.clean(message)
errorContent += ` ${chalk.red(file.path)}\n`
errorContent += ` - ${chalk.red(test.name)}\n\n`
errorContent +=
cleaned.replace(/\n+ {4}at/i, '\n\n\n at') + '\n\n\n'
})
})
if (!errorContent && file.errorMessage) {
errorContent += `${chalk.red(file.path)}\n`
errorContent += file.errorMessage + '\n\n\n'
}
return errorContent
}
public resetStartTimes() {
this.startTimes = {}
}
private generateStatusBlock(status: SpruceTestFile['status']) {
const bgColor = this.colorBasedOnStatus(status)
let statusLabel = status as string
let color = 'k'
let padding = 10
switch (status) {
case 'passed':
padding = 11
color = 'w'
break
case 'failed':
padding = 11
color = 'w'
break
}
if (status === 'running' && this.testRunnerStatus === 'stopped') {
statusLabel = 'stopped'
color = 'w'
}
return `^b^#^${bgColor}^${color}^+${this.centerStringWithSpaces(
statusLabel,
padding
)}^`
}
private colorBasedOnStatus(status: SpruceTestFile['status']) {
let color = 'y'
switch (status) {
case 'passed':
color = 'g'
break
case 'failed':
color = 'r'
break
}
if (status === 'running' && this.testRunnerStatus === 'stopped') {
color = 'b'
}
return color
}
private centerStringWithSpaces(text: string, numberOfSpaces: number) {
text = text.trim()
let l = text.length
let w2 = Math.floor(numberOfSpaces / 2)
let l2 = Math.floor(l / 2)
let s = new Array(w2 - l2 + 1).join(' ')
text = s + text + s
if (text.length < numberOfSpaces) {
text += new Array(numberOfSpaces - text.length + 1).join(' ')
}
return text
}
}