@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
362 lines • 13.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.optionsSchema = void 0;
const path_1 = __importDefault(require("path"));
const spruce_skill_utils_1 = require("@sprucelabs/spruce-skill-utils");
const open_1 = __importDefault(require("open"));
const testOptions_schema_1 = __importDefault(require("./../../../.spruce/schemas/spruceCli/v2020_07_22/testOptions.schema"));
const SpruceError_1 = __importDefault(require("../../../errors/SpruceError"));
const AbstractAction_1 = __importDefault(require("../../AbstractAction"));
const TestReporter_1 = __importDefault(require("../TestReporter"));
const TestRunner_1 = __importDefault(require("../TestRunner"));
exports.optionsSchema = testOptions_schema_1.default;
class TestAction extends AbstractAction_1.default {
optionsSchema = exports.optionsSchema;
invocationMessage = 'Starting tests... 🛡';
testReporter;
testRunner;
runnerStatus = 'hold';
pattern;
inspect;
holdPromiseResolve;
lastTestResults = { totalTestFiles: 0 };
originalInspect;
watcher;
watchMode = 'off';
isRpTraining = false;
fileChangeTimeout;
hasWatchEverBeenEnabled = false;
watchDelaySec = 2;
constructor(options) {
super(options);
}
async execute(options) {
if (!options.watchMode) {
const settings = this.Service('settings');
options.watchMode = settings.get('test.watchMode') ?? 'off';
}
const normalizedOptions = this.validateAndNormalizeOptions(options);
const { pattern, shouldReportWhileRunning, inspect, shouldHoldAtStart, watchMode, shouldReturnImmediately, } = normalizedOptions;
this.originalInspect = inspect ?? 5200;
this.inspect = inspect;
this.pattern = pattern;
this.hasWatchEverBeenEnabled = watchMode !== 'off';
this.watchMode = watchMode;
if (shouldReportWhileRunning) {
this.testReporter = new TestReporter_1.default({
cwd: this.cwd,
watchMode: this.watchMode,
status: shouldHoldAtStart ? 'stopped' : 'ready',
isDebugging: !!inspect,
filterPattern: pattern ?? undefined,
isRpTraining: this.isRpTraining,
handleRestart: this.handleRestart.bind(this),
handleStartStop: this.handleStartStop.bind(this),
handleQuit: this.handleQuit.bind(this),
handleRerunTestFile: this.handleRerunTestFile.bind(this),
handleOpenTestFile: this.handleOpenTestFile.bind(this),
handleFilterPatternChange: this.handleFilterPatternChange.bind(this),
handleToggleDebug: this.handleToggleDebug.bind(this),
handletoggleStandardWatch: this.handletoggleStandardWatch.bind(this),
handleToggleSmartWatch: this.handleToggleSmartWatch?.bind(this),
handleToggleRpTraining: this.handleToggleRpTraining?.bind(this),
});
await this.testReporter.start();
}
this.watcher = this.getFeature('watch');
void this.watcher.startWatching({ delay: 0 });
await this.emitter.on('watcher.did-detect-change', this.handleFileChange.bind(this));
this.runnerStatus = shouldHoldAtStart ? 'hold' : 'run';
const promise = this.startTestRunner(normalizedOptions);
if (shouldReturnImmediately) {
return {
meta: {
promise,
test: this,
},
};
}
void this.emitter.emit('test.reporter-did-boot', {
reporter: this,
});
const testResults = await promise;
await this.watcher?.stopWatching();
await this.testReporter?.destroy();
const actionResponse = {
meta: { testResults },
summaryLines: [
`Test files: ${testResults.totalTestFiles}`,
`Tests: ${testResults.totalTests ?? '0'}`,
`Passed: ${testResults.totalPassed ?? '0'}`,
`Failed: ${testResults.totalFailed ?? '0'}`,
`Skipped: ${testResults.totalSkipped ?? '0'}`,
`Todo: ${testResults.totalTodo ?? '0'}`,
],
};
if (testResults.totalFailed ?? 0 > 0) {
actionResponse.errors =
this.generateErrorsFromTestResults(testResults);
}
return actionResponse;
}
handleFileChange(payload) {
if (this.watchMode === 'off' ||
!(this.runnerStatus === 'run' || this.runnerStatus == 'hold')) {
return;
}
const { changes } = payload;
let shouldRestart = false;
const filesWeCareAbout = [];
for (const change of changes) {
const { path, name } = change.values;
if (this.doWeCareAboutThisFileChanging(path)) {
this.testReporter?.setStatusLabel(`Built file: ${name}`);
shouldRestart = true;
filesWeCareAbout.push(path);
break;
}
}
if (shouldRestart) {
if (this.fileChangeTimeout) {
clearTimeout(this.fileChangeTimeout);
}
this.testReporter?.startCountdownTimer(this.watchDelaySec);
this.fileChangeTimeout = setTimeout(() => {
if (this.watchMode === 'smart') {
const smartFilter = this.generateFilterFromChangedFiles(filesWeCareAbout);
if (smartFilter.length > 0) {
this.handleFilterPatternChange(smartFilter);
}
else {
this.restart();
}
}
else {
this.restart();
}
}, this.watchDelaySec * 1000);
}
}
generateFilterFromChangedFiles(filesWeCareAbout) {
const filter = filesWeCareAbout
.filter((file) => file.search('test.js') > -1)
.map((file) => this.fileToFilterPattern(file))
.join(' ');
return filter;
}
doWeCareAboutThisFileChanging(path) {
const ext = path_1.default.extname(path);
if (path.search('testDirsAndFiles') > -1 ||
path.search('.change_cache') > -1) {
return false;
}
if (ext === '.js') {
return true;
}
return false;
}
handleToggleDebug() {
if (this.inspect) {
this.inspect = undefined;
}
else {
this.inspect = this.originalInspect;
}
this.testReporter?.setIsDebugging(!!this.inspect);
this.restart();
}
handletoggleStandardWatch() {
if (this.watchMode === 'standard') {
this.testReporter?.setWatchMode('off');
}
else {
this.setWatchMode('standard');
}
}
handleToggleSmartWatch() {
if (this.watchMode === 'smart') {
this.setWatchMode('off');
}
else {
this.setWatchMode('smart');
}
}
async handleToggleRpTraining() {
// this.isRpTraining = !this.isRpTraining
// this.testReporter?.setIsRpTraining(this.isRpTraining)
// if (this.isRpTraining) {
await this.testReporter?.askForTrainingToken();
// }
}
setWatchMode(mode) {
this.watchMode = mode;
this.testReporter?.setWatchMode(mode);
this.hasWatchEverBeenEnabled = true;
}
restart() {
this.runnerStatus = 'restart';
this.kill();
}
handleQuit() {
this.runnerStatus = 'quit';
this.kill();
}
handleRerunTestFile(file) {
const name = this.fileToFilterPattern(file);
this.testReporter?.setFilterPattern(name);
this.handleFilterPatternChange(name);
}
fileToFilterPattern(file) {
const filename = path_1.default
.basename(file, '.ts')
.replace('.tsx', '')
.replace('.js', '');
const dirname = path_1.default.dirname(file).split(path_1.default.sep).pop() ?? '';
const name = path_1.default.join(dirname, filename);
return name;
}
handleFilterPatternChange(filterPattern) {
this.pattern = filterPattern;
this.testReporter?.setFilterPattern(filterPattern);
this.restart();
}
handleStartStop() {
if (this.runnerStatus === 'hold') {
this.runnerStatus = 'run';
this.holdPromiseResolve?.();
this.holdPromiseResolve = undefined;
}
else if (this.runnerStatus === 'run') {
this.runnerStatus = 'hold';
this.kill();
}
}
handleRestart() {
this.restart();
}
kill() {
this.testRunner?.kill();
this.holdPromiseResolve?.();
this.holdPromiseResolve = undefined;
}
async handleOpenTestFile(fileName) {
await this.openTestFile(fileName);
}
async startTestRunner(options) {
if (this.runnerStatus === 'hold') {
await this.waitForStart();
}
if (this.runnerStatus === 'quit') {
return this.lastTestResults;
}
this.testReporter?.setStatus('ready');
this.testReporter?.stopCountdownTimer();
this.testRunner = new TestRunner_1.default({
cwd: this.cwd,
commandService: this.Service('command'),
});
let firstUpdate = true;
if (this.testReporter) {
await this.testRunner.on('did-update', (payload) => {
if (firstUpdate) {
firstUpdate = false;
this.testReporter?.setStatus('running');
this.testReporter?.reset();
}
if (this.watchMode === 'smart' &&
payload.results.totalFailed > 0) {
const failed = payload.results.testFiles.find((file) => file.status === 'failed');
if (failed) {
const pattern = this.fileToFilterPattern(failed.path);
if (this.pattern !== pattern) {
this.handleFilterPatternChange(pattern);
}
return;
}
}
this.testReporter?.updateResults(payload.results);
this.testReporter?.render();
});
await this.testRunner.on('did-error', (payload) => {
this.testReporter?.appendError(payload.message);
this.testReporter?.render();
});
}
this.runnerStatus = 'run';
let testResults = await this.testRunner.run({
pattern: this.pattern,
debugPort: this.inspect,
});
if (
//@ts-ignore
this.runnerStatus !== 'restart' &&
(!options.shouldReportWhileRunning ||
!this.hasWatchEverBeenEnabled ||
this.runnerStatus === 'quit')) {
return testResults;
}
if (this.runnerStatus === 'run' &&
this.watchMode === 'smart' &&
this.testRunner?.hasFailedTests() === false &&
!this.testRunner?.hasSkippedTests() &&
(this.pattern ?? []).length > 0) {
this.testReporter?.setStatusLabel('Restarting...');
this.runnerStatus = 'restart';
this.testReporter?.startCountdownTimer(3);
return await new Promise((resolve) => {
setTimeout(() => {
this.pattern = '';
this.testReporter?.setFilterPattern('');
resolve(this.startTestRunner(options));
}, 3000);
});
}
if (this.runnerStatus === 'run') {
this.runnerStatus = 'hold';
}
this.testReporter?.setStatus('stopped');
this.lastTestResults = testResults;
return this.startTestRunner(options);
}
async waitForStart() {
await new Promise((resolve) => {
this.runnerStatus = 'hold';
this.holdPromiseResolve = resolve;
});
}
async openTestFile(fileName) {
const path = spruce_skill_utils_1.diskUtil.resolvePath(this.cwd, 'src', '__tests__', fileName);
await (0, open_1.default)(path);
}
generateErrorsFromTestResults(testResults) {
const errors = [];
testResults.testFiles?.forEach((file) => {
file.tests?.forEach((test) => {
test.errorMessages?.forEach((message) => {
const err = this.mapErrorResultToSpruceError(test, file, message);
errors.push(err);
});
});
});
if (errors.length > 0) {
return errors;
}
return undefined;
}
mapErrorResultToSpruceError(test, file, message) {
return new SpruceError_1.default({
code: 'TEST_FAILED',
testName: test.name,
fileName: file.path,
errorMessage: message,
});
}
getWatchMode() {
return this.watchMode;
}
}
exports.default = TestAction;
//# sourceMappingURL=TestAction.js.map